Start with type resolution

This commit is contained in:
Simon Binder 2019-06-27 16:40:48 +02:00
parent db92059610
commit 7e916b9d74
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
12 changed files with 245 additions and 27 deletions

View File

@ -15,7 +15,7 @@ part 'steps/set_parent_visitor.dart';
part 'steps/type_resolver.dart';
part 'types/data.dart';
part 'types/resolution.dart';
part 'types/resolver.dart';
part 'types/typeable.dart';

View File

@ -4,6 +4,7 @@ class AnalysisContext {
final List<AnalysisError> errors = [];
final AstNode root;
final String sql;
final TypeResolver types = TypeResolver();
AnalysisContext(this.root, this.sql);

View File

@ -1,9 +1,16 @@
part of '../analysis.dart';
// https://www.sqlite.org/lang_corefunc.html
final abs = StaticTypeFunction(
name: 'ABS', inputs: [NumericType()], output: NumericType());
final abs = _AbsFunction();
final coreFunctions = [
class _AbsFunction extends SqlFunction {
_AbsFunction() : super('ABS');
@override
void register(AnalysisContext context, Typeable functionCall,
List<Typeable> parameters) {}
}
final coreFunctions = <SqlFunction>[
abs,
];

View File

@ -1,16 +1,19 @@
part of '../analysis.dart';
class SqlFunction with Referencable, VisibleToChildren {
abstract class SqlFunction with Referencable, VisibleToChildren {
final String name;
SqlFunction(this.name);
void register(AnalysisContext context, Typeable functionCall,
List<Typeable> parameters);
}
class StaticTypeFunction extends SqlFunction {
/*class StaticTypeFunction extends SqlFunction {
final List<SqlType> inputs;
final SqlType output;
StaticTypeFunction(
{@required String name, @required this.inputs, @required this.output})
: super(name);
}
}*/

View File

@ -1,6 +1,6 @@
part of '../analysis.dart';
abstract class Column with Referencable, Typeable {
abstract class Column with Referencable implements Typeable {
String get name;
}

View File

@ -1,3 +1,116 @@
part of '../analysis.dart';
class TypeResolver extends RecursiveVisitor<void> {}
const _comparisons = [
TokenType.less,
TokenType.lessEqual,
TokenType.more,
TokenType.moreEqual,
TokenType.equal,
TokenType.doubleEqual,
TokenType.exclamationEqual,
TokenType.lessMore,
];
class TypeResolvingVisitor extends RecursiveVisitor<void> {
final AnalysisContext context;
TypeResolver get types => context.types;
TypeResolvingVisitor(this.context);
@override
void visitSelectStatement(SelectStatement e) {
if (e.where != null) {
context.types.suggestBool(e.where);
}
visitChildren(e);
}
@override
void visitResultColumn(ResultColumn e) {
visitChildren(e);
}
@override
void visitFunction(FunctionExpression e) {
// todo handle function calls
visitChildren(e);
}
@override
void visitLimit(Limit e) {
if (e.count != null) {
types.suggestType(e.count, const SqlType.int());
}
if (e.offset != null) {
types.suggestType(e.offset, const SqlType.int());
}
visitChildren(e);
}
@override
void visitBinaryExpression(BinaryExpression e) {
final operator = e.operator.type;
if (operator == TokenType.doublePipe) {
// string concatenation: Will return a string, makes most sense with a
// string.
types
..forceType(e, const SqlType.text())
..suggestType(e.left, const SqlType.text())
..suggestType(e.right, const SqlType.text());
} else if (operator == TokenType.and || operator == TokenType.or) {
types
..suggestBool(e.left)
..suggestBool(e.right)
..forceType(e, const SqlType.int())
..addTypeHint(e, const IsBoolean());
} else if (_comparisons.contains(operator)) {
types
..suggestBool(e)
..suggestSame(e.left, e.right);
} else {
// arithmetic operator
types
..forceType(e, const AnyNumericType())
..suggestType(e.right, const AnyNumericType())
..suggestType(e.left, const AnyNumericType());
}
visitChildren(e);
}
@override
void visitUnaryExpression(UnaryExpression e) {
final operator = e.operator.type;
if (operator == TokenType.plus) {
// unary type does nothing, just returns the value
types.suggestSame(e, e.inner);
} else if (operator == TokenType.minus) {
types
..forceType(e, const AnyNumericType())
..suggestType(e.inner, const AnyNumericType());
}
visitChildren(e);
}
@override
void visitLiteral(Literal e) {
if (e is NullLiteral) {
types.forceType(e, const SqlType.nullType());
} else if (e is NumericLiteral) {
if (e.number.toInt() == e.number) {
types.forceType(e, const SqlType.int());
} else {
types.forceType(e, const SqlType.real());
}
if (e is BooleanLiteral) {
types.addTypeHint(e, const IsBoolean());
}
}
visitChildren(e);
}
}

View File

@ -1,25 +1,72 @@
part of '../analysis.dart';
/// A type that sql expressions can have at runtime.
abstract class SqlType {}
abstract class SqlType {
const SqlType();
const factory SqlType.nullType() = NullType._;
const factory SqlType.int() = IntegerType._;
const factory SqlType.real() = RealType._;
const factory SqlType.text() = TextType._;
const factory SqlType.blob() = BlobType._;
class NullType extends SqlType {}
bool isSubTypeOf(SqlType other);
}
class NumericType extends SqlType {}
class NullType extends SqlType {
const NullType._();
class IntegerType extends NumericType {}
@override
bool isSubTypeOf(SqlType other) => true;
}
class RealType extends NumericType {}
class IntegerType extends SqlType {
const IntegerType._();
class TextType extends SqlType {}
@override
bool isSubTypeOf(SqlType other) => other is IntegerType;
}
class BlobType extends SqlType {}
class RealType extends SqlType {
const RealType._();
@override
bool isSubTypeOf(SqlType other) => other is RealType;
}
class TextType extends SqlType {
const TextType._();
@override
bool isSubTypeOf(SqlType other) => other is TextType;
}
class BlobType extends SqlType {
const BlobType._();
@override
bool isSubTypeOf(SqlType other) => other is BlobType;
}
class AnyNumericType extends SqlType {
const AnyNumericType();
@override
bool isSubTypeOf(SqlType other) {
return other is RealType || other is IntegerType;
}
}
/// Provides more precise hints than the [SqlType]. For instance, booleans are
/// stored as ints in sqlite, but it might be desirable to know whether an
/// expression will actually be a boolean.
abstract class TypeHint {}
abstract class TypeHint {
const TypeHint();
}
class IsBoolean extends TypeHint {}
class IsBoolean extends TypeHint {
const IsBoolean();
}
class IsDateTime extends TypeHint {}
class IsDateTime extends TypeHint {
const IsDateTime();
}

View File

@ -1,3 +0,0 @@
part of '../analysis.dart';
class TypeResolutionState {}

View File

@ -0,0 +1,49 @@
part of '../analysis.dart';
/// Attempts to resolve types of expressions, result columns and variables.
/// As sqlite is pretty lenient at typing and pretty much accepts every value
/// everywhere, this
class TypeResolver {
final Map<Typeable, _ResolvingState> _matchedStates = {};
_ResolvingState _stateFor(Typeable typeable) {
return _matchedStates.putIfAbsent(
typeable, () => _ResolvingState(typeable, this));
}
void finish() {}
/// Suggest that [t] should have the type [type].
void suggestType(Typeable t, SqlType type) {
_stateFor(t).suggested.add(type);
}
void suggestSame(Typeable a, Typeable b) {}
void suggestBool(Typeable t) {
final state = _stateFor(t);
state.suggested.add(const SqlType.int());
state.hints.add(const IsBoolean());
}
/// Marks that [t] will definitely have the type [type].
void forceType(Typeable t, SqlType type) {
_stateFor(t).forced = type;
}
/// Add an additional hint
void addTypeHint(Typeable t, TypeHint hint) {
_stateFor(t).hints.add(hint);
}
}
class _ResolvingState {
final Typeable typeable;
final TypeResolver resolver;
final List<TypeHint> hints = [];
final List<SqlType> suggested = [];
SqlType forced;
_ResolvingState(this.typeable, this.resolver);
}

View File

@ -1,6 +1,4 @@
part of '../analysis.dart';
/// Something that has a type.
mixin Typeable {
TypeResolutionState resolutionState;
}
abstract class Typeable {}

View File

@ -1,3 +1,3 @@
part of '../ast.dart';
abstract class Expression extends AstNode {}
abstract class Expression extends AstNode implements Typeable {}

View File

@ -48,7 +48,10 @@ class SqlEngine {
final scope = _constructRootScope();
ReferenceFinder(globalScope: scope).start(node);
node..accept(ColumnResolver(context))..accept(ReferenceResolver(context));
node
..accept(ColumnResolver(context))
..accept(ReferenceResolver(context))
..accept(TypeResolvingVisitor(context));
return context;
}