Reland "[ddc] Overhauling DDC's generic class representation."

This is a reland of commit e7658520bb4a88c95416ebe83abe59e329335bc6

Fixes in the reland + context:
Type parameters emitted in implicit type checks on covariant mixin forwarding stubs may reference type arguments in anonymous classes. We reduce this to their mixin's implementing subclass to avoid generating RTI rules for anonymous classes.

Previous implementations would 'translate' type parameters to that of their mixed in type, but that strategy fails if the implementing subtype  shuffles the order of type arguments relative to its mixed in type (demonstrated in the test - though not actually relevant in the Flutter break).

Original change's description:
> [ddc] Overhauling DDC's generic class representation.
>
> Prior to this change, DDC represented generic classes as closures over type parameters (with type arguments provided at runtime), which tightly coupled generic class definitions with their types and concrete instantiation.
>
> This rewrite decouples this representation, letting us 1) bind type information late and 2) separate generic class definitions from their instantiation.
>
> Notable changes:
> - Generic classes are now declared at top level (rather than within in closures).
> - RTIs are now passed to generic class constructors at runtime (except for JS Interop classes). Only the instantiated class's RTI is required (and it's retained up the type hierarchy).
> - Type signature resolvers are now lambdas that accept a type environment RTI at runtime. While signatures are still attached early, their instances' RTIs are now needed at runtime.
> - Generic classes, constructors, and factories are now evaluated in a 'Class' type environment.
> - An `RtiTypeEnvironment` is introduced to represent lookups on an RTI type environment bound to a parameter. These are used when evaluating type signatures and at constructor/factory bodies.
> - Type recipes now emit Class type parameters with names - but continue to emit method type parameters with de Bruijn indices. This is because indices aren't stable across subtypes.
> - Certain debugger functions now require instances (e.g.,`getClassMetadata`).
> - Adds a special flag for non-external JS interop factory constructors to emit 'true' types (versus 'any').
>
> Change-Id: I7cbeaaf666dd4f9bd5e3ef22a1163a659fc0ee48
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365863
> Reviewed-by: Srujan Gaddam <srujzs@google.com>
> Reviewed-by: Kallen Tu <kallentu@google.com>
> Reviewed-by: Nicholas Shahan <nshahan@google.com>
> Reviewed-by: Nate Biggs <natebiggs@google.com>
> Commit-Queue: Mark Zhou <markzipan@google.com>

Change-Id: I9b6f69b7150631f28442675c4230e093e3b821d9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/379511
Reviewed-by: Kallen Tu <kallentu@google.com>
Commit-Queue: Mark Zhou <markzipan@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 11e9546..124a5b9 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -203,8 +203,12 @@
 
   final _superHelpers = <String, js_ast.Method>{};
 
-  /// Cache for the results of calling [_typeParametersInHierarchy].
-  final _typeParametersInHierarchyCache = <Class, bool>{};
+  /// Cache for the results of calling [_requiresRtiForInstantiation].
+  final _requiresRtiForInstantiationCache = <Class, bool>{};
+
+  /// Reserved parameter used to reference RTI objects passed to generic
+  /// constructors/factories and generic method signatures.
+  final _rtiParam = js_ast.TemporaryId('_ti');
 
   // Compilation of Kernel's [BreakStatement].
   //
@@ -705,6 +709,22 @@
       _moduleItems.add(addTypeParameterVariancesStatement);
     }
 
+    // Certain RTIs must be emitted during RTI normalization. We cache these
+    // eagerly with 'findType' (without normalization) to avoid infinite loops.
+    // See normalization functions in: sdk/lib/_internal/js_shared/lib/rti.dart
+    var prerequisiteRtiTypes = [
+      _coreTypes.objectLegacyRawType,
+      _coreTypes.objectNullableRawType,
+      NeverType.legacy()
+    ];
+    prerequisiteRtiTypes.forEach((type) {
+      var recipe = _typeRecipeGenerator
+          .recipeInEnvironment(type, EmptyTypeEnvironment())
+          .recipe;
+      _moduleItems.add(js.call('#.findType("$recipe")',
+          [_emitLibraryName(_rtiLibrary)]).toStatement());
+    });
+
     // Visit directives (for exports)
     libraries.forEach(_emitExports);
     _ticker?.logMs('Emitted exports');
@@ -931,7 +951,7 @@
     // object attached to an instance. Instead we construct a type environment
     // manually when needed. Later we use the rti attached to an instance for
     // a simpler representation within instance members of the class.
-    _currentTypeEnvironment = _currentTypeEnvironment.extend(c.typeParameters);
+    _currentTypeEnvironment = ClassTypeEnvironment(c.typeParameters);
 
     // Mixins are unrolled in _defineClass.
     if (!c.isAnonymousMixin) {
@@ -979,18 +999,7 @@
       js_ast.TemporaryId(js_ast.toJSIdentifier(name));
 
   js_ast.Statement _emitClassDeclaration(Class c) {
-    // Generic classes will be defined inside a function that closes over the
-    // type parameter. So we can use their local variable name directly.
-    //
-    // TODO(jmesserly): the special case for JSArray is to support its special
-    // type-tagging factory constructors. Those will go away once we fix:
-    // https://github.com/dart-lang/sdk/issues/31003
-    var className = c.typeParameters.isNotEmpty
-        ? (c == _jsArrayClass
-            ? _emitIdentifier(c.name)
-            : _emitTemporaryId(getLocalClassName(c)))
-        : _emitTopLevelName(c);
-
+    var className = _emitTopLevelNameNoExternalInterop(c);
     var savedClassProperties = _classProperties;
     _classProperties =
         ClassPropertyModel.build(_types, _extensionTypes, _virtualFields, c);
@@ -1017,13 +1026,6 @@
 
     var jsCtors = _defineConstructors(c, className);
 
-    // TODO(nshahan) Use ClassTypeEnvironment when the representation of generic
-    // classes is no longer a closure that defines the class and captures the
-    // type arguments.
-    // Emitting class members in a class type environment results in a more
-    // succinct type representation when referencing class type arguments from
-    // instance members but the type rules must include mappings of all type
-    // arguments throughout the hierarchy.
     var jsMethods = _emitClassMethods(c);
 
     _emitSuperHelperSymbols(body);
@@ -1087,18 +1089,17 @@
       _defineExtensionMembers(className, body);
     }
 
-    var classDef = js_ast.Statement.from(body);
     var typeFormals = c.typeParameters;
     var evaluatedDeferredSupertypes =
         deferredSupertypes.map<js_ast.Statement>((f) => f()).toList();
     if (typeFormals.isNotEmpty) {
-      classDef = _defineClassTypeArguments(
-          c, typeFormals, classDef, className, evaluatedDeferredSupertypes);
+      var genericClassStmts = _defineGenericClass(typeFormals,
+          js_ast.Statement.from(body), evaluatedDeferredSupertypes);
+      body = [...genericClassStmts];
     } else {
       _afterClassDefItems.addAll(evaluatedDeferredSupertypes);
     }
 
-    body = [classDef];
     _emitStaticFieldsAndAccessors(c, body);
     if (c == _coreTypes.objectClass) {
       // Avoid polluting the native JavaScript Object prototype with the members
@@ -1121,11 +1122,7 @@
   /// or static members, returns null.
   js_ast.Statement? _emitJSInteropClassNonExternalMembers(Class c) {
     if (!hasJSInteropAnnotation(c)) return null;
-    // Generic JS interop classes are emitted like Dart generic classes, where
-    // the type arguments need to be instantiated.
-    var className = c.typeParameters.isNotEmpty
-        ? _emitTemporaryId(getLocalClassName(c))
-        : _emitTopLevelNameNoExternalInterop(c);
+    var className = _emitTopLevelNameNoExternalInterop(c);
 
     var nonExternalMethods = <js_ast.Method>[];
     for (var procedure in c.procedures) {
@@ -1154,59 +1151,32 @@
         _emitClassStatement(c, className, null, nonExternalMethods)
             .toStatement()
       ];
-      var classDef = js_ast.Statement.from(body);
       var typeFormals = c.typeParameters;
       if (typeFormals.isNotEmpty) {
-        classDef =
-            _defineClassTypeArguments(c, typeFormals, classDef, className, []);
+        var genericClassStmts =
+            _defineGenericClass(typeFormals, js_ast.Statement.from(body), []);
+        body = [...genericClassStmts, ...fieldInitialization];
+      } else {
+        body = [...body, ...fieldInitialization];
       }
-      body = [classDef, ...fieldInitialization];
       return js_ast.Statement.from(body);
     }
     return null;
   }
 
-  /// Wraps a possibly generic class in its type arguments.
-  js_ast.Statement _defineClassTypeArguments(
-      NamedNode c,
-      List<TypeParameter> formals,
-      js_ast.Statement body,
-      js_ast.Expression className,
-      List<js_ast.Statement> deferredBaseClass) {
+  /// Emits a generic class with additional initialization logic.
+  List<js_ast.Statement> _defineGenericClass(List<TypeParameter> formals,
+      js_ast.Statement body, List<js_ast.Statement> deferredBaseClass) {
     assert(formals.isNotEmpty);
-    var jsFormals = _emitTypeFormals(formals);
-    var typeConstructor = js.call('(#) => { #; #; #; return #; }', [
-      jsFormals,
-      _typeTable.dischargeFreeTypes(formals),
-      _genericClassTable.dischargeFreeTypes(formals),
+    return [
+      ..._typeTable.dischargeFreeTypes(formals),
       body,
-      className
-    ]);
-
-    var genericArgs = [
-      typeConstructor,
-      if (deferredBaseClass.isNotEmpty)
-        js.call('(#) => { #; }', [jsFormals, deferredBaseClass]),
+      ...deferredBaseClass,
     ];
-    var genericCall = _runtimeCall('generic(#)', [genericArgs]);
-    var genericName = _emitTopLevelNameNoExternalInterop(c, suffix: '\$');
-    return js.statement('{ # = #; # = #(); }', [
-      genericName,
-      genericCall,
-      _emitTopLevelNameNoExternalInterop(c),
-      genericName
-    ]);
   }
 
   js_ast.Statement _emitClassStatement(Class c, js_ast.Expression className,
       js_ast.Expression? heritage, List<js_ast.Method> methods) {
-    if (c.typeParameters.isNotEmpty) {
-      var classIdentifier = className as js_ast.Identifier;
-      if (_options.emitDebugSymbols) classIdentifiers[c] = classIdentifier;
-      return js_ast.ClassExpression(classIdentifier, heritage, methods)
-          .toStatement();
-    }
-
     var classIdentifier = _emitTemporaryId(getLocalClassName(c));
     if (_options.emitDebugSymbols) classIdentifiers[c] = classIdentifier;
     var classExpr = js_ast.ClassExpression(classIdentifier, heritage, methods);
@@ -1323,23 +1293,11 @@
       return base;
     }
 
-    /// Returns the "actual" superclass of [c].
-    ///
-    /// Walks up the superclass chain looking for the first actual class
-    /// skipping any synthetic classes inserted by the CFE.
-    Class superClassAsWritten(Class c) {
-      var superclass = c.superclass!;
-      while (superclass.isAnonymousMixin) {
-        superclass = superclass.superclass!;
-      }
-      return superclass;
-    }
-
     // Find the real (user declared) superclass and the list of mixins.
     // We'll use this to unroll the intermediate classes.
     //
     // TODO(jmesserly): consider using Kernel's mixin unrolling.
-    var superclass = superClassAsWritten(c);
+    var superclass = _superClassAsWritten(c);
     var supertype = identical(c.superclass, superclass)
         ? c.supertype!.asInterfaceType
         : _hierarchy.getClassAsInstanceOf(c, superclass)!.asInterfaceType;
@@ -1354,32 +1312,48 @@
 
     var hasUnnamedSuper = _hasUnnamedInheritedConstructor(superclass);
 
-    void emitMixinConstructors(
-        js_ast.Expression className, InterfaceType mixin) {
-      js_ast.Statement? mixinCtor;
-      if (_hasUnnamedConstructor(mixin.classNode)) {
-        mixinCtor = js.statement('#.#.call(this);', [
-          emitClassRef(mixin),
-          _usesMixinNew(mixin.classNode)
-              ? _runtimeCall('mixinNew')
-              : _constructorName('')
-        ]);
-      }
-
-      for (var ctor in superclass.constructors) {
+    void emitMixinConstructors(js_ast.Expression className,
+        Class mixinSuperclass, Class mixinClass, InterfaceType mixin) {
+      for (var ctor in mixinSuperclass.constructors) {
         var savedUri = _currentUri;
         _currentUri = ctor.enclosingClass.fileUri;
-        var jsParams = _emitParameters(ctor.function, isForwarding: true);
-        _currentUri = savedUri;
+
+        var sharedParams = _emitParameters(ctor.function, isForwarding: true);
+        var mixinConstructorParams = [
+          if (_requiresRtiForInstantiation(mixinSuperclass)) _rtiParam,
+          ...sharedParams
+        ];
+        var superConstructorArgs = [
+          if (_requiresRtiForInstantiation(ctor.enclosingClass))
+            js_ast.LiteralNull(),
+          ...sharedParams
+        ];
+
+        js_ast.Statement? mixinCtor;
+        if (_hasUnnamedConstructor(mixin.classNode)) {
+          var mixinRti = _requiresRtiForInstantiation(mixin.classNode)
+              ? js_ast.LiteralNull()
+              : null;
+          mixinCtor = js.statement('#.#.call(this, #);', [
+            emitClassRef(mixin),
+            _usesMixinNew(mixin.classNode)
+                ? _runtimeCall('mixinNew')
+                : _constructorName(''),
+            [if (mixinRti != null) mixinRti],
+          ]);
+        }
+
         var name = ctor.name.text;
         var ctorBody = [
           if (mixinCtor != null) mixinCtor,
           if (name != '' || hasUnnamedSuper)
-            _emitSuperConstructorCall(className, name, jsParams),
+            _emitSuperConstructorCall(
+                ctor, className, name, superConstructorArgs),
         ];
         // TODO(nshahan) Record the name for this constructor in memberNames.
         body.add(_addConstructorToClass(c, className, _constructorName(name),
-            js_ast.Fun(jsParams, js_ast.Block(ctorBody))));
+            js_ast.Fun(mixinConstructorParams, js_ast.Block(ctorBody))));
+        _currentUri = savedUri;
       }
     }
 
@@ -1418,8 +1392,10 @@
       // parameters from the class being mixed in and their use can appear in
       // the forwarding stubs.
       var savedTypeEnvironment = _currentTypeEnvironment;
-      _currentTypeEnvironment =
-          _currentTypeEnvironment.extend(m.typeParameters);
+      if (m.typeParameters.isNotEmpty) {
+        assert(_currentTypeEnvironment is ClassTypeEnvironment);
+        _currentTypeEnvironment = ClassTypeEnvironment(m.typeParameters);
+      }
       var forwardingMethodStubs = <js_ast.Method>[];
       for (var s in forwardingMembers.values) {
         // Members are marked as "forwarding stubs" when they require a type
@@ -1449,7 +1425,7 @@
             _emitTemporaryId(mixinName), baseClass, forwardingMethodStubs)
       ]));
 
-      emitMixinConstructors(mixinId, mixinType);
+      emitMixinConstructors(mixinId, superclass, mixinClass, mixinType);
       hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(mixinClass);
 
       if (shouldDefer(mixinType)) {
@@ -1574,6 +1550,9 @@
   /// Emit the signature on the class recording the runtime type information
   void _emitClassSignature(
       Class c, js_ast.Expression className, List<js_ast.Statement> body) {
+    var savedTypeEnvironment = _currentTypeEnvironment;
+    _currentTypeEnvironment =
+        RtiTypeEnvironment(_currentTypeEnvironment.classTypeParameters);
     var savedClass = _classEmittingSignatures;
     _classEmittingSignatures = c;
 
@@ -1608,6 +1587,25 @@
       body.add(setSignature);
     }
 
+    js_ast.Expression emitClassFieldSignature(Field field, Class fromClass) {
+      var fieldType =
+          _typeFromClass(field.type, field.enclosingClass!, fromClass)
+              .extensionTypeErasure;
+      var uri = fieldType is InterfaceType
+          ? _cacheUri(
+              _jsLibraryDebuggerName(fieldType.classNode.enclosingLibrary))
+          : null;
+      var isConst = js.boolean(field.isConst);
+      var isFinal = js.boolean(field.isFinal);
+      var type = _emitType(fieldType);
+      var typeResolver = js_ast.ArrowFun([_rtiParam], type);
+      return uri == null
+          ? js('{type: #, isConst: #, isFinal: #}',
+              [typeResolver, isConst, isFinal])
+          : js('{type: #, isConst: #, isFinal: #, libraryUri: #}',
+              [typeResolver, isConst, isFinal, uri]);
+    }
+
     var extMethods = _classProperties!.extensionMethods;
     var extAccessors = _classProperties!.extensionAccessors;
     var staticMethods = <js_ast.Property>[];
@@ -1689,28 +1687,32 @@
               for (var parameter in reifiedType.typeParameters)
                 _emitType(parameter.defaultType)
             ]);
-            instanceMethodsDefaultTypeArgs
-                .add(js_ast.Property(memberName, defaultTypeArgs));
+            var typeResolver = js_ast.ArrowFun([_rtiParam], defaultTypeArgs);
+            var property = js_ast.Property(memberName, typeResolver);
+            instanceMethodsDefaultTypeArgs.add(property);
             // As seen below, sometimes the member signatures are added again
             // using the extension symbol as the name. That logic is duplicated
             // here to ensure there are always default type arguments accessible
             // via the same name as the signature.
             // TODO(52867): Cleanup default type argument duplication.
             if (extMethods.contains(name) || extAccessors.contains(name)) {
-              instanceMethodsDefaultTypeArgs.add(js_ast.Property(
-                  _declareMemberName(member, useExtension: true),
-                  defaultTypeArgs));
+              var property = js_ast.Property(
+                  _declareMemberName(member, useExtension: true), typeResolver);
+              instanceMethodsDefaultTypeArgs.add(property);
             }
           }
         }
-        var property = js_ast.Property(memberName, type);
+        var typeResolver = js_ast.ArrowFun([_rtiParam], type);
+        var property = js_ast.Property(memberName, typeResolver);
         var signatures = getSignatureList(member);
         signatures.add(property);
         if (!member.isStatic &&
             (extMethods.contains(name) || extAccessors.contains(name))) {
           // TODO(52867): Cleanup signature duplication.
-          signatures.add(js_ast.Property(
-              _declareMemberName(member, useExtension: true), type));
+          var typeResolver = js_ast.ArrowFun([_rtiParam], type);
+          var property = js_ast.Property(
+              _declareMemberName(member, useExtension: true), typeResolver);
+          signatures.add(property);
         }
       }
     }
@@ -1736,34 +1738,18 @@
       // signatures are only used by the debugger and are not needed for runtime
       // correctness.
       var memberName = _declareMemberName(field);
-      var fieldSig = _emitClassFieldSignature(field, c);
+      var fieldSig = emitClassFieldSignature(field, c);
+      var property = js_ast.Property(memberName, fieldSig);
       // TODO(40273) Skip static fields when the debugger consumes signature
       // information from symbol files.
-      (field.isStatic ? staticFields : instanceFields)
-          .add(js_ast.Property(memberName, fieldSig));
+      (field.isStatic ? staticFields : instanceFields).add(property);
     }
     emitSignature('Field', instanceFields);
     // TODO(40273) Skip for all statics when the debugger consumes signature
     // information from symbol files.
     emitSignature('StaticField', staticFields);
     _classEmittingSignatures = savedClass;
-  }
-
-  js_ast.Expression _emitClassFieldSignature(Field field, Class fromClass) {
-    var fieldType = _typeFromClass(field.type, field.enclosingClass!, fromClass)
-        .extensionTypeErasure;
-    var uri = fieldType is InterfaceType
-        ? _cacheUri(
-            _jsLibraryDebuggerName(fieldType.classNode.enclosingLibrary))
-        : null;
-    var isConst = js.boolean(field.isConst);
-    var isFinal = js.boolean(field.isFinal);
-
-    return uri == null
-        ? js('{type: #, isConst: #, isFinal: #}',
-            [_emitType(fieldType), isConst, isFinal])
-        : js('{type: #, isConst: #, isFinal: #, libraryUri: #}',
-            [_emitType(fieldType), isConst, isFinal, uri]);
+    _currentTypeEnvironment = savedTypeEnvironment;
   }
 
   DartType _memberRuntimeType(Member member, Class fromClass) {
@@ -1828,7 +1814,13 @@
     var savedUri = _currentUri;
     _currentUri = node.fileUri;
     _staticTypeContext.enterMember(node);
-    var params = _emitParameters(node.function);
+    var savedTypeEnvironment = _currentTypeEnvironment;
+    _currentTypeEnvironment =
+        ClassTypeEnvironment(node.enclosingClass.typeParameters);
+
+    var params = <js_ast.Parameter>[];
+    // Generic class constructors accept their RTI as their first argument.
+    params.addAll(_emitParameters(node.function));
     var body = _withCurrentFunction(
         node.function,
         () => _superDisallowed(
@@ -1839,11 +1831,21 @@
     _staticTypeContext.leaveMember(node);
     end ??= _nodeEnd(node.enclosingClass.fileEndOffset);
 
-    return js_ast.Fun(params, js_ast.Block(body))..sourceInformation = end;
+    var constructor = js_ast.Fun([
+      if (_requiresRtiForInstantiation(node.enclosingClass)) _rtiParam,
+      ...params
+    ], js_ast.Block(body))
+      ..sourceInformation = end;
+
+    _currentTypeEnvironment = savedTypeEnvironment;
+    return constructor;
   }
 
   List<js_ast.Statement> _emitConstructorBody(
-      Constructor node, List<Field> fields, js_ast.Expression className) {
+    Constructor node,
+    List<Field> fields,
+    js_ast.Expression className,
+  ) {
     var cls = node.enclosingClass;
 
     // Generate optional/named argument value assignment. These can not have
@@ -1854,6 +1856,20 @@
     var fn = node.function;
     var body = _emitArgumentInitializers(fn, node.name.text);
 
+    // Class instances with type arguments are bound to their RTI on creation.
+    // This must be bound early, as instantiated fields may reference this RTI.
+    if (_requiresRtiForInstantiation(cls)) {
+      // Only set the rti if there isn't one already. This avoids superclasses
+      // overwriting the value already set by a subclass.
+      var rtiProperty = _propertyName(js_ast.FixedNames.rtiName);
+      body.add(js.statement('this.# = this.# || # || #', [
+        rtiProperty,
+        rtiProperty,
+        _rtiParam,
+        _runtimeCall('getReifiedType(this)')
+      ]));
+    }
+
     // Redirecting constructors are not allowed to have conventional
     // initializers but can have variable declarations in the form of
     // initializers to support named arguments appearing anywhere in the
@@ -1869,18 +1885,6 @@
     // multiple constructors, but it needs to be balanced against readability.
     body.add(_initializeFields(fields, node));
 
-    // Instances of classes with type arguments need an rti object attached to
-    // them since the type arguments could be instantiated differently for
-    // each instance.
-    if (_typeParametersInHierarchy(cls)) {
-      var type = cls.getThisType(_coreTypes, Nullability.nonNullable);
-      // Only set the rti if there isn't one already. This avoids superclasses
-      // from overwriting the value already set by subclass.
-      var rtiProperty = _propertyName(js_ast.FixedNames.rtiName);
-      body.add(js.statement(
-          'this.# = this.# || #', [rtiProperty, rtiProperty, _emitType(type)]));
-    }
-
     // If no superinitializer is provided, an implicit superinitializer of the
     // form `super()` is added at the end of the initializer list, unless the
     // enclosing class is class Object.
@@ -1897,17 +1901,46 @@
     return body;
   }
 
-  /// Returns `true` if [cls] or any of its transitive super classes has
-  /// generic type parameters.
-  bool _typeParametersInHierarchy(Class? cls) {
+  /// Returns the "actual" superclass of [c].
+  ///
+  /// Walks up the superclass chain looking for the first actual class
+  /// skipping any synthetic classes inserted by the CFE.
+  Class _superClassAsWritten(Class c) {
+    var superclass = c.superclass!;
+    while (superclass.isAnonymousMixin) {
+      superclass = superclass.superclass!;
+    }
+    return superclass;
+  }
+
+  /// Returns `true` if [cls] requires/accepts an RTI during instantiation.
+  ///
+  /// We check [cls]'s transitive super classes for generic type parameters,
+  /// but we do not consider anonymous mixins, implemented types or mixin on
+  /// clauses - as their constructors are never invoked via super calls.
+  /// Synthetic mixins are also skipped (despite sometimes having type
+  /// parameters) since they can't be referenced during instantiation.
+  ///
+  /// Context: type arguments must be provided to a generic class during its
+  /// instantiation. To avoid extraneous RTI evals, we pass the entire class's
+  /// RTI instead of each type parameter's RTI individually. RTIs are attached
+  /// to the instance on the hidden '$ti' field (see: FixedNames.rtiName). We
+  /// attach RTIs eagerly (i.e., closer to the 'leaf' than the 'root') for
+  /// simplicity. Setters on 'this' propagate up super calls since Dart super
+  /// calls are synthetic. Ordinary JS super calls would require us to
+  /// propagate the RTI all the way to the 'uppermost' generic class.
+  bool _requiresRtiForInstantiation(Class? cls) {
     if (cls == null) return false;
-    var cachedResult = _typeParametersInHierarchyCache[cls];
+    var cachedResult = _requiresRtiForInstantiationCache[cls];
     if (cachedResult != null) return cachedResult;
+    // Skip synthetic mixins since their RTIs are never needed during
+    // instantiation.
+    if (cls.isAnonymousMixin) {
+      cls = _superClassAsWritten(cls);
+    }
     var hasTypeParameters = cls.typeParameters.isNotEmpty ||
-        (cls.isMixinApplication &&
-            _typeParametersInHierarchy(cls.mixedInClass)) ||
-        _typeParametersInHierarchy(cls.superclass);
-    _typeParametersInHierarchyCache[cls] = hasTypeParameters;
+        _requiresRtiForInstantiation(cls.superclass);
+    _requiresRtiForInstantiationCache[cls] = hasTypeParameters;
     return hasTypeParameters;
   }
 
@@ -1921,21 +1954,29 @@
 
   js_ast.Statement _emitRedirectingConstructor(
       List<Initializer> initializers, js_ast.Expression className) {
-    var jsInitializers = <js_ast.Statement>[
-      for (var init in initializers)
-        if (init is LocalInitializer)
-          // Temporary locals are created when named arguments don't appear at
-          // the end of the arguments list.
-          visitVariableDeclaration(init.variable)
-        else if (init is RedirectingInitializer)
-          // We can't dispatch to the constructor with `this.new` as that might
-          // hit a derived class constructor with the same name.
-          js.statement('#.#.call(this, #);', [
-            className,
-            _constructorName(init.target.name.text),
-            _emitArgumentList(init.arguments, types: false)
-          ])
-    ];
+    var jsInitializers = <js_ast.Statement>[];
+    for (var init in initializers) {
+      if (init is LocalInitializer) {
+        // Temporary locals are created when named arguments don't appear at
+        // the end of the arguments list.
+        jsInitializers.add(visitVariableDeclaration(init.variable));
+      } else if (init is RedirectingInitializer) {
+        var rtiParam = _requiresRtiForInstantiation(init.target.enclosingClass)
+            ? _rtiParam
+            : null;
+        // We can't dispatch to the constructor with `this.new` as that might
+        // hit a derived class constructor with the same name.
+        var initializer = js.statement('#.#.call(this, #);', [
+          className,
+          _constructorName(init.target.name.text),
+          [
+            if (rtiParam != null) rtiParam,
+            ..._emitArgumentList(init.arguments, types: false)
+          ]
+        ]);
+        jsInitializers.add(initializer);
+      }
+    }
     return js_ast.Block(jsInitializers);
   }
 
@@ -1950,7 +1991,19 @@
       args = [];
     } else {
       ctor = superInit.target;
-      args = _emitArgumentList(superInit.arguments, types: false);
+      var savedTypeEnvironment = _currentTypeEnvironment;
+      _currentTypeEnvironment = ClassTypeEnvironment(c.typeParameters);
+      // An RTI will already have been set at the constructor call site, so
+      // pass nothing if the superclass is expecting an RTI.
+      var rti = _requiresRtiForInstantiation(ctor.enclosingClass)
+          ? js_ast.LiteralNull()
+          : null;
+      args = [
+        if (rti != null) rti,
+        ..._emitArgumentList(superInit.arguments, types: true)
+      ];
+
+      _currentTypeEnvironment = savedTypeEnvironment;
     }
     // We can skip the super call if it's empty. Most commonly this happens for
     // things that extend Object, and don't have any field initializers or their
@@ -1958,10 +2011,10 @@
     if (ctor.name.text == '' && !_hasUnnamedSuperConstructor(c)) {
       return null;
     }
-    return _emitSuperConstructorCall(className, ctor.name.text, args);
+    return _emitSuperConstructorCall(ctor, className, ctor.name.text, args);
   }
 
-  js_ast.Statement _emitSuperConstructorCall(
+  js_ast.Statement _emitSuperConstructorCall(Constructor constructor,
       js_ast.Expression className, String name, List<js_ast.Expression> args) {
     return js.statement('#.#.call(this, #);', [
       _emitJSObjectGetPrototypeOf(className, fullyQualifiedName: true),
@@ -2381,7 +2434,6 @@
   /// Emits a Dart factory constructor to a JS static method.
   js_ast.Method? _emitFactoryConstructor(Procedure node) {
     if (node.isExternal || isUnsupportedFactoryConstructor(node)) return null;
-
     var function = node.function;
 
     /// Note: factory constructors can't use `sync*`/`async*`/`async` bodies
@@ -2395,8 +2447,10 @@
     /// [_emitFunction] instead.
     var name = node.name.text;
     var savedTypeEnvironment = _currentTypeEnvironment;
-    _currentTypeEnvironment =
-        _currentTypeEnvironment.extend(function.typeParameters);
+    _currentTypeEnvironment = RtiTypeEnvironment([
+      ...function.typeParameters,
+      ..._currentTypeEnvironment.classTypeParameters
+    ]);
     var jsBody = js_ast.Block(_withCurrentFunction(function, () {
       var block = _emitArgumentInitializers(function, name);
       block.add(_emitFunctionScopedBody(function));
@@ -2404,11 +2458,21 @@
     }));
     var jsName = _constructorName(name);
     memberNames[node] = jsName.valueWithoutQuotes;
-    var jsParams = _emitParameters(function);
-    _currentTypeEnvironment = savedTypeEnvironment;
 
-    return js_ast.Method(jsName, js_ast.Fun(jsParams, jsBody), isStatic: true)
-      ..sourceInformation = _nodeEnd(node.fileEndOffset);
+    // Generic class constructors accept their RTI as their first argument.
+    var method = js_ast.Method(
+      jsName,
+      js_ast.Fun(
+        [
+          if (_requiresRtiForInstantiation(node.enclosingClass)) _rtiParam,
+          ..._emitParameters(function)
+        ],
+        jsBody,
+      ),
+      isStatic: true,
+    )..sourceInformation = _nodeEnd(node.fileEndOffset);
+    _currentTypeEnvironment = savedTypeEnvironment;
+    return method;
   }
 
   /// Emits the expression necessary to access a constructor of [type];
@@ -2944,11 +3008,6 @@
         _emitTopLevelMemberName(n, suffix: suffix));
   }
 
-  js_ast.PropertyAccess _emitFutureOrNameNoInterop({String suffix = ''}) {
-    return js_ast.PropertyAccess(_emitLibraryName(_coreTypes.asyncLibrary),
-        _propertyName('FutureOr$suffix'));
-  }
-
   /// Emits the member name portion of a top-level member.
   ///
   /// NOTE: usually you should use [_emitTopLevelName] instead of this. This
@@ -3226,7 +3285,15 @@
 
   /// Returns an expression that evaluates to the rti object from the dart:_rti
   /// library that represents [type].
-  js_ast.Expression _emitType(DartType type) {
+  ///
+  /// [emitJSInteropGenericClassTypeParametersAsAny] indicates that we should
+  /// emit the statically declared type as a JS interop generic class's type
+  /// argument (rather than substituting Any). Any is required for correctness
+  /// in most cases except for uses in non-external JS interop factories.
+  /// Note: This only applies to the old style package:js interop and isn't
+  /// necessary for any forms of static JS interop.
+  js_ast.Expression _emitType(DartType type,
+      {bool emitJSInteropGenericClassTypeParametersAsAny = true}) {
     /// Returns an expression that evaluates a type [recipe] within the type
     /// [environment].
     ///
@@ -3254,64 +3321,68 @@
     /// [environment] resulting in an rti object.
     js_ast.Expression evalInEnvironment(
         DDCTypeEnvironment environment, String recipe) {
-      if (environment is EmptyTypeEnvironment) {
-        return js
-            .call('#.findType("$recipe")', [_emitLibraryName(_rtiLibrary)]);
-      } else if (environment is BindingTypeEnvironment) {
-        js_ast.Expression env;
-        if (environment.isSingleTypeParameter) {
-          // An environment with a single type parameter can be simplified to
-          // just that parameter.
-          env = _emitTypeParameter(environment.parameters.single);
-          // Skip a no-op evaluation and just return the parameter.
-          if (recipe == '0') return env;
-        } else {
-          var environmentTypes = environment.parameters;
-          // Create a dummy interface type to "hold" type arguments.
-          env = emitRtiEval(_emitTypeParameter(environmentTypes.first), '@<0>');
-          // Bind remaining type arguments.
-          for (var i = 1; i < environmentTypes.length; i++) {
-            env = emitRtiBind(env, environmentTypes[i]);
+      switch (environment) {
+        case EmptyTypeEnvironment():
+          return js
+              .call('#._Universe.eval(#._theUniverse(), "$recipe", true)', [
+            _emitLibraryName(_rtiLibrary),
+            _emitLibraryName(_rtiLibrary),
+          ]);
+        case BindingTypeEnvironment():
+          js_ast.Expression env;
+          if (environment.isSingleTypeParameter) {
+            // An environment with a single type parameter can be simplified to
+            // just that parameter.
+            env = _emitTypeParameter(environment.functionTypeParameters.single);
+            // Skip a no-op evaluation and just return the parameter.
+            if (recipe == '0') return env;
+          } else {
+            var environmentTypes = environment.functionTypeParameters;
+            // Create a dummy interface type to "hold" type arguments.
+            env =
+                emitRtiEval(_emitTypeParameter(environmentTypes.first), '@<0>');
+            // Bind remaining type arguments.
+            for (var i = 1; i < environmentTypes.length; i++) {
+              env = emitRtiBind(env, environmentTypes[i]);
+            }
           }
-        }
-        return emitRtiEval(env, recipe);
-      } else if (environment is ClassTypeEnvironment) {
-        // Class type environments are already constructed and attached to the
-        // instance of a generic class.
-        var env = _runtimeCall('getReifiedType(this)');
-        return emitRtiEval(env, recipe);
-      } else if (environment is ExtendedClassTypeEnvironment) {
-        // A generic class instance already stores a reference to a type
-        // containing all of its type arguments.
-        var env = _runtimeCall('getReifiedType(this)');
-        // Bind extra type parameters.
-        for (var parameter in environment.extendedParameters) {
-          env = emitRtiBind(env, parameter);
-        }
-        return emitRtiEval(env, recipe);
-      } else {
-        _typeCompilationError(type,
-            'Unexpected DDCTypeEnvironment type (${environment.runtimeType}).');
+          return emitRtiEval(env, recipe);
+        case RtiTypeEnvironment():
+          // RTI type environments are already constructed and attached to the
+          // provided RTI.
+          var env = _rtiParam;
+          return emitRtiEval(env, recipe);
+        case ClassTypeEnvironment():
+          // Class type environments are already constructed and attached to the
+          // instance of a generic class.
+          var env =
+              js.call('#.instanceType(this)', [_emitLibraryName(_rtiLibrary)]);
+          return emitRtiEval(env, recipe);
+        case ExtendedClassTypeEnvironment():
+          // Class type environments are already constructed and attached to the
+          // instance of a generic class, but function type parameters need to
+          // be bound.
+          var env =
+              js.call('#.instanceType(this)', [_emitLibraryName(_rtiLibrary)]);
+          // Bind extra type parameters.
+          for (var parameter in environment.functionTypeParameters) {
+            env = emitRtiBind(env, parameter);
+          }
+          return emitRtiEval(env, recipe);
       }
+      _typeCompilationError(type,
+          'Unexpected DDCTypeEnvironment type (${environment.runtimeType}).');
     }
 
     var normalizedType =
         _futureOrNormalizer.normalize(type.extensionTypeErasure);
     try {
       var result = _typeRecipeGenerator.recipeInEnvironment(
-          normalizedType, _currentTypeEnvironment);
+          normalizedType, _currentTypeEnvironment,
+          emitJSInteropGenericClassTypeParametersAsAny:
+              emitJSInteropGenericClassTypeParametersAsAny);
       var typeRep =
           evalInEnvironment(result.requiredEnvironment, result.recipe);
-      if (_cacheTypes &&
-          // Avoid adding a the use of a single type parameter to the type
-          // table. These can be referenced directly because the are already
-          // represented as a local variable in the scope.
-          !(normalizedType is TypeParameterType &&
-                  normalizedType.isPotentiallyNonNullable ||
-              normalizedType is StructuralParameterType &&
-                  normalizedType.isPotentiallyNullable)) {
-        typeRep = _typeTable.nameType(normalizedType, typeRep);
-      }
       return typeRep;
     } on UnsupportedError catch (e) {
       _typeCompilationError(normalizedType, e.message ?? 'Unknown Error');
@@ -3329,10 +3400,8 @@
   /// The nullability of [type] is not considered because it is meaningless when
   /// describing a reference to the class itself.
   ///
-  /// In the case of a generic type, this reference will be a call to the
-  /// function that defines the class and will pass the type parameters as
-  /// arguments. The nullability of the type parameters does have meaning so it
-  /// is encoded.
+  /// For generic classes, type arguments are not needed since they are
+  /// resolved late via an RTI lookup.
   ///
   /// Note that for `package:js` types, this will emit the class we emitted
   /// using `_emitJSInteropClassNonExternalMembers`, and not the runtime type
@@ -3340,14 +3409,9 @@
   js_ast.Expression _emitClassRef(InterfaceType type) {
     var cls = type.classNode;
     _declareBeforeUse(cls);
-    var args = type.typeArguments;
-    Iterable<js_ast.Expression>? jsArgs;
-    if (args.any((a) => a != const DynamicType())) {
-      jsArgs = args.map(_emitType);
-    }
-    if (jsArgs != null) {
-      return _genericClassTable.nameType(
-          type, _emitGenericClassType(type, jsArgs));
+    if (!_emittingClassExtends && type.typeArguments.isNotEmpty) {
+      var genericName = _emitTopLevelNameNoExternalInterop(type.classNode);
+      return js.call('#', [genericName]);
     }
     return _emitTopLevelNameNoExternalInterop(type.classNode);
   }
@@ -3359,15 +3423,6 @@
   bool get _emittingClassExtends =>
       _currentClass != null && identical(_currentClass, _classEmittingExtends);
 
-  bool get _cacheTypes => !_emittingDeferredType && !_emittingClassExtends;
-
-  js_ast.Expression _emitGenericClassType(
-      InterfaceType t, Iterable<js_ast.Expression> typeArgs) {
-    var genericName =
-        _emitTopLevelNameNoExternalInterop(t.classNode, suffix: '\$');
-    return js.call('#(#)', [genericName, typeArgs]);
-  }
-
   /// Emits an expression that lets you access statics on a [type] from code.
   js_ast.Expression _emitConstructorName(InterfaceType type, Member c) {
     var isSyntheticDefault =
@@ -3445,6 +3500,11 @@
     _currentLibrary = library;
     _staticTypeContext.enterLibrary(_currentLibrary!);
     _currentClass = cls;
+    // Generic parameters should be evaluated in a class environment if
+    // provided. Otherwise we default to an empty type environment.
+    if (cls != null) {
+      _currentTypeEnvironment = ClassTypeEnvironment(cls.typeParameters);
+    }
 
     // Keep all symbols in containers.
     _containerizeSymbols = true;
@@ -3504,7 +3564,10 @@
   js_ast.Fun _emitFunction(FunctionNode f, String? name,
       {SourceLocation? functionEnd, SourceLocation? functionBody}) {
     var savedTypeEnvironment = _currentTypeEnvironment;
-    _currentTypeEnvironment = _currentTypeEnvironment.extend(f.typeParameters);
+    if (f.typeParameters.isNotEmpty) {
+      _currentTypeEnvironment =
+          _currentTypeEnvironment.extend(f.typeParameters);
+    }
     var formals = _emitParameters(f);
     var typeFormals = _emitTypeFormals(f.typeParameters);
 
@@ -4482,12 +4545,13 @@
     // full desugaring seems okay.
     var streamIterator = _coreTypes.rawType(
         _asyncStreamIteratorClass, _currentLibrary!.nonNullable);
+    var streamIteratorRti = _emitType(streamIterator);
     var createStreamIter = js_ast.Call(
         _emitConstructorName(
             streamIterator,
             _asyncStreamIteratorClass.procedures
                 .firstWhere((p) => p.isFactory && p.name.text == '')),
-        [_visitExpression(node.iterable)]);
+        [streamIteratorRti, _visitExpression(node.iterable)]);
 
     var iter = _emitTemporaryId('iter');
 
@@ -6107,27 +6171,14 @@
         // Serves as a way to access the wrapped JS exception.
         return _emitVariableRef(_rethrowParameter!);
       }
+      if (name == 'JS_RTI_PARAMETER') {
+        return _rtiParam;
+      }
     }
 
     if (_isSdkInternalRuntime(enclosingLibrary)) {
       var name = target.name.text;
-      if (node.arguments.positional.isEmpty &&
-          node.arguments.types.length == 1) {
-        var type = node.arguments.types.single;
-        if (name == 'getGenericClassStatic') {
-          if (type is InterfaceType) {
-            return _emitTopLevelNameNoExternalInterop(type.classNode,
-                suffix: '\$');
-          }
-          if (type is FutureOrType) {
-            return _emitFutureOrNameNoInterop(suffix: '\$');
-          } else {
-            throw UnsupportedError(
-                '`getGenericClassStatic` Unsupported type found: '
-                '$type (${type.runtimeType}) at ${node.location}');
-          }
-        }
-      } else if (node.arguments.positional.length == 1) {
+      if (node.arguments.positional.length == 1) {
         var firstArg = node.arguments.positional.single;
         if (name == 'extensionSymbol' && firstArg is StringLiteral) {
           return _getSymbol(_getExtensionSymbolInternal(firstArg.value));
@@ -6546,15 +6597,30 @@
         js_ast.Call(_emitTopLevelName(_coreTypes.identicalProcedure), jsArgs));
   }
 
+  /// Returns true if this [member] is a JS interop member.
+  bool isJSInteropMember(Member member) =>
+      member.isExternal && hasJSInteropAnnotation(member.enclosingClass!);
+
   @override
   js_ast.Expression visitConstructorInvocation(ConstructorInvocation node) {
     var ctor = node.target;
     var ctorClass = ctor.enclosingClass;
     var args = node.arguments;
     if (isJSAnonymousType(ctorClass)) return _emitObjectLiteral(args, ctor);
-    var result = js_ast.New(_emitConstructorName(node.constructedType, ctor),
-        _emitArgumentList(args, types: false, target: ctor));
-
+    // JS interop constructor calls do not provide an RTI at the call site.
+    var shouldProvideRti =
+        !isJSInteropMember(ctor) && _requiresRtiForInstantiation(ctorClass);
+    var rti = shouldProvideRti
+        ? _emitType(node.constructedType,
+            emitJSInteropGenericClassTypeParametersAsAny: false)
+        : null;
+    var result = js_ast.New(
+      _emitConstructorName(node.constructedType, ctor),
+      [
+        if (rti != null) rti,
+        ..._emitArgumentList(args, types: false, target: ctor)
+      ],
+    );
     return node.isConst ? _canonicalizeConstObject(result) : result;
   }
 
@@ -6562,7 +6628,8 @@
     var args = node.arguments;
     var ctor = node.target;
     var ctorClass = ctor.enclosingClass!;
-    if (ctor.isExternal && hasJSInteropAnnotation(ctorClass)) {
+    // JS interop constructor calls do not require an RTI at the call site.
+    if (isJSInteropMember(ctor)) {
       return _emitJSInteropNew(ctor, args);
     }
 
@@ -6586,20 +6653,30 @@
         case 'HashMap':
         case 'LinkedHashMap':
           if (ctor.name.text == '') {
-            return js.call('new #.new()', _emitMapImplType(type));
+            var mapType = _createMapImplType(type);
+            var mapClass = _emitClassRef(mapType);
+            var rti = _emitType(mapType);
+            return js.call('new #.new(#)', [mapClass, rti]);
           } else if (ctor.name.text == 'identity') {
-            return js.call(
-                'new #.new()', _emitMapImplType(type, identity: true));
+            var mapType = _createMapImplType(type, identity: true);
+            var mapClass = _emitClassRef(mapType);
+            var rti = _emitType(mapType);
+            return js.call('new #.new(#)', [mapClass, rti]);
           }
           break;
         case 'Set':
         case 'HashSet':
         case 'LinkedHashSet':
           if (ctor.name.text == '') {
-            return js.call('new #.new()', _emitSetImplType(type));
+            var setType = _createSetImplType(type);
+            var setClass = _emitClassRef(setType);
+            var rti = _emitType(setType);
+            return js.call('new #.new(#)', [setClass, rti]);
           } else if (ctor.name.text == 'identity') {
-            return js.call(
-                'new #.new()', _emitSetImplType(type, identity: true));
+            var setType = _createSetImplType(type, identity: true);
+            var setClass = _emitClassRef(setType);
+            var rti = _emitType(setType);
+            return js.call('new #.new(#)', [setClass, rti]);
           }
           break;
         case 'List':
@@ -6609,39 +6686,41 @@
           break;
       }
     }
-
+    var rti = _requiresRtiForInstantiation(ctorClass)
+        ? _emitType(type, emitJSInteropGenericClassTypeParametersAsAny: false)
+        : null;
     var result = js_ast.Call(_emitConstructorName(type, ctor),
-        _emitArgumentList(args, types: false));
-
+        [if (rti != null) rti, ..._emitArgumentList(args, types: false)]);
     return node.isConst ? _canonicalizeConstObject(result) : result;
   }
 
   js_ast.Expression _emitJSInteropNew(Member ctor, Arguments args) {
     var ctorClass = ctor.enclosingClass!;
     if (isJSAnonymousType(ctorClass)) return _emitObjectLiteral(args, ctor);
+    // JS interop constructor calls do not require an RTI at the call site.
     return js_ast.New(
         _emitConstructorName(_coreTypes.nonNullableRawType(ctorClass), ctor),
         _emitArgumentList(args, types: false, target: ctor));
   }
 
-  js_ast.Expression _emitMapImplType(InterfaceType type, {bool? identity}) {
+  InterfaceType _createMapImplType(InterfaceType type, {bool? identity}) {
     var typeArgs = type.typeArguments;
     if (typeArgs.isEmpty) {
-      return _emitClassRef(type);
+      return type.withDeclaredNullability(Nullability.nonNullable);
     }
     identity ??= _typeRep.isPrimitive(typeArgs[0]);
     var c = identity ? _identityHashMapImplClass : _linkedHashMapImplClass;
-    return _emitClassRef(InterfaceType(c, Nullability.nonNullable, typeArgs));
+    return InterfaceType(c, Nullability.nonNullable, typeArgs);
   }
 
-  js_ast.Expression _emitSetImplType(InterfaceType type, {bool? identity}) {
+  InterfaceType _createSetImplType(InterfaceType type, {bool? identity}) {
     var typeArgs = type.typeArguments;
     if (typeArgs.isEmpty) {
-      return _emitClassRef(type);
+      return type.withDeclaredNullability(Nullability.nonNullable);
     }
     identity ??= _typeRep.isPrimitive(typeArgs[0]);
     var c = identity ? _identityHashSetImplClass : _linkedHashSetImplClass;
-    return _emitClassRef(InterfaceType(c, Nullability.nonNullable, typeArgs));
+    return InterfaceType(c, Nullability.nonNullable, typeArgs);
   }
 
   js_ast.Expression _emitObjectLiteral(Arguments node, Member ctor) {
@@ -6945,15 +7024,15 @@
       DartType itemType, List<js_ast.Expression> items) {
     var list = js_ast.ArrayInitializer(items);
 
-    // TODO(jmesserly): analyzer will usually infer `List<Object>` because
-    // that is the least upper bound of the element types. So we rarely
-    // generate a plain `List<dynamic>` anymore.
+    // List's type parameter is default-initialized to dynamic in our runtime.
     if (itemType == const DynamicType()) return list;
 
     // Call `new JSArray<E>.of(list)`
-    var arrayType = _emitClassRef(
-        InterfaceType(_jsArrayClass, Nullability.nonNullable, [itemType]));
-    return js.call('#.of(#)', [arrayType, list]);
+    var type =
+        InterfaceType(_jsArrayClass, Nullability.nonNullable, [itemType]);
+    var arrayClass = _emitClassRef(type);
+    var arrayRti = _emitType(type);
+    return js.call('#.of(#, #)', [arrayClass, arrayRti, list]);
   }
 
   js_ast.Expression _emitConstList(
@@ -6961,20 +7040,22 @@
     // dart.constList helper internally depends on _interceptors.JSArray.
     _declareBeforeUse(_jsArrayClass);
     return _runtimeCall(
-        'constList([#], #)', [elements, _emitType(elementType)]);
+        'constList(#, [#])', [_emitType(elementType), elements]);
   }
 
   @override
   js_ast.Expression visitSetLiteral(SetLiteral node) {
     // TODO(markzipan): remove const check when we use front-end const eval
     if (!node.isConst) {
-      var setType = _emitClassRef(InterfaceType(
-          _linkedHashSetClass, Nullability.nonNullable, [node.typeArgument]));
+      var type = InterfaceType(
+          _linkedHashSetClass, Nullability.nonNullable, [node.typeArgument]);
+      var setClass = _emitClassRef(type);
+      var rti = _emitType(type);
       if (node.expressions.isEmpty) {
-        return js.call('#.new()', [setType]);
+        return js.call('#.new(#)', [setClass, rti]);
       }
-      return js.call(
-          '#.from([#])', [setType, _visitExpressionList(node.expressions)]);
+      return js.call('#.from(#, [#])',
+          [setClass, rti, _visitExpressionList(node.expressions)]);
     }
     return _emitConstSet(
         node.typeArgument, _visitExpressionList(node.expressions));
@@ -6996,12 +7077,14 @@
 
     // TODO(markzipan): remove const check when we use front-end const eval
     if (!node.isConst) {
-      var mapType = _emitMapImplType(
-          node.getStaticType(_staticTypeContext) as InterfaceType);
+      var type = node.getStaticType(_staticTypeContext) as InterfaceType;
+      var mapType = _createMapImplType(type);
+      var mapClass = _emitClassRef(mapType);
+      var rti = _emitType(mapType);
       if (node.entries.isEmpty) {
-        return js.call('new #.new()', [mapType]);
+        return js.call('new #.new(#)', [mapClass, rti]);
       }
-      return js.call('new #.from([#])', [mapType, entries]);
+      return js.call('new #.from(#, [#])', [mapClass, rti, entries]);
     }
     return _emitConstMap(node.keyType, node.valueType, entries);
   }
@@ -7419,6 +7502,12 @@
   @override
   js_ast.Expression visitInstanceConstant(InstanceConstant node) {
     _declareBeforeUse(node.classNode);
+    var savedTypeEnvironment = _currentTypeEnvironment;
+    if (node.classNode.typeParameters.isNotEmpty) {
+      _currentTypeEnvironment =
+          ClassTypeEnvironment(node.classNode.typeParameters);
+    }
+
     js_ast.Property entryToProperty(MapEntry<Reference, Constant> entry) {
       var constant = visitConstant(entry.value);
       var member = entry.key.asField;
@@ -7449,9 +7538,11 @@
       for (var e in node.fieldValues.entries.toList().reversed)
         entryToProperty(e),
     ];
-    return _canonicalizeConstObject(_emitJSObjectSetPrototypeOf(
+    var constant = _canonicalizeConstObject(_emitJSObjectSetPrototypeOf(
         js_ast.ObjectInitializer(properties, multiline: true), prototype,
         fullyQualifiedName: false));
+    _currentTypeEnvironment = savedTypeEnvironment;
+    return constant;
   }
 
   /// Emits a private name JS Symbol for [member] unique to a Dart class
@@ -7665,7 +7756,7 @@
   ///
   /// For example:
   ///
-  ///     runtimeCall('asInt(#)', [<expr>])
+  ///     _runtimeCall('asInt(#)', [<expr>])
   ///
   /// Generates a JS AST representing:
   ///
@@ -8096,8 +8187,8 @@
   ///
   /// The [moduleName] should specify the module's name, and the items should
   /// be the list resulting from startModule, with additional items added,
-  /// but not including the contents of moduleItems (which will be handled by
-  /// this method itself).
+  /// but not including the contents of [_moduleItems] (which will be handled
+  /// by this method itself).
   ///
   /// Note, this function mutates the items list and returns it as the `body`
   /// field of the result.
diff --git a/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart b/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart
index a109325..6e2c53b 100644
--- a/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart
+++ b/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart
@@ -45,15 +45,9 @@
 ///
 /// The caller of this function has to make sure that this name is unique in
 /// the current scope.
-///
-/// In the current encoding, generic classes are generated in a function scope
-/// which avoids name clashes of the escaped class name.
 String getLocalClassName(Class node) => escapeIdentifier(node.name);
 
 /// Returns the escaped name for the type parameter [node].
-///
-/// In the current encoding, generic classes are generated in a function scope
-/// which avoids name clashes of the escaped parameter name.
 String getTypeParameterName(
     /* TypeParameter | StructuralParameter */ Object node) {
   assert(node is TypeParameter || node is StructuralParameter);
diff --git a/pkg/dev_compiler/lib/src/kernel/type_environment.dart b/pkg/dev_compiler/lib/src/kernel/type_environment.dart
index e3185b8..64ea536 100644
--- a/pkg/dev_compiler/lib/src/kernel/type_environment.dart
+++ b/pkg/dev_compiler/lib/src/kernel/type_environment.dart
@@ -23,6 +23,30 @@
   /// Returns the index of [parameter] in this environment for use in a type
   /// recipe or a negative value if the parameter was not found.
   int recipeIndexOf(TypeParameter parameter);
+
+  /// Returns all class type parameters in this type environment.
+  ///
+  /// For the example:
+  /// Class Foo<T> {
+  ///   method<U> {
+  ///     ... // this
+  ///   }
+  /// }
+  ///
+  /// classTypeParameters in this would return ['T'].
+  List<TypeParameter> get classTypeParameters;
+
+  /// Returns all class type parameters in this type environment.
+  ///
+  /// For the example:
+  /// Class Foo<T> {
+  ///   method<U> {
+  ///     ... // this
+  ///   }
+  /// }
+  ///
+  /// functionTypeParameters this would return ['U'].
+  List<TypeParameter> get functionTypeParameters;
 }
 
 /// An empty environment that signals no type parameters are present or needed.
@@ -45,6 +69,12 @@
   int recipeIndexOf(TypeParameter parameter) {
     return -1;
   }
+
+  @override
+  List<TypeParameter> get classTypeParameters => UnmodifiableListView([]);
+
+  @override
+  List<TypeParameter> get functionTypeParameters => UnmodifiableListView([]);
 }
 
 /// A type environment introduced by a class with one or more generic type
@@ -78,6 +108,54 @@
     // class rti with all type arguments.
     return i + 1;
   }
+
+  @override
+  List<TypeParameter> get classTypeParameters =>
+      UnmodifiableListView(_typeParameters);
+
+  @override
+  List<TypeParameter> get functionTypeParameters => UnmodifiableListView([]);
+}
+
+/// A type environment introduced by a subroutine, wherein an RTI object is
+/// explicitly provided.
+class RtiTypeEnvironment implements DDCTypeEnvironment {
+  final List<TypeParameter> _typeParameters;
+
+  RtiTypeEnvironment(this._typeParameters);
+
+  @override
+  DDCTypeEnvironment extend(List<TypeParameter> parameters) {
+    /// RtiTypeEnvironments are only used for constructors, factories, and type
+    /// signatures - none of which can accept generic arguments.
+    throw Exception(
+        'RtiTypeEnvironments should not receive extended type parameters.');
+  }
+
+  @override
+  DDCTypeEnvironment prune(Iterable<TypeParameter> requiredParameters) {
+    // If any parameters are required, the class type environment already
+    // exists and a reference to it is suitably compact.
+    return requiredParameters.any(_typeParameters.contains)
+        ? this
+        : const EmptyTypeEnvironment();
+  }
+
+  @override
+  int recipeIndexOf(TypeParameter parameter) {
+    var i = _typeParameters.indexOf(parameter);
+    if (i < 0) return i;
+    // Index for class type parameters is one based. Zero refers to the full
+    // class rti with all type arguments.
+    return i + 1;
+  }
+
+  @override
+  List<TypeParameter> get classTypeParameters =>
+      UnmodifiableListView(_typeParameters);
+
+  @override
+  List<TypeParameter> get functionTypeParameters => UnmodifiableListView([]);
 }
 
 /// A type environment containing multiple type parameters.
@@ -115,8 +193,12 @@
     return i + 1;
   }
 
-  /// The type parameters in this environment.
-  List<TypeParameter> get parameters => UnmodifiableListView(_typeParameters);
+  @override
+  List<TypeParameter> get classTypeParameters => UnmodifiableListView([]);
+
+  @override
+  List<TypeParameter> get functionTypeParameters =>
+      UnmodifiableListView(_typeParameters);
 
   /// Returns `true` if this environment only contains a single type parameter.
   bool get isSingleTypeParameter => _typeParameters.length == 1;
@@ -155,10 +237,6 @@
           // No type parameters are needed from this environment.
           : const EmptyTypeEnvironment();
     }
-    if (!classEnvironmentNeeded) {
-      // None of the parameters are needed from the class environment.
-      return BindingTypeEnvironment(additionalParameters.toList());
-    }
     // This is already the exact environment needed.
     if (additionalParameters.length == _typeParameters.length) return this;
     // An extended class environment with fewer additional parameters is needed.
@@ -183,6 +261,11 @@
     return -1;
   }
 
-  List<TypeParameter> get extendedParameters =>
+  @override
+  List<TypeParameter> get classTypeParameters =>
+      UnmodifiableListView(_classEnvironment.classTypeParameters);
+
+  @override
+  List<TypeParameter> get functionTypeParameters =>
       UnmodifiableListView(_typeParameters);
 }
diff --git a/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart b/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart
index 384707b..3f8cd6b 100644
--- a/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart
+++ b/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart
@@ -43,14 +43,19 @@
   /// The returned environment will be a subset of the provided [environment]
   /// that includes only the necessary types to evaluate the recipe.
   GeneratedRecipe recipeInEnvironment(
-      DartType type, DDCTypeEnvironment environment) {
+      DartType type, DDCTypeEnvironment environment,
+      {bool emitJSInteropGenericClassTypeParametersAsAny = true}) {
     // Reduce the provided environment down to the parameters that are present.
     var parametersInType = TypeParameterFinder.instance().find(type);
     var minimalEnvironment = environment.prune(parametersInType);
     // Set the visitor state, generate the recipe, and package it with the
     // environment required to evaluate it.
     _recipeVisitor.setState(
-        environment: minimalEnvironment, addLiveInterfaceTypes: true);
+      environment: minimalEnvironment,
+      addLiveInterfaceTypes: true,
+      emitJSInteropGenericClassTypeParametersAsAny:
+          emitJSInteropGenericClassTypeParametersAsAny,
+    );
     var recipe = type.accept(_recipeVisitor);
     return GeneratedRecipe(recipe, minimalEnvironment);
   }
@@ -124,7 +129,8 @@
           environment: ClassTypeEnvironment(cls.typeParameters),
           // No need to add any more live types at this time. Any "new" types
           // seen in this process are not live.
-          addLiveInterfaceTypes: false);
+          addLiveInterfaceTypes: false,
+          emitClassTypeParametersAsIndices: true);
       var supertypeEntries = <String, Object>{};
       // Encode the type argument mapping portion of this type rule.
       for (var i = 0; i < cls.typeParameters.length; i++) {
@@ -258,6 +264,24 @@
   /// all of the visited interface types.
   bool _addLiveInterfaceTypes = true;
 
+  /// When `true`, references generic class type parameters via their de Bruijn
+  /// indices.
+  ///
+  /// DDC defaults to using generic parameter names instead of de Bruijn
+  /// indices because the latter aren't stable across subtype environments.
+  ///
+  /// This is only used when generating type rules for the type universe
+  /// (i.e., in `liveInterfaceTypeRules`).
+  bool _emitClassTypeParametersAsIndices = false;
+
+  /// Emits JS interop generic arguments as `Any` when true and their 'real'
+  /// type when false.
+  ///
+  /// We usually convert type arguments to `Any` to allow type checks and casts
+  /// to succeed for JS interop objects. However, their 'real' types are used
+  /// for checks in non-external constructors and factories.
+  bool _emitJSInteropGenericClassTypeParametersAsAny = true;
+
   /// All of the [InterfaceType]s visited.
   final _visitedInterfaceTypes = <InterfaceType>{};
   final _visitedJsInteropTypes = <InterfaceType>{};
@@ -270,12 +294,23 @@
   /// Generally this should be called before visiting a type, but the visitor
   /// does not modify the state so if many types need to be evaluated in the
   /// same state it can be set once before visiting all of them.
-  void setState(
-      {DDCTypeEnvironment? environment, bool? addLiveInterfaceTypes}) {
+  void setState({
+    DDCTypeEnvironment? environment,
+    bool? addLiveInterfaceTypes,
+    bool? emitClassTypeParametersAsIndices,
+    bool? emitJSInteropGenericClassTypeParametersAsAny,
+  }) {
     if (environment != null) _typeEnvironment = environment;
     if (addLiveInterfaceTypes != null) {
       _addLiveInterfaceTypes = addLiveInterfaceTypes;
     }
+    if (emitClassTypeParametersAsIndices != null) {
+      _emitClassTypeParametersAsIndices = emitClassTypeParametersAsIndices;
+    }
+    if (emitJSInteropGenericClassTypeParametersAsAny != null) {
+      _emitJSInteropGenericClassTypeParametersAsAny =
+          emitJSInteropGenericClassTypeParametersAsAny;
+    }
   }
 
   /// The [InterfaceType]s that have been visited.
@@ -312,7 +347,8 @@
     var recipeBuffer = StringBuffer(interfaceTypeRecipe(cls));
     // Generate the recipes for all type arguments.
     if (node.typeArguments.isNotEmpty) {
-      var argumentRecipes = hasJSInteropAnnotation(cls)
+      var argumentRecipes = _emitJSInteropGenericClassTypeParametersAsAny &&
+              hasJSInteropAnnotation(cls)
           ? _listOfJSAnyType(node.typeArguments.length)
           : node.typeArguments.map((typeArgument) => typeArgument.accept(this));
       recipeBuffer.write(Recipe.startTypeArgumentsString);
@@ -433,6 +469,26 @@
           '${Recipe.genericFunctionTypeParameterIndexString}'
           '${_nullabilityRecipe(node)}';
     }
+    var parameterDeclaration = node.parameter.declaration;
+    if (!_emitClassTypeParametersAsIndices && parameterDeclaration is Class) {
+      // Generic type parameters in binding positions will still be emitted
+      // with indices.
+      var typeParamInBindingPosition = _typeEnvironment
+              is BindingTypeEnvironment ||
+          (_typeEnvironment is ExtendedClassTypeEnvironment &&
+              _typeEnvironment.functionTypeParameters.contains(node.parameter));
+      if (!typeParamInBindingPosition) {
+        // We don't emit RTI rules for anonymous mixins since 1) their
+        // generated names are very large and 2) they aren't declared in user
+        // code. However, type parameters that reference them may still appear
+        // (such as for covariant mixin stubs), so we reference them via their
+        // mixin application's subclass.
+        var classNameToEmit = parameterDeclaration.isAnonymousMixin
+            ? parameterDeclaration.nameAsMixinApplicationSubclass
+            : parameterDeclaration.name;
+        return '$classNameToEmit.${node.parameter.name}${_nullabilityRecipe(node)}';
+      }
+    }
     i = _typeEnvironment.recipeIndexOf(node.parameter);
     if (i < 0) {
       throw UnsupportedError(
diff --git a/pkg/dev_compiler/test/expression_compiler/runtime_debugger_api_test.dart b/pkg/dev_compiler/test/expression_compiler/runtime_debugger_api_test.dart
index bf7a04c..5017f33 100644
--- a/pkg/dev_compiler/test/expression_compiler/runtime_debugger_api_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/runtime_debugger_api_test.dart
@@ -160,7 +160,8 @@
     test('getClassMetadata (object)', () async {
       await driver.checkRuntimeInFrame(
           breakpointId: 'BP',
-          expression: 'dart.getClassMetadata("dart:core", "Object")',
+          expression:
+              'dart.getClassMetadata("dart:core", "Object", {"objectInstance": object})',
           expectedResult: {
             'className': 'Object',
             'fields': {},
@@ -181,7 +182,7 @@
       await driver.checkRuntimeInFrame(
           breakpointId: 'BP',
           expression:
-              'dart.getClassMetadata("package:eval_test/test.dart", "BaseClass")',
+              'dart.getClassMetadata("package:eval_test/test.dart", "BaseClass", {"objectInstance": base})',
           expectedResult: {
             'className': 'BaseClass',
             'superClassName': 'Object',
@@ -243,7 +244,7 @@
       await driver.checkRuntimeInFrame(
           breakpointId: 'BP',
           expression:
-              'dart.getClassMetadata("package:eval_test/test.dart", "DerivedClass")',
+              'dart.getClassMetadata("package:eval_test/test.dart", "DerivedClass", {"objectInstance": derived})',
           expectedResult: {
             'className': 'DerivedClass',
             'superClassName': 'BaseClass',
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart
index dcd20a8..3462510 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart
@@ -98,85 +98,8 @@
 
 final mixinOn = JS('', 'Symbol("mixinOn")');
 
-/// The Symbol for storing type arguments on a specialized generic type.
-final _typeArguments = JS('', 'Symbol("typeArguments")');
-
-final _originalDeclaration = JS('', 'Symbol("originalDeclaration")');
-
 final mixinNew = JS('', 'Symbol("dart.mixinNew")');
 
-/// Memoize a generic type constructor function.
-generic(typeConstructor, setBaseClass) => JS('', '''(() => {
-  let length = $typeConstructor.length;
-  if (length < 1) {
-    $throwInternalError('must have at least one generic type argument');
-  }
-  let resultMap = new Map();
-  // TODO(vsm): Rethink how to clear the resultMap on hot restart.
-  // A simple clear via:
-  //   _cacheMaps.push(resultMap);
-  // will break (a) we hoist type expressions in generated code and
-  // (b) we don't clear those type expressions in the presence of a
-  // hot restart.  Not clearing this map (as we're doing now) should
-  // not affect correctness, but can result in a memory leak across
-  // multiple restarts.
-  function makeGenericType(...args) {
-    if (args.length != length && args.length != 0) {
-      $throwInternalError('requires ' + length + ' or 0 type arguments');
-    }
-    while (args.length < length) args.push(${TYPE_REF<dynamic>()});
-
-    let value = resultMap;
-    for (let i = 0; i < length; i++) {
-      let arg = args[i];
-      if (arg == null) {
-        $throwInternalError('type arguments should not be null: '
-                          + $typeConstructor);
-      }
-      let map = value;
-      value = map.get(arg);
-      if (value === void 0) {
-        if (i + 1 == length) {
-          value = $typeConstructor.apply(null, args);
-          // Save the type constructor and arguments for reflection.
-          if (value) {
-            value[$_typeArguments] = args;
-            value[$_originalDeclaration] = makeGenericType;
-          }
-          map.set(arg, value);
-          if ($setBaseClass != null) $setBaseClass.apply(null, args);
-        } else {
-          value = new Map();
-          map.set(arg, value);
-        }
-      }
-    }
-    return value;
-  }
-  makeGenericType[$_genericTypeCtor] = $typeConstructor;
-  return makeGenericType;
-})()''');
-
-getGenericClass(type) => safeGetOwnProperty(type, _originalDeclaration);
-
-/// Extracts the type argument as the accessor for the JS class.
-///
-/// Should be used in place of [getGenericClass] when we know the class we want
-/// statically.
-///
-/// This value is extracted and inlined by the compiler without any runtime
-/// operations. The implementation here is only provided as a theoretical fall
-/// back and shouldn't actually be run.
-///
-/// For example `getGenericClassStatic<FutureOr>` emits `async.FutureOr$`
-/// directly.
-external getGenericClassStatic<T>();
-
-// TODO(markzipan): Make this non-nullable if we can ensure this returns
-// an empty list or if null and the empty list are semantically the same.
-List? getGenericArgs(type) =>
-    JS<List?>('', '#', safeGetOwnProperty(type, _typeArguments));
-
 Object instantiateClass(Object genericClass, List<Object> typeArgs) {
   return JS('', '#.apply(null, #)', genericClass, typeArgs);
 }
@@ -208,8 +131,10 @@
 
 getGenericTypeCtor(value) => JS('', '#[#]', value, _genericTypeCtor);
 
-/// Get the type of an object.
-getType(obj) {
+/// Returns the type signature storage site for [obj].
+///
+/// This is typically an instance's JS constructor.
+getTypeSignatureContainer(obj) {
   if (obj == null) return JS('!', '#', Object);
 
   // Object.create(null) produces a js object without a prototype.
@@ -222,23 +147,13 @@
 getLibraryUri(value) => JS('', '#[#]', value, _libraryUri);
 setLibraryUri(f, uri) => JS('', '#[#] = #', f, _libraryUri, uri);
 
-/// Returns the name of the Dart class represented by [cls] including the
-/// instantiated type arguments.
+/// Returns the name of the Dart class represented by [cls].
 @notNull
 String getClassName(Object? cls) {
   if (cls != null) {
     var tag = JS('', '#[#]', cls, rti.interfaceTypeRecipePropertyName);
     if (tag != null) {
-      var name = JS<String>('!', '#.name', cls);
-      var args = getGenericArgs(cls);
-      if (args == null) return name;
-      var result = name + '<';
-      for (var i = 0; i < JS<int>('!', '#.length', args); ++i) {
-        if (i > 0) result += ', ';
-        result += typeName(JS('', '#[#]', args, i));
-      }
-      result += '>';
-      return result;
+      return JS<String>('!', '#.name', cls);
     }
   }
   return 'unknown (null)';
@@ -270,22 +185,45 @@
   return !_jsInstanceOf(obj, Object);
 }
 
-/// Get the type of a method from a type using the stored signature
-getMethodType(type, name) {
-  var m = getMethods(type);
-  return m != null ? JS('', '#[#]', m, name) : null;
+/// Returns the RTI for an object instance's field or method signature.
+Object? rtiFromSignature(instance, signature) {
+  if (signature == null) return signature;
+  if (JS<bool>('!', 'typeof # == "function"', signature)) {
+    // Signatures for generic types are resolved at runtime. A JS function here
+    // indicates that this signature has not yet been bound to an instance.
+    return JS<Object>('', '#(#)', signature, rti.instanceType(instance));
+  }
+  return signature;
 }
 
-/// Returns the default type argument values for the instance method [name] on
-/// the class [type].
-JSArray<Object> getMethodDefaultTypeArgs(type, name) =>
-    JS('!', '#[#]', getMethodsDefaultTypeArgs(type), name);
+/// Returns the type of a method [name] from an object instance [obj].
+getMethodType(obj, name) {
+  var typeSigHolder = getTypeSignatureContainer(obj);
+  var m = getMethods(typeSigHolder);
+  if (m == null) return null;
+  return rtiFromSignature(obj, JS<Object?>('', '#[#]', m, name));
+}
 
-/// Gets the type of the corresponding setter (this includes writable fields).
-getSetterType(type, name) {
-  var setters = getSetters(type);
+/// Returns the default type argument values for the instance method [name].
+JSArray<Object> getMethodDefaultTypeArgs(obj, name) {
+  var typeSigHolder = getTypeSignatureContainer(obj);
+  var typeArgsOrFunction =
+      JS<Object>('', '#[#]', getMethodsDefaultTypeArgs(typeSigHolder), name);
+  if (JS<bool>('!', 'typeof # == "function"', typeArgsOrFunction)) {
+    // Signatures for generic types are resolved at runtime.
+    // A JS function here indicates that this signature has not yet been bound
+    // to an instance.
+    typeArgsOrFunction =
+        JS<Object>('', '#(#)', typeArgsOrFunction, rti.instanceType(obj));
+  }
+  return JS<JSArray<Object>>('', '#', typeArgsOrFunction);
+}
+
+/// Returns the type of a setter [name] from an object instance [obj].
+getSetterType(obj, name) {
+  var typeSigHolder = getTypeSignatureContainer(obj);
+  var setters = getSetters(typeSigHolder);
   if (setters != null) {
-    var type = JS('', '#[#]', setters, name);
     // TODO(nshahan): setters object has properties installed on the global
     // Object that requires some extra validation to ensure they are intended
     // as setters. ex: dartx.hashCode, dartx._equals, dartx.toString etc.
@@ -293,15 +231,20 @@
     // There is a value mapped to 'toString' in setters so broken code like this
     // results in very confusing behavior:
     // `d.toString = 99;`
-    if (type != null) {
-      return type;
+    var type = JS<Object?>('', '#[#]', setters, name);
+    if (type != null && JS<bool>('!', 'typeof # == "function"', type)) {
+      // Signatures for generic types are resolved at runtime.
+      // A JS function here indicates that this signature has not yet been bound
+      // to an instance.
+      type = JS<Object>('', '#(#)', type, rti.instanceType(obj));
     }
+    if (type != null) return type;
   }
-  var fields = getFields(type);
+  var fields = getFields(typeSigHolder);
   if (fields != null) {
-    var fieldInfo = JS('', '#[#]', fields, name);
+    var fieldInfo = JS<Object?>('', '#[#]', fields, name);
     if (fieldInfo != null && JS<bool>('!', '!#.isFinal', fieldInfo)) {
-      return JS('', '#.type', fieldInfo);
+      return rtiFromSignature(obj, JS<Object?>('', '#.type', fieldInfo));
     }
   }
   return null;
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/debugger.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/debugger.dart
index 9f5e909..bd3ca45 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/debugger.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/debugger.dart
@@ -96,7 +96,8 @@
 /// ```
 /// TODO(annagrin): remove when debugger reads symbols.
 /// Issue: https://github.com/dart-lang/sdk/issues/40273
-Object? getClassMetadata(@notNull String libraryUri, @notNull String name) {
+Object? getClassMetadata(@notNull String libraryUri, @notNull String name,
+    {Object? objectInstance}) {
   var library = getLibrary('$libraryUri');
   if (library == null) throw 'cannot find library for $libraryUri';
 
@@ -108,6 +109,7 @@
   _collectFieldDescriptors(
     fieldDescriptors,
     getFields(cls),
+    objectInstance: objectInstance,
   );
   _collectFieldDescriptorsFromNames(
     fieldDescriptors,
@@ -222,17 +224,19 @@
   @notNull Map<String, dynamic> fieldDescriptors,
   Object? fields, {
   bool isStatic = false,
+  Object? objectInstance,
 }) {
   if (fields == null) return;
 
   for (var symbol in getOwnNamesAndSymbols(fields)) {
     var fieldInfo = _get<Object>(fields, symbol);
-    var type = _get(fieldInfo, 'type');
+    // An object instance is required to resolve the type of a field.
+    var typeSignature = _get(fieldInfo, 'type');
+    var typeRti = rtiFromSignature(objectInstance, typeSignature);
+    var className = typeRti == null ? '?' : typeName(typeRti);
     var isConst = _get(fieldInfo, 'isConst');
     var isFinal = _get(fieldInfo, 'isFinal');
     var libraryId = _get(fieldInfo, 'libraryUri');
-
-    var className = typeName(type);
     var name = _getDartSymbolName(symbol);
     if (name == null) continue;
 
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart
index 4242a6d..da32ef2 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart
@@ -107,23 +107,6 @@
 }
 
 String _castErrorMessage(from, to) {
-  // If both types are generic classes, see if we can infer generic type
-  // arguments for `from` that would allow the subtype relation to work.
-  // TODO(#40326) Fix suggested type or remove this code if no longer needed.
-  // var fromClass = getGenericClass(from);
-  // if (fromClass != null) {
-  //   var fromTypeFormals = getGenericTypeFormals(fromClass);
-  //   var fromType = instantiateClass(fromClass, fromTypeFormals);
-  //   var inferrer = _TypeInferrer(fromTypeFormals);
-  //   if (inferrer.trySubtypeMatch(fromType, to)) {
-  //     var inferredTypes = inferrer.getInferredTypes();
-  //     if (inferredTypes != null) {
-  //       var inferred = instantiateClass(fromClass, inferredTypes);
-  //       return "Type '${typeName(from)}' should be '${typeName(inferred)}' "
-  //           "to implement expected type '${typeName(to)}'.";
-  //     }
-  //   }
-  // }
   var fromName = "'${typeName(from)}'";
   var toName = "'${typeName(to)}'";
   if (fromName == toName) {
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
index 413d545..8b029c1 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
@@ -53,15 +53,14 @@
   // TODO(jmesserly): canonicalize tearoffs.
   JS('', '#._boundObject = #', f, obj);
   JS('', '#._boundMethod = #', f, method);
-  var objType = getType(obj);
-  var methodType = getMethodType(objType, name);
+  var methodType = getMethodType(obj, name);
   // Native JavaScript methods do not have Dart signatures attached that need
   // to be copied.
   if (methodType != null) {
     if (rti.isGenericFunctionType(methodType)) {
       // Attach the default type argument values to the new function in case
       // they are needed for a dynamic call.
-      var defaultTypeArgs = getMethodDefaultTypeArgs(objType, name);
+      var defaultTypeArgs = getMethodDefaultTypeArgs(obj, name);
       JS('', '#._defaultTypeArgs = #', f, defaultTypeArgs);
     }
     JS('', '#[#] = #', f, JS_GET_NAME(JsGetName.SIGNATURE_NAME), methodType);
@@ -80,8 +79,7 @@
 /// a native type/interface with `call`.
 bindCall(obj, name) {
   if (obj == null) return null;
-  var objType = getType(obj);
-  var ftype = getMethodType(objType, name);
+  var ftype = getMethodType(obj, name);
   if (ftype == null) return null;
   var method = JS('', '#[#]', obj, name);
   var f = JS('', '#.bind(#)', method, obj);
@@ -92,7 +90,7 @@
   if (rti.isGenericFunctionType(ftype)) {
     // Attach the default type argument values to the new function in case
     // they are needed for a dynamic call.
-    var defaultTypeArgs = getMethodDefaultTypeArgs(objType, name);
+    var defaultTypeArgs = getMethodDefaultTypeArgs(obj, name);
     JS('', '#._defaultTypeArgs = #', f, defaultTypeArgs);
   }
   return f;
@@ -104,8 +102,8 @@
 /// associated function type.
 gbind(f, @rest List<Object> typeArgs) {
   Object fnType = JS('!', '#[#]', f, JS_GET_NAME(JsGetName.SIGNATURE_NAME));
-  var instantiationBinding =
-      rti.bindingRtiFromList(JS<JSArray>('!', '#', typeArgs));
+  var typeArgsAsJSArray = JS<JSArray<Object>>('!', '#', typeArgs);
+  var instantiationBinding = rti.bindingRtiFromList(typeArgsAsJSArray);
   var instantiatedType = rti.instantiatedGenericFunctionType(
       JS<rti.Rti>('!', '#', fnType), instantiationBinding);
   // Create a JS wrapper function that will also pass the type arguments.
@@ -114,7 +112,7 @@
   // Tag the wrapper with the original function to be used for equality
   // checks.
   JS('', '#["_originalFn"] = #', result, f);
-  JS('', '#["_typeArgs"] = #', result, constList(typeArgs, Object));
+  JS('', '#["_typeArgs"] = #', result, constList<Object>(typeArgsAsJSArray));
 
   // Tag the wrapper with the instantiated function type.
   return fn(result, instantiatedType);
@@ -133,10 +131,11 @@
 
   trackCall(obj);
   if (f != null) {
-    var type = getType(obj);
+    var typeSigHolder = getTypeSignatureContainer(obj);
 
-    if (hasField(type, f) || hasGetter(type, f)) return JS('', '#[#]', obj, f);
-    if (hasMethod(type, f)) return bind(obj, f, null);
+    if (hasField(typeSigHolder, f) || hasGetter(typeSigHolder, f))
+      return JS('', '#[#]', obj, f);
+    if (hasMethod(typeSigHolder, f)) return bind(obj, f, null);
 
     // Handle record types by trying to access [f] via convenience getters.
     if (_jsInstanceOf(obj, RecordImpl) && f is String) {
@@ -152,19 +151,13 @@
   return noSuchMethod(obj, InvocationImpl(field, JS('', '[]'), isGetter: true));
 }
 
-_stripGenericArguments(type) {
-  var genericClass = getGenericClass(type);
-  if (genericClass != null) return JS('', '#()', genericClass);
-  return type;
-}
-
 dputRepl(obj, field, value) => dput(obj, replNameLookup(obj, field), value);
 
 dput(obj, field, value) {
   var f = _canonicalMember(obj, field);
   trackCall(obj);
   if (f != null) {
-    var setterType = getSetterType(getType(obj), f);
+    var setterType = getSetterType(obj, f);
     if (setterType != null) {
       return JS('', '#[#] = #.#(#)', obj, f, setterType,
           JS_GET_NAME(JsGetName.RTI_FIELD_AS), value);
@@ -504,11 +497,10 @@
     return noSuchMethod(obj, InvocationImpl(displayName, args, isMethod: true));
   }
   var f = obj != null ? JS('', '#[#]', obj, symbol) : null;
-  var type = getType(obj);
-  var ftype = getMethodType(type, symbol);
+  var ftype = getMethodType(obj, symbol);
   if (ftype != null && rti.isGenericFunctionType(ftype) && typeArgs == null) {
     // No type arguments were provided, use the default values in this call.
-    typeArgs = getMethodDefaultTypeArgs(type, symbol);
+    typeArgs = getMethodDefaultTypeArgs(obj, symbol);
   }
   // No such method if dart object and ftype is missing.
   return _checkAndCall(f, ftype, obj, typeArgs, args, named, displayName);
@@ -607,9 +599,6 @@
   return x;
 }
 
-/// The global constant map table.
-final constantMaps = JS<Object>('!', 'new Map()');
-
 // TODO(leafp): This table gets quite large in apps.
 // Keeping the paths is probably expensive.  It would probably
 // be more space efficient to just use a direct hash table with
@@ -621,6 +610,30 @@
   return result!;
 }
 
+/// The global constant list table.
+/// This maps the number of elements in the list (n)
+/// to a path of length n of maps indexed by the value
+/// of the field.  The final map is indexed by the element
+/// type and contains the canonical version of the list.
+final constantLists = JS<Object>('!', 'new Map()');
+
+/// Canonicalize a constant list
+List<E> constList<E>(JSArray elements) {
+  var count = elements.length;
+  var map = _lookupNonTerminal(constantLists, count);
+  for (var i = 0; i <= count; i++) {
+    map = _lookupNonTerminal(map, JS('', '#[#]', elements, i));
+  }
+  List<E>? result = JS('', '#.get(#)', map, E);
+  if (result != null) return result;
+  result = JSArray.unmodifiable(elements);
+  JS('', '#.set(#, #)', map, E, result);
+  return result;
+}
+
+/// The global constant map table.
+final constantMaps = JS<Object>('!', 'new Map()');
+
 Map<K, V> constMap<K, V>(JSArray elements) {
   var count = elements.length;
   var map = _lookupNonTerminal(constantMaps, count);
@@ -728,28 +741,6 @@
   return $obj;
 })()''');
 
-/// The global constant list table.
-/// This maps the number of elements in the list (n)
-/// to a path of length n of maps indexed by the value
-/// of the field.  The final map is indexed by the element
-/// type and contains the canonical version of the list.
-final constantLists = JS('', 'new Map()');
-
-/// Canonicalize a constant list
-constList(elements, elementType) => JS('', '''(() => {
-  let count = $elements.length;
-  let map = $_lookupNonTerminal($constantLists, count);
-  for (let i = 0; i < count; i++) {
-    map = $_lookupNonTerminal(map, elements[i]);
-  }
-  let value = map.get($elementType);
-  if (value) return value;
-
-  ${getGenericClassStatic<JSArray>()}($elementType).unmodifiable($elements);
-  map.set($elementType, elements);
-  return elements;
-})()''');
-
 constFn(x) => JS('', '() => x');
 
 /// Gets the extension symbol given a member [name].
diff --git a/sdk/lib/_internal/js_dev_runtime/private/debugger.dart b/sdk/lib/_internal/js_dev_runtime/private/debugger.dart
index ab67707..8840907 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/debugger.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/debugger.dart
@@ -568,13 +568,15 @@
   bool hasChildren(object) => true;
 
   children(object) {
-    var type = dart.getType(object);
+    var typeSigHolder = dart.getTypeSignatureContainer(object);
     var ret = LinkedHashSet<NameValuePair>();
     // We use a Set rather than a List to avoid duplicates.
     var fields = Set<NameValuePair>();
-    addPropertiesFromSignature(dart.getFields(type), fields, object, true);
+    addPropertiesFromSignature(
+        dart.getFields(typeSigHolder), fields, object, true);
     var getters = Set<NameValuePair>();
-    addPropertiesFromSignature(dart.getGetters(type), getters, object, true);
+    addPropertiesFromSignature(
+        dart.getGetters(typeSigHolder), getters, object, true);
     ret.addAll(sortProperties(fields));
     ret.addAll(sortProperties(getters));
     addMetadataChildren(object, ret);
diff --git a/sdk/lib/_internal/js_dev_runtime/private/foreign_helper.dart b/sdk/lib/_internal/js_dev_runtime/private/foreign_helper.dart
index 0d0ffb4..65275b5 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/foreign_helper.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/foreign_helper.dart
@@ -209,6 +209,10 @@
 /// representations in JavaScript.
 external String JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG();
 
+/// Returns the identifier associated with the RTI parameter passed to some
+/// generic constructors, factories, and signatures.
+external String JS_RTI_PARAMETER();
+
 /// Returns the JS name for [name] from the Namer.
 @pragma('ddc:trust-inline')
 external String JS_GET_NAME(JsGetName name);
diff --git a/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart b/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
index 22d6926..42848d3 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
@@ -7,7 +7,7 @@
 import 'dart:collection';
 import 'dart:_internal' hide Symbol;
 import 'dart:_js_helper';
-import 'dart:_foreign_helper' show JS, JS_GET_FLAG, TYPE_REF;
+import 'dart:_foreign_helper' show JS, JS_GET_FLAG, TYPE_REF, JS_RTI_PARAMETER;
 import 'dart:math' show Random, ln2;
 import 'dart:_rti' as rti show createRuntimeType, Rti;
 import 'dart:_runtime' as dart;
diff --git a/sdk/lib/_internal/js_dev_runtime/private/js_array.dart b/sdk/lib/_internal/js_dev_runtime/private/js_array.dart
index af4742c..553f0cb 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/js_array.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/js_array.dart
@@ -4,6 +4,9 @@
 
 part of dart._interceptors;
 
+/// Holds cached RTI objects for JS Array instances.
+final _arrayRtiSymbol = JS<Object>('', 'Symbol("arrayRti")');
+
 /// The interceptor class for [List]. The compiler recognizes this
 /// class as an interceptor, and changes references to [:this:] to
 /// actually use the receiver of the method, which is generated as an extra
@@ -17,34 +20,36 @@
   /// Array. Used for creating literal lists.
   factory JSArray.of(@notNull Object list) {
     // TODO(sra): Move this to core.List for better readability.
-    //
-    // TODO(jmesserly): this uses special compiler magic to close over the
-    // parameterized ES6 'JSArray' class.
-    jsObjectSetPrototypeOf(list, JS('', 'JSArray.prototype'));
+    jsObjectSetPrototypeOf(list, JS('', 'this.prototype'));
+    JS('', '#.# = #', list, _arrayRtiSymbol, JS_RTI_PARAMETER());
     return JS('-dynamic', '#', list);
   }
 
   // TODO(jmesserly): consider a fixed array subclass instead.
   factory JSArray.fixed(@notNull Object list) {
-    jsObjectSetPrototypeOf(list, JS('', 'JSArray.prototype'));
+    jsObjectSetPrototypeOf(list, JS('', 'this.prototype'));
     JS('', r'#.fixed$length = Array', list);
+    JS('', '#.# = #', list, _arrayRtiSymbol, JS_RTI_PARAMETER());
     return JS('-dynamic', '#', list);
   }
 
   factory JSArray.unmodifiable(@notNull Object list) {
-    jsObjectSetPrototypeOf(list, JS('', 'JSArray.prototype'));
+    jsObjectSetPrototypeOf(list, JS('', 'this.prototype'));
     JS('', r'#.fixed$length = Array', list);
     JS('', r'#.immutable$list = Array', list);
+    JS('', '#.# = #', list, _arrayRtiSymbol, JS_RTI_PARAMETER());
     return JS('-dynamic', '#', list);
   }
 
   /// Provides the Rti object for this.
   ///
+  /// Default-initialized to JSArray<dynamic>.
   /// Only intended for use by the dart:_rti library.
   ///
   /// NOTE: The name of this getter is directly tied to the result of compiling
   /// `JS_EMBEDDED_GLOBAL('', ARRAY_RTI_PROPERTY)`.
-  Object get arrayRti => TYPE_REF<JSArray<E>>();
+  Object get arrayRti => JS<Object>(
+      '', '#.# || #', this, _arrayRtiSymbol, TYPE_REF<JSArray<dynamic>>());
 
   /// Unsupported action, only provided here to help diagnosis of an accidental
   /// attempt to set the value manually.
diff --git a/tests/dartdevc/const_test.dart b/tests/dartdevc/const_test.dart
index 24f46ab..f40a5c1 100644
--- a/tests/dartdevc/const_test.dart
+++ b/tests/dartdevc/const_test.dart
@@ -10,7 +10,7 @@
   dynamic data = JS('', '[1, 2, 3, 4]');
   Expect.isFalse(data is List<int>);
 
-  var list = dart.constList(data, TYPE_REF<int>());
+  var list = dart.constList<int>(data);
   Expect.isTrue(list is List<int>);
   Expect.throws(() {
     list[0] = 0;
diff --git a/tests/dartdevc/debugger/debugger_test_golden.txt b/tests/dartdevc/debugger/debugger_test_golden.txt
index 78231d9..59970a2 100644
--- a/tests/dartdevc/debugger/debugger_test_golden.txt
+++ b/tests/dartdevc/debugger/debugger_test_golden.txt
@@ -239,7 +239,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "List<Object>"
+    "List"
 ]
 -----------------------------------
 Test: List<Object> definition formatting body
@@ -1788,7 +1788,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "List<int>"
+    "List"
 ]
 -----------------------------------
 Test: List<int> large definition formatting body
@@ -3371,7 +3371,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "MappedListIterable<String, String>"
+    "MappedListIterable"
 ]
 -----------------------------------
 Test: Iterable definition formatting body
@@ -4148,7 +4148,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "LinkedSet<dynamic>"
+    "LinkedSet"
 ]
 -----------------------------------
 Test: Set definition formatting body
@@ -4560,7 +4560,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "LinkedMap<dynamic, dynamic>"
+    "LinkedMap"
 ]
 -----------------------------------
 Test: Map<dynamic, dynamic> definition formatting body
@@ -5227,7 +5227,7 @@
             {
                 "style": "background-color: thistle; color: rgb(136, 19, 145); margin-right: -13px"
             },
-            "TestGenericClass<dynamic, dynamic>: "
+            "TestGenericClass: "
         ],
         [
             "span",
@@ -7063,7 +7063,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "TestGenericClass<int, List<dynamic>>"
+    "TestGenericClass"
 ]
 -----------------------------------
 Test: TestGenericClass definition formatting body
@@ -7203,7 +7203,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "Instance of 'TestGenericClass<ExampleJSClass<any>, int>'"
+    "Instance of 'TestGenericClass<ExampleJSClass<String>, int>'"
 ]
 -----------------------------------
 Test: TestGenericClassJSInterop instance body
@@ -7300,7 +7300,7 @@
     {
         "style": "background-color: #d9edf7;color: black"
     },
-    "TestGenericClass<ExampleJSClass<any>, int>"
+    "TestGenericClass"
 ]
 -----------------------------------
 Test: TestGenericClassJSInterop definition formatting body
diff --git a/tests/dartdevc/js_interop_non_external_lib.dart b/tests/dartdevc/js_interop_non_external_lib.dart
index ef6eaa9..4066b98 100644
--- a/tests/dartdevc/js_interop_non_external_lib.dart
+++ b/tests/dartdevc/js_interop_non_external_lib.dart
@@ -12,6 +12,12 @@
 import 'package:expect/expect.dart';
 import 'package:js/js.dart';
 
+late List topLevelList;
+
+void topLevelListAppend(item) {
+  topLevelList.add(item);
+}
+
 @JS('JSClass')
 class OtherJSClass<T extends num> {
   external OtherJSClass.cons(T t);
@@ -20,6 +26,7 @@
     Expect.type<T>(t);
     Expect.notType<T>('');
     field = 'unnamed';
+    topLevelList = <T>[t];
     return OtherJSClass.cons(t);
   }
 
@@ -28,6 +35,7 @@
     Expect.type<T>(t);
     Expect.notType<T>('');
     field = 'named';
+    topLevelList = <T>[t];
     return OtherJSClass.cons(t);
   }
 
diff --git a/tests/dartdevc/js_interop_non_external_test.dart b/tests/dartdevc/js_interop_non_external_test.dart
index 25cf658..e827810 100644
--- a/tests/dartdevc/js_interop_non_external_test.dart
+++ b/tests/dartdevc/js_interop_non_external_test.dart
@@ -13,6 +13,8 @@
 
 import 'js_interop_non_external_lib.dart';
 
+class _Class {}
+
 @JS()
 external dynamic eval(String code);
 
@@ -138,8 +140,12 @@
   expect(OtherJSClass.field, 'modified');
 
   OtherJSClass<int>(0);
+  Expect.throws<TypeError>(() => topLevelListAppend(_Class()));
+  topLevelListAppend(42);
   expect(OtherJSClass.field, 'unnamed');
   OtherJSClass.named(0);
+  Expect.throws<TypeError>(() => topLevelListAppend(_Class()));
+  topLevelListAppend(42);
   expect(OtherJSClass.field, 'named');
   OtherJSClass<double>.redirecting(0.1);
   expect(OtherJSClass.field, 'unnamed');
diff --git a/tests/language/factory/factory_const_test.dart b/tests/language/factory/factory_const_test.dart
new file mode 100644
index 0000000..5bf403e
--- /dev/null
+++ b/tests/language/factory/factory_const_test.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Tests that generic constants can be constructed from a named factory.
+
+import "package:expect/expect.dart";
+
+class Optional<T> {
+  final T? value;
+  const Optional.absent() : value = null;
+}
+
+class Foo {
+  Optional<int> value;
+  Foo._(this.value);
+  factory Foo.named() {
+    return Foo._(const Optional.absent());
+  }
+}
+
+void main() {
+  var foo = Foo.named();
+  Expect.equals(foo.value, const Optional<int>.absent());
+  Expect.equals(foo.value.value, null);
+}
diff --git a/tests/language/generic_methods/generic_static_tearoffs_test.dart b/tests/language/generic_methods/generic_static_tearoffs_test.dart
new file mode 100644
index 0000000..c120dff
--- /dev/null
+++ b/tests/language/generic_methods/generic_static_tearoffs_test.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import "package:expect/expect.dart";
+
+class Foo<T> {
+  static final create = <U>(e) => C<U>.filled(e);
+}
+
+class C<T> implements I<T>, J<T> {
+  final T value;
+
+  C(this.value);
+
+  factory C.filled(T fill) {
+    return C<T>(fill);
+  }
+}
+
+abstract mixin class I<E> {
+  I();
+}
+
+mixin class J<E> {
+  J();
+}
+
+void main() {
+  var foo1 = Foo.create<int>(42);
+  Expect.equals(foo1.value, 42);
+  Expect.type<C<int>>(foo1);
+
+  var j = J<J<bool>>();
+
+  var foo2 = Foo.create(j);
+  Expect.equals(foo2.value, j);
+  Expect.type<C<dynamic>>(foo2);
+
+  var foo3 = Foo.create<J<J<bool>>>(j);
+  Expect.equals(foo3.value, j);
+  Expect.type<C<J<J<bool>>>>(foo3);
+}
diff --git a/tests/language/mixin/covariant_mixin_stub_test.dart b/tests/language/mixin/covariant_mixin_stub_test.dart
new file mode 100644
index 0000000..36e08e2
--- /dev/null
+++ b/tests/language/mixin/covariant_mixin_stub_test.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Tests that implicit covariant type checks in super calls from generic mixin
+// forwarding stubs behave properly when type parameters are shuffled.
+
+import "package:expect/expect.dart";
+
+abstract class ClassWithGenericMix<TypeArgA1, TypeArgA2, TypeArgA3>
+    extends ClassWithCovariantSuperCall
+    with GenericMixWithCovariantOverride<TypeArgA1, TypeArgA3, TypeArgA2> {
+  const ClassWithGenericMix({super.key});
+}
+
+mixin Mix<TypeArgB1, TypeArgB2, TypeArgB3> on Object {}
+
+class ClassWithCovariantSuperCall extends ConstWrapper {
+  const ClassWithCovariantSuperCall({super.key});
+
+  void update(covariant Object obj) {
+    print('updated!');
+  }
+}
+
+mixin GenericMixWithCovariantOverride<TypeArgC1, TypeArgC2, TypeArgC3>
+    on ClassWithCovariantSuperCall {
+  @override
+  void update(Mix<TypeArgC3, TypeArgC1, TypeArgC2> obj);
+}
+
+class ConstWrapper {
+  final dynamic key;
+  const ConstWrapper({this.key});
+}
+
+class ClassWithGenericMixImpl extends ClassWithGenericMix<int, String, bool> {
+  final ConstWrapper element;
+
+  const ClassWithGenericMixImpl({
+    required this.element,
+  });
+}
+
+class ClassWithMix extends Object with Mix<String, int, bool> {}
+
+void main() {
+  var wrapper = const ConstWrapper();
+  var impl = ClassWithGenericMixImpl(element: wrapper);
+  var mixedIn = ClassWithMix();
+  impl.update(mixedIn);
+
+  Expect.equals(impl.element, wrapper);
+}