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 { ResolvedType get withoutNullabilityInfo {
return nullable == null return nullable == null
? this ? this
: ResolvedType(type: type, hint: hint, isArray: isArray); : ResolvedType(
type: type, hint: hint, isArray: isArray, nullable: null);
} }
ResolvedType withNullable(bool nullable) { ResolvedType withNullable(bool nullable) {

View File

@ -41,6 +41,14 @@ class TypeGraph {
bool knowsType(Typeable? t) => bool knowsType(Typeable? t) =>
_knownTypes.containsKey(variables.normalize(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) { void addRelation(TypeRelation relation) {
_relations.add(relation); _relations.add(relation);
} }

View File

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

View File

@ -83,7 +83,7 @@ WITH RECURSIVE
SELECT x+1 FROM cnt SELECT x+1 FROM cnt
LIMIT 1000000 LIMIT 1000000
) )
SELECT x FROM cnt; SELECT x FROM cnt;
'''); ''');
final expressions = content.root.allDescendants.whereType<Expression>(); final expressions = content.root.allDescendants.whereType<Expression>();
@ -92,4 +92,20 @@ WITH RECURSIVE
everyElement(isNotNull), 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)));
});
} }