Fix nullability analysis for COALESCE and IFNULL

This commit is contained in:
Simon Binder 2021-07-26 20:39:59 +02:00
parent 7b86f69102
commit 33fd36df80
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
3 changed files with 29 additions and 9 deletions

View File

@ -38,8 +38,10 @@ class CopyEncapsulating extends TypeRelation implements MultiSourceRelation {
final List<Typeable?> from;
final CastMode? cast;
final EncapsulatingNullability nullability;
CopyEncapsulating(this.target, this.from, [this.cast]);
CopyEncapsulating(this.target, this.from,
[this.cast, this.nullability = EncapsulatingNullability.nullIfAny]);
}
/// Dependency declaring that [first] and [second] have the same type. This is
@ -77,6 +79,11 @@ enum CastMode {
boolean,
}
enum EncapsulatingNullability {
nullIfAny,
nullIfAll,
}
/// Dependency declaring that [target] has the same type as [other] after
/// casting it with [cast].
class CopyAndCast extends TypeRelation implements DirectedRelation {

View File

@ -118,7 +118,7 @@ class TypeGraph {
if (edge is CopyEncapsulating) {
if (!knowsType(edge.target)) {
final fromTypes = edge.from.map((t) => this[t]).where((e) => e != null);
var encapsulated = _encapsulate(fromTypes);
var encapsulated = _encapsulate(fromTypes, edge.nullability);
if (encapsulated != null) {
if (edge.cast != null) {
@ -148,19 +148,31 @@ class TypeGraph {
}
}
ResolvedType? _encapsulate(Iterable<ResolvedType?> targets) {
ResolvedType? _encapsulate(
Iterable<ResolvedType?> targets, EncapsulatingNullability nullability) {
return targets.fold<ResolvedType?>(null, (previous, element) {
if (previous == null) return element;
final previousType = previous.type;
final elementType = element!.type;
final eitherNullable =
previous.nullable == true || element.nullable == true;
bool nullableTogether;
switch (nullability) {
case EncapsulatingNullability.nullIfAny:
nullableTogether =
previous.nullable == true || element.nullable == true;
break;
case EncapsulatingNullability.nullIfAll:
nullableTogether =
previous.nullable == true && element.nullable == true;
break;
}
if (previousType == elementType || elementType == BasicType.nullType) {
return previous.withNullable(eitherNullable);
return previous.withNullable(nullableTogether);
}
if (previousType == BasicType.nullType) {
return element.withNullable(nullableTogether);
}
if (previousType == BasicType.nullType) return element.withNullable(true);
bool isIntOrNumeric(BasicType? type) {
return type == BasicType.int || type == BasicType.real;
@ -168,7 +180,7 @@ class TypeGraph {
// encapsulate two different numeric types to real
if (isIntOrNumeric(previousType) && isIntOrNumeric(elementType)) {
return ResolvedType(type: BasicType.real, nullable: eitherNullable);
return ResolvedType(type: BasicType.real, nullable: nullableTogether);
}
// fallback to text if everything else fails

View File

@ -531,7 +531,8 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
return null;
case 'coalesce':
case 'ifnull':
session._addRelation(CopyEncapsulating(e, params));
session._addRelation(CopyEncapsulating(
e, params, null, EncapsulatingNullability.nullIfAll));
for (final param in params) {
session._addRelation(DefaultType(param, isNullable: true));
}