Skip to content

Commit 5cd6606

Browse files
alexmarkovcommit-bot@chromium.org
authored andcommitted
[vm/aot/tfa/nnbd] Fix handling of non-nullable types in NNBD strong mode
This change contains the following fixes for NNBD strong mode in TFA: * TypesBuilder.fromStaticType now correctly applies nullability of TypeParameterType even if bound is non-nullable. * UnknownType is introduced to represent a type argument not known at compile time. Previously AnyType was used for that purpose, and it could pass isSubtypeOfRuntimeType check for non-nullable Object type. Fixes dart-lang#41452 Change-Id: Ie836c99b38ad288aa68d9aae48e50844f9f949d5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/143310 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Alexander Markov <[email protected]>
1 parent 8044a97 commit 5cd6606

File tree

7 files changed

+169
-36
lines changed

7 files changed

+169
-36
lines changed

pkg/vm/lib/transformations/type_flow/analysis.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ class _DirectInvocation extends _Invocation {
230230
// does not throw exception.
231231
initializerResult = new Type.nullable(initializerResult);
232232
}
233+
if (kPrintTrace) {
234+
tracePrint("Result of ${field} initializer: $initializerResult");
235+
}
233236
fieldValue.setValue(initializerResult, typeFlowAnalysis,
234237
field.isStatic ? null : args.receiver);
235238
fieldValue.isInitialized = true;
@@ -989,7 +992,7 @@ class GenericInterfacesInfoImpl implements GenericInterfacesInfo {
989992
result = new List<Type>(flattenedTypeArgs.length);
990993
for (int i = 0; i < flattenedTypeArgs.length; ++i) {
991994
final translated = closedTypeTranslator.translate(flattenedTypeArgs[i]);
992-
assertx(translated is RuntimeType || translated is AnyType);
995+
assertx(translated is RuntimeType || translated is UnknownType);
993996
result[i] = translated;
994997
}
995998
cachedFlattenedTypeArgsForNonGeneric[klass] = result;

pkg/vm/lib/transformations/type_flow/summary.dart

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ class Extract extends Statement {
339339

340340
void extractType(ConcreteType c) {
341341
if (c.typeArgs == null) {
342-
extractedType = const AnyType();
342+
extractedType = const UnknownType();
343343
} else {
344344
final interfaceOffset = typeHierarchy.genericInterfaceOffsetFor(
345345
c.cls.classNode, referenceClass);
@@ -364,12 +364,12 @@ class Extract extends Statement {
364364
}
365365
}
366366
} else {
367-
assertx(typeArg is AnyType);
367+
assertx(typeArg is UnknownType);
368368
}
369369
if (extractedType == null || extracted == extractedType) {
370370
extractedType = extracted;
371371
} else {
372-
extractedType = const AnyType();
372+
extractedType = const UnknownType();
373373
}
374374
}
375375
}
@@ -381,7 +381,7 @@ class Extract extends Statement {
381381
argType.types.forEach(extractType);
382382
}
383383

384-
return extractedType ?? const AnyType();
384+
return extractedType ?? const UnknownType();
385385
}
386386
}
387387

@@ -414,7 +414,7 @@ class CreateConcreteType extends Statement {
414414
final types = new List<Type>(flattenedTypeArgs.length);
415415
for (int i = 0; i < types.length; ++i) {
416416
final computed = flattenedTypeArgs[i].getComputedType(computedTypes);
417-
assertx(computed is RuntimeType || computed is AnyType);
417+
assertx(computed is RuntimeType || computed is UnknownType);
418418
if (computed is RuntimeType) hasRuntimeType = true;
419419
types[i] = computed;
420420
}
@@ -424,7 +424,7 @@ class CreateConcreteType extends Statement {
424424

425425
// Similar to "CreateConcreteType", but creates a "RuntimeType" rather than a
426426
// "ConcreteType". Unlike a "ConcreteType", none of the type arguments can be
427-
// missing ("AnyType").
427+
// missing ("UnknownType").
428428
class CreateRuntimeType extends Statement {
429429
final Class klass;
430430
final Nullability nullability;
@@ -446,8 +446,8 @@ class CreateRuntimeType extends Statement {
446446
final types = new List<RuntimeType>(flattenedTypeArgs.length);
447447
for (int i = 0; i < types.length; ++i) {
448448
final computed = flattenedTypeArgs[i].getComputedType(computedTypes);
449-
assertx(computed is RuntimeType || computed is AnyType);
450-
if (computed is AnyType) return const AnyType();
449+
assertx(computed is RuntimeType || computed is UnknownType);
450+
if (computed is UnknownType) return const UnknownType();
451451
types[i] = computed;
452452
}
453453
return new RuntimeType(new InterfaceType(klass, nullability), types);
@@ -501,11 +501,11 @@ class TypeCheck extends Statement {
501501
Type argType = arg.getComputedType(computedTypes);
502502
Type checkType = type.getComputedType(computedTypes);
503503
// TODO(sjindel/tfa): Narrow the result if possible.
504-
assertx(checkType is AnyType || checkType is RuntimeType);
504+
assertx(checkType is UnknownType || checkType is RuntimeType);
505505

506506
bool canSkip = true; // Can this check be skipped on this invocation.
507507

508-
if (checkType is AnyType) {
508+
if (checkType is UnknownType) {
509509
// If we don't know what the RHS of the check is going to be, we can't
510510
// guarantee that it will pass.
511511
canSkip = false;

pkg/vm/lib/transformations/type_flow/summary_collector.dart

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,8 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
584584

585585
Summary createSummary(Member member,
586586
{fieldSummaryType: FieldSummaryType.kInitializer}) {
587-
debugPrint("===== ${member} =====");
587+
debugPrint(
588+
"===== ${member}${fieldSummaryType == FieldSummaryType.kFieldGuard ? " (guard)" : ""} =====");
588589
assertx(!member.isAbstract);
589590

590591
_staticTypeContext = new StaticTypeContext(member, _environment);
@@ -779,7 +780,7 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
779780

780781
final numTypeParameters = numTypeParams(member);
781782
for (int i = 0; i < numTypeParameters; ++i) {
782-
args.add(const AnyType());
783+
args.add(const UnknownType());
783784
}
784785

785786
if (hasReceiverArg(member)) {
@@ -2175,16 +2176,16 @@ class RuntimeTypeTranslatorImpl extends DartTypeVisitor<TypeExpr>
21752176
final flattenedTypeExprs = new List<TypeExpr>(flattenedTypeArgs.length);
21762177

21772178
bool createConcreteType = true;
2178-
bool allAnyType = true;
2179+
bool allUnknown = true;
21792180
for (int i = 0; i < flattenedTypeArgs.length; ++i) {
21802181
final typeExpr =
21812182
translate(substitution.substituteType(flattenedTypeArgs[i]));
2182-
if (typeExpr != const AnyType()) allAnyType = false;
2183+
if (typeExpr is! UnknownType) allUnknown = false;
21832184
if (typeExpr is Statement) createConcreteType = false;
21842185
flattenedTypeExprs[i] = typeExpr;
21852186
}
21862187

2187-
if (allAnyType) return type;
2188+
if (allUnknown) return type;
21882189

21892190
if (createConcreteType) {
21902191
return new ConcreteType(
@@ -2199,7 +2200,7 @@ class RuntimeTypeTranslatorImpl extends DartTypeVisitor<TypeExpr>
21992200
// Creates a TypeExpr representing the set of types which can flow through a
22002201
// given DartType.
22012202
//
2202-
// Will return AnyType, RuntimeType or Statement.
2203+
// Will return UnknownType, RuntimeType or Statement.
22032204
TypeExpr translate(DartType type) {
22042205
final cached = typesCache[type];
22052206
if (cached != null) return cached;
@@ -2209,18 +2210,19 @@ class RuntimeTypeTranslatorImpl extends DartTypeVisitor<TypeExpr>
22092210
// class A<T> extends Comparable<A<T>> {}
22102211
//
22112212
// Creating the factored type arguments of A will lead to an infinite loop.
2212-
// We break such loops by inserting an 'AnyType' in place of the currently
2213+
// We break such loops by inserting an 'UnknownType' in place of the currently
22132214
// processed type, ensuring we try to build 'A<T>' in the process of
22142215
// building 'A<T>'.
2215-
typesCache[type] = const AnyType();
2216+
typesCache[type] = const UnknownType();
22162217
final result = type.accept(this);
2217-
assertx(result is AnyType || result is RuntimeType || result is Statement);
2218+
assertx(
2219+
result is UnknownType || result is RuntimeType || result is Statement);
22182220
typesCache[type] = result;
22192221
return result;
22202222
}
22212223

22222224
@override
2223-
TypeExpr defaultDartType(DartType node) => const AnyType();
2225+
TypeExpr defaultDartType(DartType node) => const UnknownType();
22242226

22252227
@override
22262228
TypeExpr visitDynamicType(DynamicType type) => new RuntimeType(type, null);
@@ -2248,7 +2250,7 @@ class RuntimeTypeTranslatorImpl extends DartTypeVisitor<TypeExpr>
22482250
for (var i = 0; i < flattenedTypeArgs.length; ++i) {
22492251
final typeExpr =
22502252
translate(substitution.substituteType(flattenedTypeArgs[i]));
2251-
if (typeExpr == const AnyType()) return const AnyType();
2253+
if (typeExpr == const UnknownType()) return const UnknownType();
22522254
if (typeExpr is! RuntimeType) createRuntimeType = false;
22532255
flattenedTypeExprs[i] = typeExpr;
22542256
}
@@ -2271,7 +2273,7 @@ class RuntimeTypeTranslatorImpl extends DartTypeVisitor<TypeExpr>
22712273
final result = functionTypeVariables[type.parameter];
22722274
if (result != null) return result;
22732275
}
2274-
if (type.parameter.parent is! Class) return const AnyType();
2276+
if (type.parameter.parent is! Class) return const UnknownType();
22752277
final interfaceClass = type.parameter.parent as Class;
22762278
assertx(receiver != null);
22772279
// Undetermined nullability is equivalent to nonNullable when

pkg/vm/lib/transformations/type_flow/transformer.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
190190
if (type is ConcreteType && type.typeArgs != null) {
191191
typeArgs = type.typeArgs
192192
.take(type.numImmediateTypeArgs)
193-
.map((t) => t is AnyType ? null : (t as RuntimeType).representedType)
193+
.map((t) =>
194+
t is UnknownType ? null : (t as RuntimeType).representedType)
194195
.toList();
195196
}
196197

@@ -256,7 +257,7 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
256257

257258
// If the call is not marked as 'isResultUsed', the 'resultType' will
258259
// not be observed (i.e., it will always be EmptyType). This is the
259-
// case even if the result acutally might be used but is not used by
260+
// case even if the result actually might be used but is not used by
260261
// the summary, e.g. if the result is an argument to a closure call.
261262
// Therefore, we need to pass in 'NullableType(AnyType)' as the
262263
// inferred result type here (since we don't know what it actually

pkg/vm/lib/transformations/type_flow/types.dart

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,15 @@ abstract class TypesBuilder {
9393
if (bound is TypeParameterType) {
9494
result = const AnyType();
9595
} else {
96-
return fromStaticType(bound, canBeNull);
96+
result = fromStaticType(bound, canBeNull);
9797
}
9898
} else {
9999
throw 'Unexpected type ${type.runtimeType} $type';
100100
}
101101
if (nullSafety && type.nullability == Nullability.nonNullable) {
102102
canBeNull = false;
103103
}
104-
if (canBeNull) {
104+
if (canBeNull && result is! NullableType) {
105105
result = new Type.nullable(result);
106106
}
107107
return result;
@@ -186,6 +186,7 @@ abstract class Type extends TypeExpr {
186186
/// Order of precedence between types for evaluation of union/intersection.
187187
enum TypeOrder {
188188
RuntimeType,
189+
Unknown,
189190
Empty,
190191
Nullable,
191192
Any,
@@ -293,7 +294,6 @@ class NullableType extends Type {
293294

294295
/// Type representing any instance except `null`.
295296
/// Semantically equivalent to ConeType of Object, but more efficient.
296-
/// Can also represent a set of types, the set of all types.
297297
class AnyType extends Type {
298298
const AnyType();
299299

@@ -612,7 +612,7 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
612612
int _hashCode;
613613

614614
// May be null if there are no type arguments constraints. The type arguments
615-
// should represent type sets, i.e. `AnyType` or `RuntimeType`. The type
615+
// should represent type sets, i.e. `UnknownType` or `RuntimeType`. The type
616616
// arguments vector is factored against the generic interfaces implemented by
617617
// the class (see [TypeHierarchy.flattenedTypeArgumentsFor]).
618618
//
@@ -693,11 +693,13 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
693693
runtimeType.numImmediateTypeArgs);
694694

695695
for (int i = 0; i < runtimeType.numImmediateTypeArgs; ++i) {
696-
if (usableTypeArgs[i + interfaceOffset] == const AnyType())
696+
final ta = usableTypeArgs[i + interfaceOffset];
697+
if (ta is UnknownType) {
697698
return false;
698-
assertx(usableTypeArgs[i + interfaceOffset] is RuntimeType);
699-
if (!usableTypeArgs[i + interfaceOffset]
700-
.isSubtypeOfRuntimeType(typeHierarchy, runtimeType.typeArgs[i])) {
699+
}
700+
assertx(ta is RuntimeType);
701+
if (!ta.isSubtypeOfRuntimeType(
702+
typeHierarchy, runtimeType.typeArgs[i])) {
701703
return false;
702704
}
703705
}
@@ -864,7 +866,7 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
864866
// class A<T> extends Comparable<A<T>> {}
865867
//
866868
// To avoid these cycles, we approximate generic super-bounded types (the second
867-
// case), so the representation for 'A<String>' would be simply 'AnyType'.
869+
// case), so the representation for 'A<String>' would be simply 'UnknownType'.
868870
// However, approximating non-generic types like 'int' and 'num' (the first
869871
// case) would be too coarse, so we leave an null 'typeArgs' field for these
870872
// types. As a result, when doing an 'isSubtypeOfRuntimeType' against
@@ -963,11 +965,11 @@ class RuntimeType extends Type {
963965
Type union(Type other, TypeHierarchy typeHierarchy) =>
964966
throw "ERROR: RuntimeType does not support union.";
965967

966-
// This only works between "type-set" representations ('AnyType' and
968+
// This only works between "type-set" representations ('UnknownType' and
967969
// 'RuntimeType') and is used when merging type arguments.
968970
@override
969971
Type intersection(Type other, TypeHierarchy typeHierarchy) {
970-
if (other is AnyType) {
972+
if (other is UnknownType) {
971973
return this;
972974
} else if (other is RuntimeType) {
973975
return this == other ? this : const EmptyType();
@@ -1043,3 +1045,52 @@ class RuntimeType extends Type {
10431045
return true;
10441046
}
10451047
}
1048+
1049+
/// Type which is not known at compile time.
1050+
/// It is used as the right-hand-side of type tests.
1051+
class UnknownType extends Type {
1052+
const UnknownType();
1053+
1054+
@override
1055+
int get hashCode => 1019;
1056+
1057+
@override
1058+
bool operator ==(other) => (other is UnknownType);
1059+
1060+
@override
1061+
String toString() => "UNKNOWN";
1062+
1063+
@override
1064+
int get order => TypeOrder.Unknown.index;
1065+
1066+
@override
1067+
bool isSubtypeOf(TypeHierarchy typeHierarchy, Class cls) =>
1068+
throw "ERROR: UnknownType does not support isSubtypeOf.";
1069+
1070+
@override
1071+
Type union(Type other, TypeHierarchy typeHierarchy) {
1072+
if (other is UnknownType || other is RuntimeType) {
1073+
return this;
1074+
}
1075+
throw "ERROR: UnknownType does not support union with ${other.runtimeType}";
1076+
}
1077+
1078+
// This only works between "type-set" representations ('UnknownType' and
1079+
// 'RuntimeType') and is used when merging type arguments.
1080+
@override
1081+
Type intersection(Type other, TypeHierarchy typeHierarchy) {
1082+
if (other is UnknownType || other is RuntimeType) {
1083+
return other;
1084+
}
1085+
throw "ERROR: UnknownType does not support intersection with ${other.runtimeType}";
1086+
}
1087+
1088+
bool isSubtypeOfRuntimeType(TypeHierarchy typeHierarchy, RuntimeType other) {
1089+
final rhs = other._type;
1090+
return (rhs is DynamicType) ||
1091+
(rhs is VoidType) ||
1092+
(rhs is InterfaceType &&
1093+
rhs.classNode == typeHierarchy.coreTypes.objectClass &&
1094+
rhs.nullability != Nullability.nonNullable);
1095+
}
1096+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Regression test for https://github.com/dart-lang/sdk/issues/41452.
6+
// Tests handling of null initializer of covariant field.
7+
// This test requires non-nullable experiment and NNBD strong mode.
8+
9+
// @dart = 2.8
10+
11+
import "package:expect/expect.dart";
12+
13+
class _SplayTreeNode<Node extends _SplayTreeNode<Node>> {
14+
Node? left;
15+
_SplayTreeNode();
16+
}
17+
18+
class _SplayTreeMapNode<V> extends _SplayTreeNode<_SplayTreeMapNode<V>> {
19+
_SplayTreeMapNode();
20+
}
21+
22+
class _SplayTree<Node extends _SplayTreeNode<Node>> {
23+
Node? _root;
24+
25+
add(Node n) {
26+
Node? root = _root;
27+
if (root == null) return;
28+
print(root.left); // Should be inferred as nullable.
29+
}
30+
}
31+
32+
class SplayTreeMap<V> extends _SplayTree<_SplayTreeMapNode<V>> {
33+
_SplayTreeMapNode<V>? _root = _SplayTreeMapNode<V>();
34+
}
35+
36+
void main() {
37+
SplayTreeMap().add(_SplayTreeMapNode());
38+
}

0 commit comments

Comments
 (0)