Take variance into account when computing rti checks
Change-Id: Ie3f93a6ac1fbf796b1c66bf29d2e9de51f5dee13
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96942
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/js_backend/runtime_types.dart b/pkg/compiler/lib/src/js_backend/runtime_types.dart
index 7af5856..e5132c6 100644
--- a/pkg/compiler/lib/src/js_backend/runtime_types.dart
+++ b/pkg/compiler/lib/src/js_backend/runtime_types.dart
@@ -2000,14 +2000,32 @@
Set<ClassEntity> typeLiterals = new Set<ClassEntity>();
Set<ClassEntity> typeArguments = new Set<ClassEntity>();
+ // The [liveTypeVisitor] is used to register class use in the type of
+ // instantiated objects like `new T` and the function types of
+ // tear offs and closures.
+ //
+ // A type found in a covariant position of such types is considered live
+ // whereas a type found in a contravariant position of such types is
+ // considered tested.
+ //
+ // For instance
+ //
+ // new A<B Function(C)>();
+ //
+ // makes A and B live but C tested.
TypeVisitor liveTypeVisitor =
new TypeVisitor(onClass: (ClassEntity cls, {TypeVisitorState state}) {
ClassUse classUse = classUseMap.putIfAbsent(cls, () => new ClassUse());
switch (state) {
- case TypeVisitorState.typeArgument:
+ case TypeVisitorState.covariantTypeArgument:
classUse.typeArgument = true;
typeArguments.add(cls);
break;
+ case TypeVisitorState.contravariantTypeArgument:
+ classUse.typeArgument = true;
+ classUse.checkedTypeArgument = true;
+ typeArguments.add(cls);
+ break;
case TypeVisitorState.typeLiteral:
classUse.typeLiteral = true;
typeLiterals.add(cls);
@@ -2017,15 +2035,31 @@
}
});
+ // The [testedTypeVisitor] is used to register class use in type tests like
+ // `o is T` and `o as T` (both implicit and explicit).
+ //
+ // A type found in a covariant position of such types is considered tested
+ // whereas a type found in a contravariant position of such types is
+ // considered live.
+ //
+ // For instance
+ //
+ // o is A<B Function(C)>;
+ //
+ // makes A and B tested but C live.
TypeVisitor testedTypeVisitor =
new TypeVisitor(onClass: (ClassEntity cls, {TypeVisitorState state}) {
ClassUse classUse = classUseMap.putIfAbsent(cls, () => new ClassUse());
switch (state) {
- case TypeVisitorState.typeArgument:
+ case TypeVisitorState.covariantTypeArgument:
classUse.typeArgument = true;
classUse.checkedTypeArgument = true;
typeArguments.add(cls);
break;
+ case TypeVisitorState.contravariantTypeArgument:
+ classUse.typeArgument = true;
+ typeArguments.add(cls);
+ break;
case TypeVisitorState.typeLiteral:
break;
case TypeVisitorState.direct:
@@ -2046,31 +2080,32 @@
classUse.directInstance = true;
FunctionType callType = _types.getCallType(type);
if (callType != null) {
- testedTypeVisitor.visitType(callType, TypeVisitorState.direct);
+ liveTypeVisitor.visitType(callType, TypeVisitorState.direct);
}
});
for (FunctionEntity element
in codegenWorldBuilder.staticFunctionsNeedingGetter) {
FunctionType functionType = _elementEnvironment.getFunctionType(element);
- testedTypeVisitor.visitType(functionType, TypeVisitorState.direct);
+ liveTypeVisitor.visitType(functionType, TypeVisitorState.direct);
}
for (FunctionEntity element in codegenWorldBuilder.closurizedMembers) {
FunctionType functionType = _elementEnvironment.getFunctionType(element);
- testedTypeVisitor.visitType(functionType, TypeVisitorState.direct);
+ liveTypeVisitor.visitType(functionType, TypeVisitorState.direct);
}
void processMethodTypeArguments(_, Set<DartType> typeArguments) {
for (DartType typeArgument in typeArguments) {
- liveTypeVisitor.visit(typeArgument, TypeVisitorState.typeArgument);
+ liveTypeVisitor.visit(
+ typeArgument, TypeVisitorState.covariantTypeArgument);
}
}
codegenWorldBuilder.forEachStaticTypeArgument(processMethodTypeArguments);
codegenWorldBuilder.forEachDynamicTypeArgument(processMethodTypeArguments);
codegenWorldBuilder.liveTypeArguments.forEach((DartType type) {
- liveTypeVisitor.visitType(type, TypeVisitorState.typeArgument);
+ liveTypeVisitor.visitType(type, TypeVisitorState.covariantTypeArgument);
});
codegenWorldBuilder.constTypeLiterals.forEach((DartType type) {
liveTypeVisitor.visitType(type, TypeVisitorState.typeLiteral);
@@ -2123,7 +2158,8 @@
DartType bound =
_elementEnvironment.getTypeVariableBound(typeVariable.element);
processCheckedType(bound);
- liveTypeVisitor.visit(bound, TypeVisitorState.typeArgument);
+ liveTypeVisitor.visit(
+ bound, TypeVisitorState.covariantTypeArgument);
}
}
}
@@ -2840,7 +2876,12 @@
'TypeCheck(cls=$cls,needsIs=$needsIs,substitution=$substitution)';
}
-enum TypeVisitorState { direct, typeArgument, typeLiteral }
+enum TypeVisitorState {
+ direct,
+ covariantTypeArgument,
+ contravariantTypeArgument,
+ typeLiteral,
+}
class TypeVisitor extends DartTypeVisitor<void, TypeVisitorState> {
Set<FunctionTypeVariable> _visitedFunctionTypeVariables =
@@ -2856,6 +2897,34 @@
visitType(DartType type, TypeVisitorState state) => type.accept(this, state);
+ TypeVisitorState covariantArgument(TypeVisitorState state) {
+ switch (state) {
+ case TypeVisitorState.direct:
+ return TypeVisitorState.covariantTypeArgument;
+ case TypeVisitorState.covariantTypeArgument:
+ return TypeVisitorState.covariantTypeArgument;
+ case TypeVisitorState.contravariantTypeArgument:
+ return TypeVisitorState.contravariantTypeArgument;
+ case TypeVisitorState.typeLiteral:
+ return TypeVisitorState.typeLiteral;
+ }
+ throw new UnsupportedError("Unexpected TypeVisitorState $state");
+ }
+
+ TypeVisitorState contravariantArgument(TypeVisitorState state) {
+ switch (state) {
+ case TypeVisitorState.direct:
+ return TypeVisitorState.contravariantTypeArgument;
+ case TypeVisitorState.covariantTypeArgument:
+ return TypeVisitorState.contravariantTypeArgument;
+ case TypeVisitorState.contravariantTypeArgument:
+ return TypeVisitorState.covariantTypeArgument;
+ case TypeVisitorState.typeLiteral:
+ return TypeVisitorState.typeLiteral;
+ }
+ throw new UnsupportedError("Unexpected TypeVisitorState $state");
+ }
+
visitTypes(List<DartType> types, TypeVisitorState state) {
for (DartType type in types) {
visitType(type, state);
@@ -2874,11 +2943,7 @@
if (onClass != null) {
onClass(type.element, state: state);
}
- visitTypes(
- type.typeArguments,
- state == TypeVisitorState.typeLiteral
- ? state
- : TypeVisitorState.typeArgument);
+ visitTypes(type.typeArguments, covariantArgument(state));
}
@override
@@ -2888,13 +2953,10 @@
}
// Visit all nested types as type arguments; these types are not runtime
// instances but runtime type representations.
- state = state == TypeVisitorState.typeLiteral
- ? state
- : TypeVisitorState.typeArgument;
- visitType(type.returnType, state);
- visitTypes(type.parameterTypes, state);
- visitTypes(type.optionalParameterTypes, state);
- visitTypes(type.namedParameterTypes, state);
+ visitType(type.returnType, covariantArgument(state));
+ visitTypes(type.parameterTypes, contravariantArgument(state));
+ visitTypes(type.optionalParameterTypes, contravariantArgument(state));
+ visitTypes(type.namedParameterTypes, contravariantArgument(state));
_visitedFunctionTypeVariables.removeAll(type.typeVariables);
}
diff --git a/tests/compiler/dart2js/rti/emission/function_typed_arguments.dart b/tests/compiler/dart2js/rti/emission/function_typed_arguments.dart
index 08b5e64..a434694 100644
--- a/tests/compiler/dart2js/rti/emission/function_typed_arguments.dart
+++ b/tests/compiler/dart2js/rti/emission/function_typed_arguments.dart
@@ -16,10 +16,10 @@
test6();
}
-/*class: B1:checks=[],typeArgument*/
+/*class: B1:checkedTypeArgument,checks=[],typeArgument*/
class B1<T> {}
-/*class: C1:checkedTypeArgument,checks=[],typeArgument*/
+/*class: C1:checkedTypeArgument,checks=[$asB1],typeArgument*/
class C1 extends B1<int> {}
@pragma('dart2js:noInline')
@@ -51,7 +51,7 @@
/*class: B3:checkedTypeArgument,checks=[],typeArgument*/
class B3<T> {}
-/*class: C3:checks=[$asB3],typeArgument*/
+/*class: C3:checkedTypeArgument,checks=[$asB3],typeArgument*/
class C3 extends B3<int> {}
@pragma('dart2js:noInline')
@@ -80,10 +80,10 @@
@pragma('dart4js:noInline')
_test4(f) => f is A<B4<int> Function()>;
-/*class: B5:checks=[],typeArgument*/
+/*class: B5:checkedTypeArgument,checks=[],typeArgument*/
class B5<T> {}
-/*class: C5:checkedTypeArgument,checks=[],typeArgument*/
+/*class: C5:checkedTypeArgument,checks=[$asB5],typeArgument*/
class C5 extends B5<int> {}
@pragma('dart2js:noInline')
diff --git a/tests/compiler/dart2js/rti/emission/tear_off_types.dart b/tests/compiler/dart2js/rti/emission/tear_off_types.dart
index d9adf72..86d41eb 100644
--- a/tests/compiler/dart2js/rti/emission/tear_off_types.dart
+++ b/tests/compiler/dart2js/rti/emission/tear_off_types.dart
@@ -17,7 +17,7 @@
/*class: A1:checkedTypeArgument,checks=[],typeArgument*/
class A1<T> {}
-/*class: B1:checkedTypeArgument,checks=[$asA1],typeArgument*/
+/*class: B1:checks=[$asA1],typeArgument*/
class B1 extends A1<int> {}
@pragma('dart2js:noInline')
@@ -78,10 +78,10 @@
@pragma('dart3js:noInline')
_test3(f) => f is void Function(B3);
-/*class: A4:checkedTypeArgument,checks=[],typeArgument*/
+/*class: A4:checks=[],typeArgument*/
class A4<T> {}
-/*class: B4:checkedTypeArgument,checks=[$asA4],typeArgument*/
+/*class: B4:checkedTypeArgument,checks=[],typeArgument*/
class B4 extends A4<int> {}
@pragma('dart4js:noInline')
@@ -101,8 +101,7 @@
/*class: A5:checkedTypeArgument,checks=[],typeArgument*/
class A5<T> {}
-/*strong.class: B5:checkedTypeArgument,checks=[$asA5],typeArgument*/
-/*omit.class: B5:checkedTypeArgument,checks=[$asA5],typeArgument*/
+/*class: B5:checks=[$asA5],typeArgument*/
class B5 extends A5<int> {}
@pragma('dart2js:noInline')
@@ -119,12 +118,10 @@
@pragma('dart2js:noInline')
bool _test5(f) => f is void Function(void Function(A5<int>));
-/*strong.class: A6:checkedTypeArgument,checks=[],typeArgument*/
-/*omit.class: A6:checkedTypeArgument,checks=[],typeArgument*/
+/*class: A6:checkedTypeArgument,checks=[],typeArgument*/
class A6<T> {}
-/*strong.class: B6:checkedTypeArgument,checks=[$asA6],typeArgument*/
-/*omit.class: B6:checkedTypeArgument,checks=[$asA6],typeArgument*/
+/*class: B6:checkedTypeArgument,checks=[$asA6],typeArgument*/
class B6 extends A6<int> {}
@pragma('dart6js:noInline')
@@ -141,12 +138,10 @@
@pragma('dart6js:noInline')
_test6(f) => f is void Function(B6) Function();
-/*strong.class: A7:checkedTypeArgument,checks=[],typeArgument*/
-/*omit.class: A7:checkedTypeArgument,checks=[],typeArgument*/
+/*class: A7:checks=[],typeArgument*/
class A7<T> {}
-/*strong.class: B7:checkedTypeArgument,checks=[$asA7],typeArgument*/
-/*omit.class: B7:checkedTypeArgument,checks=[$asA7],typeArgument*/
+/*class: B7:checkedTypeArgument,checks=[],typeArgument*/
class B7 extends A7<int> {}
@pragma('dart7js:noInline')