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')