[dart2js] Ensure live/checked type visitors consider instantiations of
type variables.

Change-Id: I2fb404e5d544382fd4387268fd8c5765475abe65
Fixes: #48277
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241148
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Mayank Patke <fishythefish@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 8f63902..8b351dc 100644
--- a/pkg/compiler/lib/src/js_backend/runtime_types.dart
+++ b/pkg/compiler/lib/src/js_backend/runtime_types.dart
@@ -546,6 +546,20 @@
     Set<ClassEntity> typeLiterals = {};
     Set<ClassEntity> typeArguments = {};
 
+    Iterable<DartType> instantiateTypeVariable(TypeVariableEntity variable) {
+      Entity declaration = variable.typeDeclaration;
+      int index = variable.index;
+      if (declaration is ClassEntity) {
+        return typeVariableTests
+            .classInstantiationsOf(declaration)
+            .map((InterfaceType interface) => interface.typeArguments[index]);
+      } else {
+        return typeVariableTests.instantiationsOf(declaration).map(
+            (GenericInstantiation instantiation) =>
+                instantiation.typeArguments[index]);
+      }
+    }
+
     // 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.
@@ -559,27 +573,28 @@
     //    new A<B Function(C)>();
     //
     // makes A and B live but C tested.
-    TypeVisitor liveTypeVisitor =
-        TypeVisitor(onClass: (ClassEntity cls, {TypeVisitorState state}) {
-      ClassUse classUse = classUseMap.putIfAbsent(cls, () => ClassUse());
-      switch (state) {
-        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);
-          break;
-        case TypeVisitorState.direct:
-          break;
-      }
-    });
+    TypeVisitor liveTypeVisitor = TypeVisitor(
+        onClass: (ClassEntity cls, {TypeVisitorState state}) {
+          ClassUse classUse = classUseMap.putIfAbsent(cls, () => ClassUse());
+          switch (state) {
+            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);
+              break;
+            case TypeVisitorState.direct:
+              break;
+          }
+        },
+        instantiateTypeVariable: instantiateTypeVariable);
 
     // The [testedTypeVisitor] is used to register class use in type tests like
     // `o is T` and `o as T` (both implicit and explicit).
@@ -593,26 +608,27 @@
     //    o is A<B Function(C)>;
     //
     // makes A and B tested but C live.
-    TypeVisitor testedTypeVisitor =
-        TypeVisitor(onClass: (ClassEntity cls, {TypeVisitorState state}) {
-      ClassUse classUse = classUseMap.putIfAbsent(cls, () => ClassUse());
-      switch (state) {
-        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:
-          classUse.checkedInstance = true;
-          break;
-      }
-    });
+    TypeVisitor testedTypeVisitor = TypeVisitor(
+        onClass: (ClassEntity cls, {TypeVisitorState state}) {
+          ClassUse classUse = classUseMap.putIfAbsent(cls, () => ClassUse());
+          switch (state) {
+            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:
+              classUse.checkedInstance = true;
+              break;
+          }
+        },
+        instantiateTypeVariable: instantiateTypeVariable);
 
     codegenWorld.instantiatedClasses.forEach((ClassEntity cls) {
       ClassUse classUse = classUseMap.putIfAbsent(cls, () => ClassUse());
@@ -875,15 +891,14 @@
 }
 
 class TypeVisitor extends DartTypeVisitor<void, TypeVisitorState> {
+  final Set<TypeVariableType> _visitedTypeVariables = {};
   final Set<FunctionTypeVariable> _visitedFunctionTypeVariables = {};
 
   final void Function(ClassEntity entity, {TypeVisitorState state}) onClass;
-  final void Function(TypeVariableEntity entity, {TypeVisitorState state})
-      onTypeVariable;
-  final void Function(FunctionType type, {TypeVisitorState state})
-      onFunctionType;
+  final Iterable<DartType> Function(TypeVariableEntity entity)
+      instantiateTypeVariable;
 
-  TypeVisitor({this.onClass, this.onTypeVariable, this.onFunctionType});
+  TypeVisitor({this.onClass, this.instantiateTypeVariable});
 
   void visitType(DartType type, TypeVisitorState state) =>
       type.accept(this, state);
@@ -938,8 +953,10 @@
 
   @override
   void visitTypeVariableType(TypeVariableType type, TypeVisitorState state) {
-    if (onTypeVariable != null) {
-      onTypeVariable(type.element, state: state);
+    if (_visitedTypeVariables.add(type) && instantiateTypeVariable != null) {
+      for (DartType instantiation in instantiateTypeVariable(type.element)) {
+        visitType(instantiation, state);
+      }
     }
   }
 
@@ -952,9 +969,6 @@
 
   @override
   void visitFunctionType(FunctionType type, TypeVisitorState state) {
-    if (onFunctionType != null) {
-      onFunctionType(type, state: state);
-    }
     // Visit all nested types as type arguments; these types are not runtime
     // instances but runtime type representations.
     visitType(type.returnType, covariantArgument(state));
diff --git a/pkg/compiler/lib/src/js_backend/runtime_types_resolution.dart b/pkg/compiler/lib/src/js_backend/runtime_types_resolution.dart
index 365a3f8..ce85047 100644
--- a/pkg/compiler/lib/src/js_backend/runtime_types_resolution.dart
+++ b/pkg/compiler/lib/src/js_backend/runtime_types_resolution.dart
@@ -258,6 +258,12 @@
     _instantiationMap.forEach(f);
   }
 
+  Set<GenericInstantiation> instantiationsOf(Entity target) =>
+      _instantiationMap[target] ?? const {};
+
+  Set<InterfaceType> classInstantiationsOf(ClassEntity cls) =>
+      _classInstantiationMap[cls] ?? const {};
+
   ClassNode _getClassNode(ClassEntity cls) {
     return _classes.putIfAbsent(cls, () => ClassNode(cls));
   }
@@ -554,12 +560,12 @@
     TypeVariableEntity entity = variable.element;
     Entity declaration = entity.typeDeclaration;
     if (declaration is ClassEntity) {
-      _classInstantiationMap[declaration]?.forEach((InterfaceType type) {
+      classInstantiationsOf(declaration).forEach((InterfaceType type) {
         _addImplicitCheck(type.typeArguments[entity.index]);
       });
     } else {
-      _instantiationMap[declaration]
-          ?.forEach((GenericInstantiation instantiation) {
+      instantiationsOf(declaration)
+          .forEach((GenericInstantiation instantiation) {
         _addImplicitCheck(instantiation.typeArguments[entity.index]);
       });
       _world.forEachStaticTypeArgument(
@@ -598,7 +604,7 @@
       // one of its type arguments in an is-check and add the arguments to the
       // set of is-checks.
       for (ClassEntity base in _classHierarchy.allSubtypesOf(cls)) {
-        _classInstantiationMap[base]?.forEach((InterfaceType subtype) {
+        classInstantiationsOf(base).forEach((InterfaceType subtype) {
           InterfaceType instance = _dartTypes.asInstanceOf(subtype, cls);
           assert(instance != null);
           _addImplicitChecks(instance.typeArguments);
diff --git a/pkg/compiler/test/rti/emission/future_or_generic2.dart b/pkg/compiler/test/rti/emission/future_or_generic2.dart
index 1bb791c..dae38e57 100644
--- a/pkg/compiler/test/rti/emission/future_or_generic2.dart
+++ b/pkg/compiler/test/rti/emission/future_or_generic2.dart
@@ -18,10 +18,10 @@
 /*class: B:checkedInstance,checkedTypeArgument,checks=[],instance,typeArgument*/
 class B<T> {}
 
-/*class: C:checkedInstance,typeArgument*/
+/*class: C:checkedInstance,checkedTypeArgument,typeArgument*/
 class C {}
 
-/*class: D:checkedInstance,typeArgument*/
+/*class: D:checkedInstance,checkedTypeArgument,typeArgument*/
 class D {}
 
 main() {