mirror of https://github.com/AMT-Cheif/drift.git
Start with type resolution
This commit is contained in:
parent
db92059610
commit
7e916b9d74
|
@ -15,7 +15,7 @@ part 'steps/set_parent_visitor.dart';
|
||||||
part 'steps/type_resolver.dart';
|
part 'steps/type_resolver.dart';
|
||||||
|
|
||||||
part 'types/data.dart';
|
part 'types/data.dart';
|
||||||
part 'types/resolution.dart';
|
part 'types/resolver.dart';
|
||||||
|
|
||||||
part 'types/typeable.dart';
|
part 'types/typeable.dart';
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ class AnalysisContext {
|
||||||
final List<AnalysisError> errors = [];
|
final List<AnalysisError> errors = [];
|
||||||
final AstNode root;
|
final AstNode root;
|
||||||
final String sql;
|
final String sql;
|
||||||
|
final TypeResolver types = TypeResolver();
|
||||||
|
|
||||||
AnalysisContext(this.root, this.sql);
|
AnalysisContext(this.root, this.sql);
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
// https://www.sqlite.org/lang_corefunc.html
|
// https://www.sqlite.org/lang_corefunc.html
|
||||||
final abs = StaticTypeFunction(
|
final abs = _AbsFunction();
|
||||||
name: 'ABS', inputs: [NumericType()], output: NumericType());
|
|
||||||
|
|
||||||
final coreFunctions = [
|
class _AbsFunction extends SqlFunction {
|
||||||
|
_AbsFunction() : super('ABS');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void register(AnalysisContext context, Typeable functionCall,
|
||||||
|
List<Typeable> parameters) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
final coreFunctions = <SqlFunction>[
|
||||||
abs,
|
abs,
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
class SqlFunction with Referencable, VisibleToChildren {
|
abstract class SqlFunction with Referencable, VisibleToChildren {
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
SqlFunction(this.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 List<SqlType> inputs;
|
||||||
final SqlType output;
|
final SqlType output;
|
||||||
|
|
||||||
StaticTypeFunction(
|
StaticTypeFunction(
|
||||||
{@required String name, @required this.inputs, @required this.output})
|
{@required String name, @required this.inputs, @required this.output})
|
||||||
: super(name);
|
: super(name);
|
||||||
}
|
}*/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
abstract class Column with Referencable, Typeable {
|
abstract class Column with Referencable implements Typeable {
|
||||||
String get name;
|
String get name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,116 @@
|
||||||
part of '../analysis.dart';
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,25 +1,72 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
/// A type that sql expressions can have at runtime.
|
/// 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
|
/// 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
|
/// stored as ints in sqlite, but it might be desirable to know whether an
|
||||||
/// expression will actually be a boolean.
|
/// 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();
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
part of '../analysis.dart';
|
|
||||||
|
|
||||||
class TypeResolutionState {}
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
part of '../analysis.dart';
|
part of '../analysis.dart';
|
||||||
|
|
||||||
/// Something that has a type.
|
/// Something that has a type.
|
||||||
mixin Typeable {
|
abstract class Typeable {}
|
||||||
TypeResolutionState resolutionState;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
part of '../ast.dart';
|
part of '../ast.dart';
|
||||||
|
|
||||||
abstract class Expression extends AstNode {}
|
abstract class Expression extends AstNode implements Typeable {}
|
||||||
|
|
|
@ -48,7 +48,10 @@ class SqlEngine {
|
||||||
final scope = _constructRootScope();
|
final scope = _constructRootScope();
|
||||||
|
|
||||||
ReferenceFinder(globalScope: scope).start(node);
|
ReferenceFinder(globalScope: scope).start(node);
|
||||||
node..accept(ColumnResolver(context))..accept(ReferenceResolver(context));
|
node
|
||||||
|
..accept(ColumnResolver(context))
|
||||||
|
..accept(ReferenceResolver(context))
|
||||||
|
..accept(TypeResolvingVisitor(context));
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue