Fix nullability propagation for type analysis

This commit is contained in:
Simon Binder 2022-03-02 16:06:30 +01:00
parent a463476c44
commit 9c80fb047b
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
4 changed files with 38 additions and 6 deletions

View File

@ -35,7 +35,8 @@ class ResolvedType {
ResolvedType get withoutNullabilityInfo {
return nullable == null
? this
: ResolvedType(type: type, hint: hint, isArray: isArray);
: ResolvedType(
type: type, hint: hint, isArray: isArray, nullable: null);
}
ResolvedType withNullable(bool nullable) {

View File

@ -41,6 +41,14 @@ class TypeGraph {
bool knowsType(Typeable? t) =>
_knownTypes.containsKey(variables.normalize(t));
bool knowsNullability(Typeable? t) {
final normalized = variables.normalize(t);
final knownType = _knownTypes[normalized];
return (knownType != null && knownType.nullable != null) ||
_knownNullability.containsKey(normalized);
}
void addRelation(TypeRelation relation) {
_relations.add(relation);
}

View File

@ -323,7 +323,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
break;
case TokenType.doublePipe:
// string concatenation.
session._checkAndResolve(e, _textType, arg);
session._checkAndResolve(e, _textType.withoutNullabilityInfo, arg);
session._addRelation(NullableIfSomeOtherIs(e, [e.left, e.right]));
const childExpectation = ExactTypeExpectation.laxly(_textType);
visit(e.left, childExpectation);
@ -502,7 +502,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
case 'trim':
case 'upper':
nullableIfChildIs();
return _textType;
return _textType.withoutNullabilityInfo;
case 'group_concat':
return _textType.withNullable(true);
case 'date':
@ -668,8 +668,15 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
void _lazyCopy(Typeable to, Typeable? from, {bool makeNullable = false}) {
if (session.graph.knowsType(from)) {
var type = session.typeOf(from)!;
if (makeNullable) type = type.withNullable(true);
session._markTypeResolved(to, type);
if (makeNullable) {
type = type.withNullable(true);
session._markTypeResolved(to, type);
} else if (session.graph.knowsNullability(from)) {
session._markTypeResolved(to, type);
} else {
session._markTypeResolved(to, type);
session._addRelation(NullableIfSomeOtherIs(to, [from!]));
}
} else {
session._addRelation(CopyTypeFrom(to, from, makeNullable: makeNullable));
}

View File

@ -83,7 +83,7 @@ WITH RECURSIVE
SELECT x+1 FROM cnt
LIMIT 1000000
)
SELECT x FROM cnt;
SELECT x FROM cnt;
''');
final expressions = content.root.allDescendants.whereType<Expression>();
@ -92,4 +92,20 @@ WITH RECURSIVE
everyElement(isNotNull),
);
});
test('concatenation is nullable when any part is', () {
// https://github.com/simolus3/moor/issues/1719
final engine = SqlEngine()
..registerTableFromSql('CREATE TABLE foobar (foo TEXT, bar TEXT);');
final content =
engine.analyze("SELECT foo, bar, foo || ' ' || bar FROM foobar;");
final columns = (content.root as SelectStatement).resolvedColumns!;
expect(
columns.map(content.typeOf),
everyElement(isA<ResolveResult>()
.having((e) => e.type?.nullable, 'type.nullable', isTrue)));
});
}