[dart:js_util] Handle type parameters in export/mocks.

Several changes are made:

- createDartExport now does not export methods that define type parameters.
- createStaticInteropMock adds conformance checks to make sure implementing
members can handle all possible values of a type parameter in an interop
member. An error is added to reduce confusion around this.
- Export creator now uses dart:js_interop_unsafe for a lot of its lowering
as the dart:js_util equivalents are buggy when it comes to calling exported
functions in JS with JS types.
- Small code changes are added to backends to handle the above changes.

Change-Id: Ie3b6b157930537267f270b60373b2b17e0a14344
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/316141
Reviewed-by: Joshua Litt <joshualitt@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index fdab0d0..b738057 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -7803,7 +7803,7 @@
             name)> templateJsInteropExportDisallowedMember = const Template<
         Message Function(String name)>(
     problemMessageTemplate:
-        r"""Member '#name' is not a concrete instance member, and therefore can't be exported.""",
+        r"""Member '#name' is not a concrete instance member or declares type parameters, and therefore can't be exported.""",
     correctionMessageTemplate:
         r"""Remove the `@JSExport` annotation from the member, and use an instance member to call this member instead.""",
     withArguments: _withArgumentsJsInteropExportDisallowedMember);
@@ -7820,7 +7820,7 @@
   name = demangleMixinApplicationName(name);
   return new Message(codeJsInteropExportDisallowedMember,
       problemMessage:
-          """Member '${name}' is not a concrete instance member, and therefore can't be exported.""",
+          """Member '${name}' is not a concrete instance member or declares type parameters, and therefore can't be exported.""",
       correctionMessage: """Remove the `@JSExport` annotation from the member, and use an instance member to call this member instead.""",
       arguments: {'name': name});
 }
diff --git a/pkg/_js_interop_checks/lib/src/transformations/export_checker.dart b/pkg/_js_interop_checks/lib/src/transformations/export_checker.dart
index a838f43..8492c41 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/export_checker.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/export_checker.dart
@@ -258,13 +258,15 @@
 }
 
 extension ProcedureExtension on Procedure {
-  // We only care about concrete instance procedures.
+  // We only care about concrete instance procedures that don't define their own
+  // type parameters.
   bool get exportable =>
       !isAbstract &&
       !isStatic &&
       !isExtensionMember &&
       !isFactory &&
       !isExternal &&
+      function.typeParameters.isEmpty &&
       kind != ProcedureKind.Operator;
 }
 
diff --git a/pkg/_js_interop_checks/lib/src/transformations/export_creator.dart b/pkg/_js_interop_checks/lib/src/transformations/export_creator.dart
index a6f8d6c..f6f7545 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/export_creator.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/export_creator.dart
@@ -20,68 +20,76 @@
 import 'static_interop_mock_validator.dart';
 
 class ExportCreator extends Transformer {
-  final Procedure _allowInterop;
+  final Procedure _callMethodVarArgs;
   final Procedure _createDartExport;
   final Procedure _createStaticInteropMock;
   final JsInteropDiagnosticReporter _diagnosticReporter;
   final ExportChecker _exportChecker;
-  final InterfaceType _functionType;
+  final Procedure _functionToJS;
   final Procedure _getProperty;
-  final Procedure _globalThis;
-  final InterfaceType _objectType;
+  final Procedure _globalJSObject;
+  final Class _jsAny;
+  final Class _jsObject;
   final Procedure _setProperty;
+  final Procedure _stringToJS;
   final StaticInteropMockValidator _staticInteropMockValidator;
   final TypeEnvironment _typeEnvironment;
 
   ExportCreator(
       this._typeEnvironment, this._diagnosticReporter, this._exportChecker)
-      : _allowInterop = _typeEnvironment.coreTypes.index
-            .getTopLevelProcedure('dart:js_util', 'allowInterop'),
+      : _callMethodVarArgs = _typeEnvironment.coreTypes.index
+            .getTopLevelProcedure('dart:js_interop_unsafe',
+                'JSObjectUtilExtension|callMethodVarArgs'),
         _createDartExport = _typeEnvironment.coreTypes.index
             .getTopLevelProcedure('dart:js_util', 'createDartExport'),
         _createStaticInteropMock = _typeEnvironment.coreTypes.index
             .getTopLevelProcedure('dart:js_util', 'createStaticInteropMock'),
-        _functionType = _typeEnvironment.coreTypes.functionNonNullableRawType,
-        _getProperty = (_typeEnvironment.coreTypes.index.tryGetTopLevelMember(
-                'dart:js_util', '_getPropertyTrustType') ??
-            _typeEnvironment.coreTypes.index.getTopLevelProcedure(
-                'dart:js_util', 'getProperty')) as Procedure,
-        _globalThis = _typeEnvironment.coreTypes.index
-            .getTopLevelProcedure('dart:js_util', 'get:globalThis'),
-        _objectType = _typeEnvironment.coreTypes.objectNonNullableRawType,
-        _setProperty = (_typeEnvironment.coreTypes.index.tryGetTopLevelMember(
-                'dart:js_util', '_setPropertyUnchecked') ??
-            _typeEnvironment.coreTypes.index.getTopLevelProcedure(
-                'dart:js_util', 'setProperty')) as Procedure,
+        _functionToJS = _typeEnvironment.coreTypes.index.getTopLevelProcedure(
+            'dart:js_interop', 'FunctionToJSExportedDartFunction|get#toJS'),
+        _getProperty = _typeEnvironment.coreTypes.index.getTopLevelProcedure(
+            'dart:js_interop_unsafe', 'JSObjectUtilExtension|[]'),
+        _globalJSObject = _typeEnvironment.coreTypes.index
+            .getTopLevelProcedure('dart:js_interop', 'get:globalJSObject'),
+        _jsAny = _typeEnvironment.coreTypes.index
+            .getClass('dart:_js_types', 'JSAny'),
+        _jsObject = _typeEnvironment.coreTypes.index
+            .getClass('dart:_js_types', 'JSObject'),
+        _setProperty = _typeEnvironment.coreTypes.index.getTopLevelProcedure(
+            'dart:js_interop_unsafe', 'JSObjectUtilExtension|[]='),
+        _stringToJS = _typeEnvironment.coreTypes.index.getTopLevelProcedure(
+            'dart:js_interop', 'StringToJSString|get#toJS'),
         _staticInteropMockValidator = StaticInteropMockValidator(
             _diagnosticReporter, _exportChecker, _typeEnvironment);
 
   @override
   TreeNode visitStaticInvocation(StaticInvocation node) {
     if (node.target == _createDartExport) {
-      var typeArguments = node.arguments.types;
+      final typeArguments = node.arguments.types;
       assert(typeArguments.length == 1);
       if (_verifyExportable(node, typeArguments[0])) {
         return _createExport(node, typeArguments[0] as InterfaceType);
       }
     } else if (node.target == _createStaticInteropMock) {
-      var typeArguments = node.arguments.types;
+      final typeArguments = node.arguments.types;
       assert(typeArguments.length == 2);
-      var staticInteropType = typeArguments[0];
-      var dartType = typeArguments[1];
+      final staticInteropType = typeArguments[0];
+      final dartType = typeArguments[1];
 
-      var exportable = _verifyExportable(node, dartType);
-      var staticInteropTypeArgumentCorrect = _staticInteropMockValidator
+      final exportable = _verifyExportable(node, dartType);
+      final staticInteropTypeArgumentCorrect = _staticInteropMockValidator
           .validateStaticInteropTypeArgument(node, staticInteropType);
+      final dartTypeArgumentCorrect =
+          _staticInteropMockValidator.validateDartTypeArgument(node, dartType);
       if (exportable &&
           staticInteropTypeArgumentCorrect &&
+          dartTypeArgumentCorrect &&
           _staticInteropMockValidator.validateCreateStaticInteropMock(
               node,
               (staticInteropType as InterfaceType).classNode,
               (dartType as InterfaceType).classNode)) {
-        var arguments = node.arguments.positional;
+        final arguments = node.arguments.positional;
         assert(arguments.length == 1 || arguments.length == 2);
-        var proto = arguments.length == 2 ? arguments[1] : null;
+        final proto = arguments.length == 2 ? arguments[1] : null;
 
         return _createExport(node, dartType, staticInteropType, proto);
       }
@@ -164,6 +172,48 @@
   /// and returns it.
   TreeNode _createExport(StaticInvocation node, InterfaceType dartType,
       [DartType? returnType, Expression? proto]) {
+    Expression asJSObject(Expression object, [bool nullable = false]) =>
+        AsExpression(
+            object,
+            InterfaceType(_jsObject,
+                nullable ? Nullability.nullable : Nullability.nonNullable))
+          ..fileOffset = node.fileOffset;
+
+    Expression toJSString(String string) =>
+        StaticInvocation(_stringToJS, Arguments([StringLiteral(string)]))
+          ..fileOffset = node.fileOffset;
+
+    StaticInvocation callMethodVarArgs(Expression jsObject, String methodName,
+        List<Expression> args, DartType returnType) {
+      // `jsObject.callMethodVarArgs(methodName.toJS, args)`
+      return StaticInvocation(
+          _callMethodVarArgs,
+          Arguments([
+            jsObject,
+            toJSString(methodName),
+            ListLiteral(args,
+                typeArgument: InterfaceType(_jsAny, Nullability.nullable))
+          ], types: [
+            returnType
+          ]))
+        ..fileOffset = node.fileOffset;
+    }
+
+    // Get the global 'Object' property.
+    Expression getObjectProperty() => asJSObject(StaticInvocation(_getProperty,
+        Arguments([StaticGet(_globalJSObject), toJSString('Object')])))
+      ..fileOffset = node.fileOffset;
+
+    // Get a fresh object literal, using the proto to create it if one was
+    // given.
+    StaticInvocation getLiteral([Expression? proto]) {
+      return callMethodVarArgs(
+          getObjectProperty(),
+          'create',
+          [asJSObject(proto ?? NullLiteral(), true)],
+          InterfaceType(_jsObject, Nullability.nonNullable));
+    }
+
     var exportMap =
         _exportChecker.exportClassToMemberMap[dartType.classNode.reference]!;
 
@@ -178,23 +228,9 @@
       ..parent = node.parent;
     block.add(dartInstance);
 
-    // Get the global 'Object' property.
-    StaticInvocation getObjectProperty() => StaticInvocation(
-        _getProperty,
-        Arguments([StaticGet(_globalThis), StringLiteral('Object')],
-            types: [_objectType]));
-
-    // Get a fresh object literal, using the proto to create it if one was
-    // given.
-    StaticInvocation getLiteral([Expression? proto]) {
-      return _callMethod(getObjectProperty(), StringLiteral('create'),
-          [proto ?? NullLiteral()], _objectType);
-    }
-
     var jsExporter = VariableDeclaration('#jsExporter',
-        initializer: AsExpression(getLiteral(proto), returnType)
-          ..fileOffset = node.fileOffset,
-        type: returnType,
+        initializer: getLiteral(proto),
+        type: InterfaceType(_jsObject, Nullability.nonNullable),
         isSynthesized: true)
       ..fileOffset = node.fileOffset
       ..parent = node.parent;
@@ -202,13 +238,11 @@
 
     for (var exportName in exportMap.keys) {
       var exports = exportMap[exportName]!;
-      ExpressionStatement setProperty(VariableGet jsObject, String propertyName,
-          StaticInvocation wrappedValue) {
-        // `setProperty(jsObject, propertyName, wrappedValue)`
-        return ExpressionStatement(StaticInvocation(
-            _setProperty,
-            Arguments([jsObject, StringLiteral(propertyName), wrappedValue],
-                types: [_objectType])))
+      ExpressionStatement setProperty(
+          VariableGet jsObject, String propertyName, StaticInvocation jsValue) {
+        // `jsObject[propertyName.toJS] = jsValue`
+        return ExpressionStatement(StaticInvocation(_setProperty,
+            Arguments([jsObject, toJSString(propertyName), jsValue])))
           ..fileOffset = node.fileOffset
           ..parent = node.parent;
       }
@@ -217,19 +251,19 @@
       // With methods, there's only one export per export name.
       if (firstExport is Procedure &&
           firstExport.kind == ProcedureKind.Method) {
-        // `setProperty(jsMock, jsName, allowInterop(dartMock.tearoffMethod))`
+        // `jsExport[jsName.toJS] = dartMock.tearoffMethod.toJS`
         block.add(setProperty(
             VariableGet(jsExporter),
             exportName,
             StaticInvocation(
-                _allowInterop,
+                _functionToJS,
                 Arguments([
                   InstanceTearOff(InstanceAccessKind.Instance,
                       VariableGet(dartInstance), firstExport.name,
                       interfaceTarget: firstExport,
-                      resultType: firstExport.getterType)
-                ], types: [
-                  _functionType
+                      resultType: _staticInteropMockValidator
+                          .typeParameterResolver
+                          .resolve(firstExport.getterType))
                 ]))));
       } else {
         // Create the mapping from `get` and `set` to their `dartInstance` calls
@@ -242,17 +276,17 @@
         // The AST code looks like:
         //
         // ```
-        // setProperty(getSetMap, 'get', allowInterop(() {
+        // getSetMap['get'.toJS] = () {
         //   return dartInstance.getter;
-        // }));
+        // }.toJS;
         // ```
         //
         // in the case of a getter and:
         //
         // ```
-        // setProperty(getSetMap, 'set', allowInterop((val) {
+        // getSetMap['set'.toJS] = (val) {
         //  dartInstance.setter = val;
-        // }));
+        // }.toJS;
         // ```
         //
         // in the case of a setter.
@@ -260,7 +294,9 @@
         // A new map VariableDeclaration is created and added to the block of
         // statements for each export name.
         var getSetMap = VariableDeclaration('#${exportName}Mapping',
-            initializer: getLiteral(), type: _objectType, isSynthesized: true)
+            initializer: getLiteral(),
+            type: InterfaceType(_jsObject, Nullability.nonNullable),
+            isSynthesized: true)
           ..fileOffset = node.fileOffset
           ..parent = node.parent;
         block.add(getSetMap);
@@ -272,28 +308,30 @@
               VariableGet(getSetMap),
               'get',
               StaticInvocation(
-                  _allowInterop,
+                  _functionToJS,
                   Arguments([
                     FunctionExpression(FunctionNode(ReturnStatement(InstanceGet(
                         InstanceAccessKind.Instance,
                         VariableGet(dartInstance),
                         getter.name,
                         interfaceTarget: getter,
-                        resultType: getter.getterType))))
-                  ], types: [
-                    _functionType
+                        resultType: _staticInteropMockValidator
+                            .typeParameterResolver
+                            .resolve(getter.getterType)))))
                   ]))));
         }
         if (setter != null) {
           var setterParameter = VariableDeclaration('#val',
-              type: setter.setterType, isSynthesized: true)
+              type: _staticInteropMockValidator.typeParameterResolver
+                  .resolve(setter.setterType),
+              isSynthesized: true)
             ..fileOffset = node.fileOffset
             ..parent = node.parent;
           block.add(setProperty(
               VariableGet(getSetMap),
               'set',
               StaticInvocation(
-                  _allowInterop,
+                  _functionToJS,
                   Arguments([
                     FunctionExpression(FunctionNode(
                         ExpressionStatement(InstanceSet(
@@ -303,19 +341,17 @@
                             VariableGet(setterParameter),
                             interfaceTarget: setter)),
                         positionalParameters: [setterParameter]))
-                  ], types: [
-                    _functionType
                   ]))));
         }
         // Call `Object.defineProperty` to define the export name with the
         // 'get' and/or 'set' mapping. This allows us to treat get/set
         // semantics as methods.
-        block.add(ExpressionStatement(_callMethod(
+        block.add(ExpressionStatement(callMethodVarArgs(
             getObjectProperty(),
-            StringLiteral('defineProperty'),
+            'defineProperty',
             [
               VariableGet(jsExporter),
-              StringLiteral(exportName),
+              toJSString(exportName),
               VariableGet(getSetMap)
             ],
             VoidType()))
@@ -324,7 +360,8 @@
       }
     }
 
-    block.add(ReturnStatement(VariableGet(jsExporter)));
+    block.add(ReturnStatement(AsExpression(VariableGet(jsExporter), returnType)
+      ..fileOffset = node.fileOffset));
     // Return a call to evaluate the entire block of code and return the JS mock
     // that was created.
     return FunctionInvocation(
@@ -335,24 +372,4 @@
       ..fileOffset = node.fileOffset
       ..parent = node.parent;
   }
-
-  // Optimize `callMethod` calls if possible.
-  StaticInvocation _callMethod(Expression object, StringLiteral methodName,
-      List<Expression> args, DartType returnType) {
-    var index = args.length;
-    var callMethodOptimized = _typeEnvironment.coreTypes.index
-        .tryGetTopLevelMember(
-            'dart:js_util', '_callMethodUncheckedTrustType$index');
-    if (callMethodOptimized == null) {
-      var callMethod = _typeEnvironment.coreTypes.index
-          .getTopLevelProcedure('dart:js_util', 'callMethod');
-      return StaticInvocation(
-          callMethod,
-          Arguments([object, methodName, ListLiteral(args)],
-              types: [returnType]));
-    } else {
-      return StaticInvocation(callMethodOptimized as Procedure,
-          Arguments([object, methodName, ...args], types: [returnType]));
-    }
-  }
 }
diff --git a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
index 7ab6395..71193d7 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
@@ -967,4 +967,15 @@
 
   bool isNonLiteralConstructor(Procedure node) =>
       _isStaticInteropConstructor(node, literal: false);
+
+  bool isStaticInteropType(DartType type) {
+    if (type is InterfaceType) {
+      return hasStaticInteropAnnotation(type.classNode);
+    } else if (type is ExtensionType) {
+      return isInteropInlineClass(type.extensionTypeDeclaration);
+    } else if (type is TypeParameterType) {
+      return isStaticInteropType(type.bound);
+    }
+    return false;
+  }
 }
diff --git a/pkg/_js_interop_checks/lib/src/transformations/static_interop_mock_validator.dart b/pkg/_js_interop_checks/lib/src/transformations/static_interop_mock_validator.dart
index ba28564..c0a11cd 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/static_interop_mock_validator.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/static_interop_mock_validator.dart
@@ -12,8 +12,11 @@
     show JsInteropDiagnosticReporter;
 import 'package:_js_interop_checks/src/js_interop.dart' as js_interop;
 import 'package:front_end/src/fasta/fasta_codes.dart'
-    show templateJsInteropStaticInteropMockNotStaticInteropType;
+    show
+        templateJsInteropStaticInteropMockNotStaticInteropType,
+        templateJsInteropStaticInteropMockTypeParametersNotAllowed;
 import 'package:kernel/ast.dart';
+import 'package:kernel/src/replacement_visitor.dart';
 import 'package:kernel/type_environment.dart';
 
 import 'export_checker.dart';
@@ -26,9 +29,10 @@
   // members and those members' export names.
   final Map<Class, Map<String, Set<ExtensionMemberDescriptor>>>
       _staticInteropExportNameToDescriptorMap = {};
-  final TypeEnvironment _typeEnvironment;
   late final Map<Reference, Set<Extension>>
       _staticInteropClassesWithExtensions = _computeStaticInteropExtensionMap();
+  final TypeEnvironment _typeEnvironment;
+  final TypeParameterResolver typeParameterResolver = TypeParameterResolver();
   StaticInteropMockValidator(
       this._diagnosticReporter, this._exportChecker, this._typeEnvironment);
 
@@ -43,6 +47,48 @@
           node.name.text.length,
           node.location?.file);
       return false;
+    } else {
+      return _validateNoTypeParametersInTypeArgument(node, staticInteropType);
+    }
+  }
+
+  bool validateDartTypeArgument(StaticInvocation node, DartType dartType) =>
+      _validateNoTypeParametersInTypeArgument(node, dartType);
+
+  /// Validate that [type] argument does not pass type arguments beyond the
+  /// bounds.
+  ///
+  /// [node] is the createStaticInteropMock call that [type] occurs in.
+  ///
+  /// We do this check because reasoning about type arguments beyond their
+  /// bounds is complex and requires substitution in multiple places. It gets
+  /// even more complex when you have to account for extensions and supertypes
+  /// having their own type parameters too. In order to properly handle all
+  /// these cases, we'd have to keep constraints around and see what extensions
+  /// apply and what extensions don't. This may be simpler to do for inline
+  /// classes/extension types, as all the members are in the class and not in an
+  /// extension, but for now, we require that users must implement members with
+  /// type parameters based on their bounds.
+  ///
+  /// Returns whether the validation passed.
+  bool _validateNoTypeParametersInTypeArgument(
+      StaticInvocation node, DartType type) {
+    if (type is InterfaceType) {
+      final typeArguments = type.typeArguments;
+      final typeParams = type.classNode.typeParameters;
+      for (var i = 0; i < typeParams.length; i++) {
+        final arg = typeArguments[i];
+        // Uninstantiated type parameters are replaced with dynamic by the CFE.
+        if (arg is! DynamicType && arg != typeParams[i].bound) {
+          _diagnosticReporter.report(
+              templateJsInteropStaticInteropMockTypeParametersNotAllowed
+                  .withArguments(type, true),
+              node.fileOffset,
+              node.name.text.length,
+              node.location?.file);
+          return false;
+        }
+      }
     }
     return true;
   }
@@ -72,6 +118,7 @@
             type = FunctionType([type], VoidType(), Nullability.nonNullable);
             name += '=';
           }
+          type = typeParameterResolver.resolve(type);
           return '$extension.$name ($type)';
         }).toList()
           ..sort();
@@ -161,15 +208,17 @@
       return interopMember.function.positionalParameters[1].type;
     } else {
       assert(interopDescriptor.isMethod);
-      var interopMemberType =
-          interopMember.function.computeFunctionType(Nullability.nonNullable);
+      // We don't care about the method's own type parameters to determine
+      // subtyping. We simply substitute them by their bounds, if any.
+      final interopMemberType = interopMember.function
+          .computeThisFunctionType(Nullability.nonNullable)
+          .withoutTypeParameters;
       // Ignore the first argument `this` in the generated procedure.
       return FunctionType(
           interopMemberType.positionalParameters.skip(1).toList(),
           interopMemberType.returnType,
           interopMemberType.declaredNullability,
           namedParameters: interopMemberType.namedParameters,
-          typeParameters: interopMemberType.typeParameters,
           requiredParameterCount: interopMemberType.requiredParameterCount - 1);
     }
   }
@@ -190,8 +239,12 @@
     }
 
     bool isSubtypeOf(DartType dartType, DartType interopType) {
+      // Remove and substitute type parameters with their bounds/instantiated
+      // type arguments.
       return _typeEnvironment.isSubtypeOf(
-          dartType, interopType, SubtypeCheckMode.withNullabilities);
+          typeParameterResolver.resolve(dartType),
+          typeParameterResolver.resolve(interopType),
+          SubtypeCheckMode.withNullabilities);
     }
 
     var interopType = _getTypeOfDescriptor(interopDescriptor);
@@ -208,7 +261,8 @@
       if (!isSubtypeOf(
           (dartMember as Procedure)
               .function
-              .computeFunctionType(Nullability.nonNullable),
+              .computeThisFunctionType(Nullability.nonNullable)
+              .withoutTypeParameters,
           interopType)) {
         return false;
       }
@@ -302,3 +356,18 @@
         exportNameToDescriptors;
   }
 }
+
+/// Visitor that replaces each type parameter with its bound.
+///
+/// We use this to determine conformance of interop methods that use type
+/// parameters.
+class TypeParameterResolver extends ReplacementVisitor {
+  @override
+  DartType? visitTypeParameterType(TypeParameterType node, int variance) {
+    return node.resolveTypeParameterType;
+  }
+
+  DartType resolve(DartType node) {
+    return node.accept1(this, Variance.unrelated) ?? node;
+  }
+}
diff --git a/pkg/compiler/lib/src/kernel/dart2js_target.dart b/pkg/compiler/lib/src/kernel/dart2js_target.dart
index 5b48751..e19af8c 100644
--- a/pkg/compiler/lib/src/kernel/dart2js_target.dart
+++ b/pkg/compiler/lib/src/kernel/dart2js_target.dart
@@ -109,9 +109,11 @@
         'dart:_foreign_helper',
         'dart:_interceptors',
         'dart:_js_helper',
+        'dart:_js_types',
         'dart:_late_helper',
         'dart:js',
         'dart:js_interop',
+        'dart:js_interop_unsafe',
         'dart:js_util',
         'dart:typed_data',
       ];
@@ -256,6 +258,8 @@
   'dart:_interceptors',
   'dart:_js_helper',
   'dart:_late_helper',
+  // Needed since dart:js_util methods like createDartExport use this.
+  'dart:js_interop_unsafe',
   'dart:js_util'
 ];
 
@@ -294,8 +298,8 @@
     'dart:isolate',
     'dart:js',
     'dart:js_interop',
-    'dart:js_util',
     'dart:js_interop_unsafe',
+    'dart:js_util',
     'dart:math',
     'dart:svg',
     'dart:typed_data',
diff --git a/pkg/compiler/test/impact/data/jsinterop.dart b/pkg/compiler/test/impact/data/jsinterop.dart
index ddd2b24..7a0f3f3 100644
--- a/pkg/compiler/test/impact/data/jsinterop.dart
+++ b/pkg/compiler/test/impact/data/jsinterop.dart
@@ -32,6 +32,28 @@
 
   /*member: JsInteropClass.method:type=[
     native:GenericClass<dynamic>,
+    native:JSAny,
+    native:JSArray,
+    native:JSArrayBuffer,
+    native:JSBoolean,
+    native:JSBoxedDartObject,
+    native:JSDataView,
+    native:JSExportedDartFunction,
+    native:JSFloat32Array,
+    native:JSFloat64Array,
+    native:JSFunction,
+    native:JSInt16Array,
+    native:JSInt32Array,
+    native:JSInt8Array,
+    native:JSNumber,
+    native:JSObject,
+    native:JSPromise,
+    native:JSString,
+    native:JSTypedArray,
+    native:JSUint16Array,
+    native:JSUint32Array,
+    native:JSUint8Array,
+    native:JSUint8ClampedArray,
     native:JsInteropClass]*/
   @JS()
   external double method();
diff --git a/pkg/compiler/test/impact/data/jsinterop_setter1.dart b/pkg/compiler/test/impact/data/jsinterop_setter1.dart
index eb917cb..8c61da5 100644
--- a/pkg/compiler/test/impact/data/jsinterop_setter1.dart
+++ b/pkg/compiler/test/impact/data/jsinterop_setter1.dart
@@ -55,6 +55,28 @@
   native:DomError,
   native:DomException,
   native:ErrorEvent,
+  native:JSAny,
+  native:JSArray,
+  native:JSArrayBuffer,
+  native:JSBoolean,
+  native:JSBoxedDartObject,
+  native:JSDataView,
+  native:JSExportedDartFunction,
+  native:JSFloat32Array,
+  native:JSFloat64Array,
+  native:JSFunction,
+  native:JSInt16Array,
+  native:JSInt32Array,
+  native:JSInt8Array,
+  native:JSNumber,
+  native:JSObject,
+  native:JSPromise,
+  native:JSString,
+  native:JSTypedArray,
+  native:JSUint16Array,
+  native:JSUint32Array,
+  native:JSUint8Array,
+  native:JSUint8ClampedArray,
   native:MediaError,
   native:NavigatorUserMediaError,
   native:OverconstrainedError,
diff --git a/pkg/compiler/test/impact/data/jsinterop_setter2.dart b/pkg/compiler/test/impact/data/jsinterop_setter2.dart
index 7f5b60b..742bc0f 100644
--- a/pkg/compiler/test/impact/data/jsinterop_setter2.dart
+++ b/pkg/compiler/test/impact/data/jsinterop_setter2.dart
@@ -62,6 +62,28 @@
   native:DomException,
   native:ErrorEvent,
   native:File,
+  native:JSAny,
+  native:JSArray,
+  native:JSArrayBuffer,
+  native:JSBoolean,
+  native:JSBoxedDartObject,
+  native:JSDataView,
+  native:JSExportedDartFunction,
+  native:JSFloat32Array,
+  native:JSFloat64Array,
+  native:JSFunction,
+  native:JSInt16Array,
+  native:JSInt32Array,
+  native:JSInt8Array,
+  native:JSNumber,
+  native:JSObject,
+  native:JSPromise,
+  native:JSString,
+  native:JSTypedArray,
+  native:JSUint16Array,
+  native:JSUint32Array,
+  native:JSUint8Array,
+  native:JSUint8ClampedArray,
   native:MediaError,
   native:NavigatorUserMediaError,
   native:OverconstrainedError,
diff --git a/pkg/dart2wasm/lib/js/callback_specializer.dart b/pkg/dart2wasm/lib/js/callback_specializer.dart
index 448c5f6..3f39cd4 100644
--- a/pkg/dart2wasm/lib/js/callback_specializer.dart
+++ b/pkg/dart2wasm/lib/js/callback_specializer.dart
@@ -1,7 +1,8 @@
 // Copyright (c) 2023, 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:_js_interop_checks/src/transformations/js_util_optimizer.dart'
+    show InlineExtensionIndex;
 import 'package:dart2wasm/js/method_collector.dart';
 import 'package:dart2wasm/js/util.dart';
 import 'package:kernel/ast.dart';
@@ -12,9 +13,10 @@
   final StatefulStaticTypeContext _staticTypeContext;
   final MethodCollector _methodCollector;
   final CoreTypesUtil _util;
+  final InlineExtensionIndex _inlineExtensionIndex;
 
-  CallbackSpecializer(
-      this._staticTypeContext, this._util, this._methodCollector) {}
+  CallbackSpecializer(this._staticTypeContext, this._util,
+      this._methodCollector, this._inlineExtensionIndex) {}
 
   bool _needsArgumentsLength(FunctionType type) =>
       type.requiredParameterCount < type.positionalParameters.length;
@@ -30,7 +32,8 @@
       DartType callbackParameterType = function.positionalParameters[i];
       Expression expression;
       VariableGet v = VariableGet(positionalParameters[i]);
-      if (callbackParameterType.isStaticInteropType && boxExternRef) {
+      if (_inlineExtensionIndex.isStaticInteropType(callbackParameterType) &&
+          boxExternRef) {
         expression = _createJSValue(v);
       } else {
         expression = AsExpression(
diff --git a/pkg/dart2wasm/lib/js/interop_specializer.dart b/pkg/dart2wasm/lib/js/interop_specializer.dart
index 889aeeb..bb1acc1 100644
--- a/pkg/dart2wasm/lib/js/interop_specializer.dart
+++ b/pkg/dart2wasm/lib/js/interop_specializer.dart
@@ -330,7 +330,8 @@
             _util.jsifyTarget(expr.getStaticType(_staticTypeContext)),
             Arguments([expr])))
         .toList();
-    assert(function.returnType.isStaticInteropType);
+    assert(
+        factory._inlineExtensionIndex.isStaticInteropType(function.returnType));
     return invokeOneArg(_util.jsValueBoxTarget,
         StaticInvocation(interopProcedure, Arguments(positionalArgs)));
   }
@@ -345,12 +346,8 @@
   late String _libraryJSString;
   late final InlineExtensionIndex _inlineExtensionIndex;
 
-  InteropSpecializerFactory(
-      this._staticTypeContext, this._util, this._methodCollector) {
-    final typeEnvironment = _staticTypeContext.typeEnvironment;
-    _inlineExtensionIndex =
-        InlineExtensionIndex(typeEnvironment.coreTypes, typeEnvironment);
-  }
+  InteropSpecializerFactory(this._staticTypeContext, this._util,
+      this._methodCollector, this._inlineExtensionIndex);
 
   void enterLibrary(Library library) {
     _libraryJSString = getJSName(library);
diff --git a/pkg/dart2wasm/lib/js/interop_transformer.dart b/pkg/dart2wasm/lib/js/interop_transformer.dart
index a0f8c90..187144c 100644
--- a/pkg/dart2wasm/lib/js/interop_transformer.dart
+++ b/pkg/dart2wasm/lib/js/interop_transformer.dart
@@ -2,6 +2,8 @@
 // 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:_js_interop_checks/src/transformations/js_util_optimizer.dart'
+    show InlineExtensionIndex;
 import 'package:dart2wasm/js/callback_specializer.dart';
 import 'package:dart2wasm/js/inline_expander.dart';
 import 'package:dart2wasm/js/interop_specializer.dart';
@@ -31,22 +33,28 @@
   final MethodCollector _methodCollector;
   final CoreTypesUtil _util;
 
-  InteropTransformer._(
-      this._staticTypeContext, this._util, this._methodCollector)
-      : _callbackSpecializer =
-            CallbackSpecializer(_staticTypeContext, _util, _methodCollector),
+  InteropTransformer._(this._staticTypeContext, this._util,
+      this._methodCollector, inlineExtensionIndex)
+      : _callbackSpecializer = CallbackSpecializer(
+            _staticTypeContext, _util, _methodCollector, inlineExtensionIndex),
         _inlineExpander =
             InlineExpander(_staticTypeContext, _util, _methodCollector),
         _interopSpecializerFactory = InteropSpecializerFactory(
-            _staticTypeContext, _util, _methodCollector) {}
+            _staticTypeContext,
+            _util,
+            _methodCollector,
+            inlineExtensionIndex) {}
 
   factory InteropTransformer(CoreTypes coreTypes, ClassHierarchy hierarchy) {
-    final util = CoreTypesUtil(coreTypes);
+    final typeEnvironment = TypeEnvironment(coreTypes, hierarchy);
+    final inlineExtensionIndex =
+        InlineExtensionIndex(coreTypes, typeEnvironment);
+    final util = CoreTypesUtil(coreTypes, inlineExtensionIndex);
     return InteropTransformer._(
-        StatefulStaticTypeContext.stacked(
-            TypeEnvironment(coreTypes, hierarchy)),
+        StatefulStaticTypeContext.stacked(typeEnvironment),
         util,
-        MethodCollector(util));
+        MethodCollector(util),
+        inlineExtensionIndex);
   }
 
   @override
diff --git a/pkg/dart2wasm/lib/js/util.dart b/pkg/dart2wasm/lib/js/util.dart
index 1c3f1a9..00f83e3 100644
--- a/pkg/dart2wasm/lib/js/util.dart
+++ b/pkg/dart2wasm/lib/js/util.dart
@@ -2,8 +2,8 @@
 // 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:_js_interop_checks/src/js_interop.dart'
-    show hasJSInteropAnnotation, hasStaticInteropAnnotation;
+import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
+    show InlineExtensionIndex;
 import 'package:kernel/ast.dart';
 import 'package:kernel/core_types.dart';
 
@@ -11,6 +11,7 @@
 
 /// A utility wrapper for [CoreTypes].
 class CoreTypesUtil {
+  final InlineExtensionIndex _inlineExtensionIndex;
   final CoreTypes coreTypes;
   final Procedure allowInteropTarget;
   final Procedure dartifyRawTarget;
@@ -26,7 +27,7 @@
   final Class wasmExternRefClass;
   final Procedure wrapDartFunctionTarget;
 
-  CoreTypesUtil(this.coreTypes)
+  CoreTypesUtil(this.coreTypes, this._inlineExtensionIndex)
       : allowInteropTarget = coreTypes.index
             .getTopLevelProcedure('dart:js_util', 'allowInterop'),
         dartifyRawTarget = coreTypes.index
@@ -72,7 +73,9 @@
       wasmExternRefClass.getThisType(coreTypes, Nullability.nullable);
 
   Procedure jsifyTarget(DartType type) =>
-      type.isStaticInteropType ? jsValueUnboxTarget : jsifyRawTarget;
+      _inlineExtensionIndex.isStaticInteropType(type)
+          ? jsValueUnboxTarget
+          : jsifyRawTarget;
 
   void annotateProcedure(
       Procedure procedure, String pragmaOptionString, AnnotationType type) {
@@ -109,7 +112,7 @@
       return invokeOneArg(dartifyRawTarget, invocation);
     } else {
       Expression expression;
-      if (returnType.isStaticInteropType) {
+      if (_inlineExtensionIndex.isStaticInteropType(returnType)) {
         // TODO(joshualitt): Expose boxed `JSNull` and `JSUndefined` to Dart
         // code after migrating existing users of js interop on Dart2Wasm.
         // expression = _createJSValue(invocation);
@@ -179,16 +182,6 @@
   }
 }
 
-extension DartTypeExtension on DartType {
-  bool get isStaticInteropType {
-    final type = this;
-    return (type is InterfaceType &&
-            hasStaticInteropAnnotation(type.classReference.asClass)) ||
-        (type is ExtensionType &&
-            hasJSInteropAnnotation(type.extensionTypeDeclaration));
-  }
-}
-
 StaticInvocation invokeOneArg(Procedure target, Expression arg) =>
     StaticInvocation(target, Arguments([arg]));
 
diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart
index dbc2972..16d77c7 100644
--- a/pkg/dart2wasm/lib/target.dart
+++ b/pkg/dart2wasm/lib/target.dart
@@ -120,6 +120,7 @@
         'dart:_wasm',
         'dart:collection',
         'dart:js_interop',
+        'dart:js_interop_unsafe',
         'dart:js_util',
         'dart:typed_data',
       ];
diff --git a/pkg/dev_compiler/lib/src/kernel/target.dart b/pkg/dev_compiler/lib/src/kernel/target.dart
index c2ae514..fad5748 100644
--- a/pkg/dev_compiler/lib/src/kernel/target.dart
+++ b/pkg/dev_compiler/lib/src/kernel/target.dart
@@ -101,6 +101,7 @@
         'dart:js',
         'dart:js_util',
         'dart:js_interop',
+        'dart:js_interop_unsafe',
         'dart:math',
         'dart:svg',
         'dart:typed_data',
@@ -109,6 +110,7 @@
         'dart:_foreign_helper',
         'dart:_interceptors',
         'dart:_js_helper',
+        'dart:_js_types',
         'dart:_native_typed_data',
         'dart:_runtime',
         'dart:_rti',
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
index 2b7cf77..3c7bdf5 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
@@ -3939,6 +3939,38 @@
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
+    templateJsInteropStaticInteropMockTypeParametersNotAllowed = const Template<
+            Message Function(DartType _type, bool isNonNullableByDefault)>(
+        problemMessageTemplate:
+            r"""Type argument '#type' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.""",
+        correctionMessageTemplate:
+            r"""Remove the type parameter in the type argument or replace it with its bound.""",
+        withArguments:
+            _withArgumentsJsInteropStaticInteropMockTypeParametersNotAllowed);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(DartType _type, bool isNonNullableByDefault)>
+    codeJsInteropStaticInteropMockTypeParametersNotAllowed =
+    const Code<Message Function(DartType _type, bool isNonNullableByDefault)>(
+  "JsInteropStaticInteropMockTypeParametersNotAllowed",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsJsInteropStaticInteropMockTypeParametersNotAllowed(
+    DartType _type, bool isNonNullableByDefault) {
+  TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
+  List<Object> typeParts = labeler.labelType(_type);
+  String type = typeParts.join();
+  return new Message(codeJsInteropStaticInteropMockTypeParametersNotAllowed,
+      problemMessage:
+          """Type argument '${type}' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.""" +
+              labeler.originMessages,
+      correctionMessage: """Remove the type parameter in the type argument or replace it with its bound.""",
+      arguments: {'type': _type});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
     templateJsInteropStrictModeViolation = const Template<
             Message Function(DartType _type, bool isNonNullableByDefault)>(
         problemMessageTemplate:
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 1a5347b..735b835 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -629,6 +629,8 @@
 JsInteropStaticInteropMockMissingImplements/example: Fail # Web compiler specific
 JsInteropStaticInteropMockNotStaticInteropType/analyzerCode: Fail # Web compiler specific
 JsInteropStaticInteropMockNotStaticInteropType/example: Fail # Web compiler specific
+JsInteropStaticInteropMockTypeParametersNotAllowed/analyzerCode: Fail # Web compiler specific
+JsInteropStaticInteropMockTypeParametersNotAllowed/example: Fail # Web compiler specific
 JsInteropStaticInteropNoJSAnnotation/analyzerCode: Fail # Web compiler specific
 JsInteropStaticInteropNoJSAnnotation/example: Fail # Web compiler specific
 JsInteropStaticInteropParameterInitializersAreIgnored/analyzerCode: Fail # Web compiler specific
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index c171b07..d75e3ea 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5495,7 +5495,7 @@
   severity: WARNING
 
 JsInteropExportDisallowedMember:
-  problemMessage: "Member '#name' is not a concrete instance member, and therefore can't be exported."
+  problemMessage: "Member '#name' is not a concrete instance member or declares type parameters, and therefore can't be exported."
   correctionMessage: "Remove the `@JSExport` annotation from the member, and use an instance member to call this member instead."
 
 JsInteropExportInvalidInteropTypeArgument:
@@ -5597,6 +5597,10 @@
   problemMessage: "Type argument '#type' needs to be a `@staticInterop` type."
   correctionMessage: "Use a `@staticInterop` class instead."
 
+JsInteropStaticInteropMockTypeParametersNotAllowed:
+  problemMessage: "Type argument '#type' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance."
+  correctionMessage: "Remove the type parameter in the type argument or replace it with its bound."
+
 JsInteropStaticInteropNoJSAnnotation:
   problemMessage: "`@staticInterop` classes should also have the `@JS` annotation."
   correctionMessage: "Add `@JS` to class '#name'."
diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt
index a2689be..7ca72f8 100644
--- a/pkg/front_end/test/spell_checking_list_messages.txt
+++ b/pkg/front_end/test/spell_checking_list_messages.txt
@@ -28,9 +28,11 @@
 collide
 compilercontext.runincontext
 compilesdk
+conformance
 constructor(s)
 core
 count.#count
+createstaticinteropmock
 d
 dart.dev
 dart2js_server
@@ -67,6 +69,7 @@
 macro
 member(s)
 migrate
+mocking
 n
 name.#name
 name.stack
diff --git a/pkg/front_end/testcases/dart2js/inline_class/external.dart b/pkg/front_end/testcases/dart2js/inline_class/external.dart
index c9d2ff0..fb5fa0d 100644
--- a/pkg/front_end/testcases/dart2js/inline_class/external.dart
+++ b/pkg/front_end/testcases/dart2js/inline_class/external.dart
@@ -23,9 +23,7 @@
 
   external A method();
 
-  // TODO: Once https://github.com/dart-lang/sdk/issues/53046 is resolved,
-  // uncomment this, the static variant below, and the usage of the members.
-  // external T genericMethod<T extends B>(T t);
+  external T genericMethod<T extends B>(T t);
 
   external B get getter;
 
@@ -35,7 +33,7 @@
 
   external static A staticMethod();
 
-  // external static T staticGenericMethod<T extends B>(T t);
+  external static T staticGenericMethod<T extends B>(T t);
 
   external static B get staticGetter;
 
@@ -48,13 +46,13 @@
   a = b1.field;
   b1.field = a;
   a = b1.method();
-  // b2 = b2.genericMethod(b2);
+  b2 = b2.genericMethod(b2);
   b1 = b2.getter;
   b1.setter = b2;
   a = B.staticField;
   B.staticField = a;
   a = B.staticMethod();
-  // b2 = B.staticGenericMethod(b2);
+  b2 = B.staticGenericMethod(b2);
   b1 = B.staticGetter;
   B.staticSetter = b2;
 }
\ No newline at end of file
diff --git a/pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.expect b/pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.expect
index fe48805..fc20f1d 100644
--- a/pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.expect
+++ b/pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.expect
@@ -22,10 +22,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -45,11 +48,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => self::B|method(#this);
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => self::B|genericMethod<T>(#this, t);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -58,11 +65,13 @@
   a = self::B|get#field(b1);
   self::B|set#field(b1, a);
   a = self::B|method(b1);
+  b2 = self::B|genericMethod<self::B>(b2, b2);
   b1 = self::B|get#getter(b2);
   self::B|set#setter(b1, b2);
   a = self::B|staticField;
   self::B|staticField = a;
   a = self::B|staticMethod();
+  b2 = self::B|staticGenericMethod<self::B>(b2);
   b1 = self::B|staticGetter;
   self::B|staticSetter = b2;
 }
diff --git a/pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.transformed.expect
index 7d84e0f..292c0f8 100644
--- a/pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.transformed.expect
@@ -23,10 +23,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -46,11 +49,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => js_2::_callMethodUnchecked0<self::A>(#this, "method");
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => js_2::callMethod<T>(#this, "genericMethod", <dynamic>[t]);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -59,11 +66,13 @@
   a = js_2::getProperty<self::A>(b1, "field");
   js_2::setProperty<self::A>(b1, "field", a);
   a = js_2::_callMethodUnchecked0<self::A>(b1, "method");
+  b2 = js_2::callMethod<self::B>(b2, "genericMethod", <dynamic>[b2]);
   b1 = js_2::getProperty<self::B>(b2, "getter");
   js_2::setProperty<self::B>(b1, "setter", b2);
   a = js_2::getProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField");
   js_2::setProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField", a);
   a = js_2::_callMethodUnchecked0<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticMethod");
+  b2 = js_2::callMethod<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGenericMethod", <dynamic>[b2]);
   b1 = js_2::getProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGetter");
   js_2::setProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticSetter", b2);
 }
diff --git a/pkg/front_end/testcases/dart2js/inline_class/external.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/inline_class/external.dart.textual_outline.expect
index 8fa81c1..9c5e795c 100644
--- a/pkg/front_end/testcases/dart2js/inline_class/external.dart.textual_outline.expect
+++ b/pkg/front_end/testcases/dart2js/inline_class/external.dart.textual_outline.expect
@@ -11,10 +11,12 @@
   external B.named(int i);
   external A field;
   external A method();
+  external T genericMethod<T extends B>(T t);
   external B get getter;
   external void set setter(B b);
   external static A staticField;
   external static A staticMethod();
+  external static T staticGenericMethod<T extends B>(T t);
   external static B get staticGetter;
   external static void set staticSetter(B b);
 }
diff --git a/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.expect b/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.expect
index fe48805..fc20f1d 100644
--- a/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.expect
+++ b/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.expect
@@ -22,10 +22,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -45,11 +48,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => self::B|method(#this);
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => self::B|genericMethod<T>(#this, t);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -58,11 +65,13 @@
   a = self::B|get#field(b1);
   self::B|set#field(b1, a);
   a = self::B|method(b1);
+  b2 = self::B|genericMethod<self::B>(b2, b2);
   b1 = self::B|get#getter(b2);
   self::B|set#setter(b1, b2);
   a = self::B|staticField;
   self::B|staticField = a;
   a = self::B|staticMethod();
+  b2 = self::B|staticGenericMethod<self::B>(b2);
   b1 = self::B|staticGetter;
   self::B|staticSetter = b2;
 }
diff --git a/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.modular.expect
index fe48805..fc20f1d 100644
--- a/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.modular.expect
+++ b/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.modular.expect
@@ -22,10 +22,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -45,11 +48,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => self::B|method(#this);
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => self::B|genericMethod<T>(#this, t);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -58,11 +65,13 @@
   a = self::B|get#field(b1);
   self::B|set#field(b1, a);
   a = self::B|method(b1);
+  b2 = self::B|genericMethod<self::B>(b2, b2);
   b1 = self::B|get#getter(b2);
   self::B|set#setter(b1, b2);
   a = self::B|staticField;
   self::B|staticField = a;
   a = self::B|staticMethod();
+  b2 = self::B|staticGenericMethod<self::B>(b2);
   b1 = self::B|staticGetter;
   self::B|staticSetter = b2;
 }
diff --git a/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.outline.expect
index 17dba4e..660817e 100644
--- a/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.outline.expect
@@ -21,10 +21,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -44,11 +47,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => self::B|method(#this);
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => self::B|genericMethod<T>(#this, t);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void
@@ -60,4 +67,4 @@
 Evaluated: ConstructorInvocation @ org-dartlang-testcase:///external.dart:10:2 -> InstanceConstant(const JS{JS.name: null})
 Evaluated: StaticGet @ org-dartlang-testcase:///external.dart:11:2 -> InstanceConstant(const _StaticInterop{})
 Evaluated: ConstructorInvocation @ org-dartlang-testcase:///external.dart:14:2 -> InstanceConstant(const JS{JS.name: null})
-Extra constant evaluation: evaluated: 12, effectively constant: 4
+Extra constant evaluation: evaluated: 16, effectively constant: 4
diff --git a/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.transformed.expect
index 7d84e0f..292c0f8 100644
--- a/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/dart2js/inline_class/external.dart.weak.transformed.expect
@@ -23,10 +23,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -46,11 +49,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => js_2::_callMethodUnchecked0<self::A>(#this, "method");
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => js_2::callMethod<T>(#this, "genericMethod", <dynamic>[t]);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -59,11 +66,13 @@
   a = js_2::getProperty<self::A>(b1, "field");
   js_2::setProperty<self::A>(b1, "field", a);
   a = js_2::_callMethodUnchecked0<self::A>(b1, "method");
+  b2 = js_2::callMethod<self::B>(b2, "genericMethod", <dynamic>[b2]);
   b1 = js_2::getProperty<self::B>(b2, "getter");
   js_2::setProperty<self::B>(b1, "setter", b2);
   a = js_2::getProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField");
   js_2::setProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField", a);
   a = js_2::_callMethodUnchecked0<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticMethod");
+  b2 = js_2::callMethod<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGenericMethod", <dynamic>[b2]);
   b1 = js_2::getProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGetter");
   js_2::setProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticSetter", b2);
 }
diff --git a/pkg/front_end/testcases/dartdevc/inline_class/external.dart b/pkg/front_end/testcases/dartdevc/inline_class/external.dart
index f9d1c9b..c7fbdae 100644
--- a/pkg/front_end/testcases/dartdevc/inline_class/external.dart
+++ b/pkg/front_end/testcases/dartdevc/inline_class/external.dart
@@ -24,9 +24,7 @@
 
   external A method();
 
-  // TODO: Once https://github.com/dart-lang/sdk/issues/53046 is resolved,
-  // uncomment this, the static variant below, and the usage of the members.
-  // external T genericMethod<T extends B>(T t);
+  external T genericMethod<T extends B>(T t);
 
   external B get getter;
 
@@ -36,7 +34,7 @@
 
   external static A staticMethod();
 
-  // external static T staticGenericMethod<T extends B>(T t);
+  external static T staticGenericMethod<T extends B>(T t);
 
   external static B get staticGetter;
 
@@ -49,13 +47,13 @@
   a = b1.field;
   b1.field = a;
   a = b1.method();
-  // b2 = b2.genericMethod(b2);
+  b2 = b2.genericMethod(b2);
   b1 = b2.getter;
   b1.setter = b2;
   a = B.staticField;
   B.staticField = a;
   a = B.staticMethod();
-  // b2 = B.staticGenericMethod(b2);
+  b2 = B.staticGenericMethod(b2);
   b1 = B.staticGetter;
   B.staticSetter = b2;
 }
\ No newline at end of file
diff --git a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.strong.expect b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.strong.expect
index fe48805..fc20f1d 100644
--- a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.strong.expect
+++ b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.strong.expect
@@ -22,10 +22,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -45,11 +48,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => self::B|method(#this);
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => self::B|genericMethod<T>(#this, t);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -58,11 +65,13 @@
   a = self::B|get#field(b1);
   self::B|set#field(b1, a);
   a = self::B|method(b1);
+  b2 = self::B|genericMethod<self::B>(b2, b2);
   b1 = self::B|get#getter(b2);
   self::B|set#setter(b1, b2);
   a = self::B|staticField;
   self::B|staticField = a;
   a = self::B|staticMethod();
+  b2 = self::B|staticGenericMethod<self::B>(b2);
   b1 = self::B|staticGetter;
   self::B|staticSetter = b2;
 }
diff --git a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.strong.transformed.expect b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.strong.transformed.expect
index 7d84e0f..292c0f8 100644
--- a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.strong.transformed.expect
@@ -23,10 +23,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -46,11 +49,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => js_2::_callMethodUnchecked0<self::A>(#this, "method");
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => js_2::callMethod<T>(#this, "genericMethod", <dynamic>[t]);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -59,11 +66,13 @@
   a = js_2::getProperty<self::A>(b1, "field");
   js_2::setProperty<self::A>(b1, "field", a);
   a = js_2::_callMethodUnchecked0<self::A>(b1, "method");
+  b2 = js_2::callMethod<self::B>(b2, "genericMethod", <dynamic>[b2]);
   b1 = js_2::getProperty<self::B>(b2, "getter");
   js_2::setProperty<self::B>(b1, "setter", b2);
   a = js_2::getProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField");
   js_2::setProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField", a);
   a = js_2::_callMethodUnchecked0<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticMethod");
+  b2 = js_2::callMethod<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGenericMethod", <dynamic>[b2]);
   b1 = js_2::getProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGetter");
   js_2::setProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticSetter", b2);
 }
diff --git a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.textual_outline.expect b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.textual_outline.expect
index 36defdf..9c5e795c 100644
--- a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.textual_outline.expect
+++ b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.textual_outline.expect
@@ -1,12 +1,9 @@
 @JS()
 library static_interop;
-
 import 'dart:js_interop';
-
 @JS()
 @staticInterop
 class A {}
-
 @JS()
 inline class B {
   final A a;
@@ -14,12 +11,13 @@
   external B.named(int i);
   external A field;
   external A method();
+  external T genericMethod<T extends B>(T t);
   external B get getter;
   external void set setter(B b);
   external static A staticField;
   external static A staticMethod();
+  external static T staticGenericMethod<T extends B>(T t);
   external static B get staticGetter;
   external static void set staticSetter(B b);
 }
-
 void method(A a) {}
diff --git a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.expect b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.expect
index fe48805..fc20f1d 100644
--- a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.expect
+++ b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.expect
@@ -22,10 +22,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -45,11 +48,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => self::B|method(#this);
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => self::B|genericMethod<T>(#this, t);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -58,11 +65,13 @@
   a = self::B|get#field(b1);
   self::B|set#field(b1, a);
   a = self::B|method(b1);
+  b2 = self::B|genericMethod<self::B>(b2, b2);
   b1 = self::B|get#getter(b2);
   self::B|set#setter(b1, b2);
   a = self::B|staticField;
   self::B|staticField = a;
   a = self::B|staticMethod();
+  b2 = self::B|staticGenericMethod<self::B>(b2);
   b1 = self::B|staticGetter;
   self::B|staticSetter = b2;
 }
diff --git a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.modular.expect b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.modular.expect
index fe48805..fc20f1d 100644
--- a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.modular.expect
+++ b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.modular.expect
@@ -22,10 +22,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -45,11 +48,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => self::B|method(#this);
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => self::B|genericMethod<T>(#this, t);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -58,11 +65,13 @@
   a = self::B|get#field(b1);
   self::B|set#field(b1, a);
   a = self::B|method(b1);
+  b2 = self::B|genericMethod<self::B>(b2, b2);
   b1 = self::B|get#getter(b2);
   self::B|set#setter(b1, b2);
   a = self::B|staticField;
   self::B|staticField = a;
   a = self::B|staticMethod();
+  b2 = self::B|staticGenericMethod<self::B>(b2);
   b1 = self::B|staticGetter;
   self::B|staticSetter = b2;
 }
diff --git a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.outline.expect b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.outline.expect
index 12c4e1a..53adbd8 100644
--- a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.outline.expect
@@ -21,10 +21,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -44,11 +47,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => self::B|method(#this);
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => self::B|genericMethod<T>(#this, t);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void
@@ -60,4 +67,4 @@
 Evaluated: ConstructorInvocation @ org-dartlang-testcase:///external.dart:11:2 -> InstanceConstant(const JS{JS.name: null})
 Evaluated: StaticGet @ org-dartlang-testcase:///external.dart:12:2 -> InstanceConstant(const _StaticInterop{})
 Evaluated: ConstructorInvocation @ org-dartlang-testcase:///external.dart:15:2 -> InstanceConstant(const JS{JS.name: null})
-Extra constant evaluation: evaluated: 12, effectively constant: 4
+Extra constant evaluation: evaluated: 16, effectively constant: 4
diff --git a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.transformed.expect b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.transformed.expect
index 7d84e0f..292c0f8 100644
--- a/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/dartdevc/inline_class/external.dart.weak.transformed.expect
@@ -23,10 +23,13 @@
   set field = self::B|set#field;
   method method = self::B|method;
   tearoff method = self::B|get#method;
+  method genericMethod = self::B|genericMethod;
+  tearoff genericMethod = self::B|get#genericMethod;
   get getter = self::B|get#getter;
   static get staticField = get self::B|staticField;
   static set staticField = set self::B|staticField;
   static method staticMethod = self::B|staticMethod;
+  static method staticGenericMethod = self::B|staticGenericMethod;
   static get staticGetter = get self::B|staticGetter;
   set setter = self::B|set#setter;
   static set staticSetter = set self::B|staticSetter;
@@ -46,11 +49,15 @@
 external static inline-class-member method B|method(lowered final self::B #this) → self::A;
 static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
   return () → self::A => js_2::_callMethodUnchecked0<self::A>(#this, "method");
+external static inline-class-member method B|genericMethod<T extends self::B>(lowered final self::B #this, self::B|genericMethod::T t) → self::B|genericMethod::T;
+static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends self::B>(T) → T
+  return <T extends self::B>(T t) → T => js_2::callMethod<T>(#this, "genericMethod", <dynamic>[t]);
 external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
 external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
 external static inline-class-member get B|staticField() → self::A;
 external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
 external static inline-class-member method B|staticMethod() → self::A;
+external static inline-class-member method B|staticGenericMethod<T extends self::B>(self::B|staticGenericMethod::T t) → self::B|staticGenericMethod::T;
 external static inline-class-member get B|staticGetter() → self::B;
 external static inline-class-member set B|staticSetter(self::B b) → void;
 static method method(self::A a) → void {
@@ -59,11 +66,13 @@
   a = js_2::getProperty<self::A>(b1, "field");
   js_2::setProperty<self::A>(b1, "field", a);
   a = js_2::_callMethodUnchecked0<self::A>(b1, "method");
+  b2 = js_2::callMethod<self::B>(b2, "genericMethod", <dynamic>[b2]);
   b1 = js_2::getProperty<self::B>(b2, "getter");
   js_2::setProperty<self::B>(b1, "setter", b2);
   a = js_2::getProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField");
   js_2::setProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField", a);
   a = js_2::_callMethodUnchecked0<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticMethod");
+  b2 = js_2::callMethod<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGenericMethod", <dynamic>[b2]);
   b1 = js_2::getProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGetter");
   js_2::setProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticSetter", b2);
 }
diff --git a/tests/lib/js/export/static_interop_mock/functional_test_lib.dart b/tests/lib/js/export/static_interop_mock/functional_test_lib.dart
index ba50b89..2994caf 100644
--- a/tests/lib/js/export/static_interop_mock/functional_test_lib.dart
+++ b/tests/lib/js/export/static_interop_mock/functional_test_lib.dart
@@ -6,8 +6,12 @@
 // (final and not), getters, and setters are tested along with potential
 // renames.
 
+@JS()
+library functional_test_lib;
+
+import 'dart:js_interop';
+
 import 'package:expect/minitest.dart';
-import 'package:js/js.dart';
 import 'package:js/js_util.dart';
 
 @JS()
@@ -21,7 +25,7 @@
   @JS('_rename')
   external String rename();
   external String optionalConcat(String foo, String bar,
-      [String boo = '', String? baz]);
+      [String boo, String? baz]);
 }
 
 @JSExport()
@@ -84,6 +88,58 @@
   String _differentNameSameRename = 'initialized';
 }
 
+@JS()
+@staticInterop
+class Supersupertype {}
+
+@JS()
+@staticInterop
+class Supertype<T extends JSObject> implements Supersupertype {}
+
+extension SupertypeExtension<T extends JSObject> on Supertype<T> {
+  external T superMethod(T param);
+}
+
+// Note that even though `Supertype` is instantiated with `JSArray`, we still
+// require users to implement `superMethod` using the `JSObject` bound.
+// Functionally, this means `superMethod` needs to accept a `JSObject` (or a
+// supertype), even though an object typed `Params` can never actually call
+// `superMethod` with a type less specific than `JSArray`. This is probably
+// rare, but can be a bit frustrating. A similar analysis can be made for the
+// type parameters on `Params`. A user may want to mock only a
+// `Params<JSArray, Params>`, but we require them to mock a
+// `Params<JSObject, Supertype>` as those are the bounds. Unlike the
+// `superMethod` case however, we have an error check when trying to pass in
+// such a type to `createStaticInteropMock` to reduce confusion.
+@JS()
+@staticInterop
+class Params<T extends JSObject, U extends Supertype>
+    extends Supertype<JSArray> {
+  external factory Params();
+}
+
+extension ParamsExtension<T extends JSObject, U extends Supertype>
+    on Params<T, U> {
+  external T get getSet;
+  external set getSet(T val);
+  external T method(T param);
+  external U interopTypeMethod(U param);
+  external V genericMethod<V extends JSObject>(V param);
+}
+
+late JSObject _jsObject;
+
+@JSExport()
+class ParamsImpl<S extends JSObject, T extends JSObject, U extends Supertype> {
+  S superMethod(S param) => param;
+
+  T get getSet => _jsObject as T;
+  set getSet(T val) => _jsObject = val;
+  T method(T param) => param;
+  U interopTypeMethod(U param) => param;
+  JSObject genericMethod(JSObject param) => param;
+}
+
 void test([Object? proto]) {
   var jsMethods =
       createStaticInteropMock<Methods, MethodsDart>(MethodsDart(), proto);
@@ -138,4 +194,18 @@
   expect(jsGetSet.renamedGetSet, 'dartModified');
   expect(jsGetSet.sameNameDifferentRename, 'dartModifiedGet');
   expect(jsGetSet.differentNameSameRenameGet, 'dartModified');
+  // Calling members with type parameters.
+  final jsParams = createStaticInteropMock<Params, ParamsImpl>(ParamsImpl());
+  final jsArray = JSArray();
+  expect(jsParams.superMethod(jsArray), jsArray);
+  _jsObject = newObject<JSObject>();
+  expect(jsParams.getSet, _jsObject);
+  final newJsObject = newObject<JSObject>();
+  jsParams.getSet = newJsObject;
+  expect(jsParams.getSet, newJsObject);
+  expect(jsParams.method(_jsObject), _jsObject);
+  expect(jsParams.interopTypeMethod(_jsObject as Supertype), _jsObject);
+  expect(jsParams.genericMethod(_jsObject), _jsObject);
+  expect(jsParams.genericMethod<JSObject>(_jsObject), _jsObject);
+  expect(jsParams.genericMethod<JSArray>(jsArray), jsArray);
 }
diff --git a/tests/lib/js/export/static_interop_mock/type_parameter_static_test.dart b/tests/lib/js/export/static_interop_mock/type_parameter_static_test.dart
new file mode 100644
index 0000000..9d933ce
--- /dev/null
+++ b/tests/lib/js/export/static_interop_mock/type_parameter_static_test.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2023, 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.
+
+// Test that `createStaticInteropMock` correctly instantiates to bounds for type
+// parameters.
+
+import 'dart:js_interop';
+import 'package:js/js_util.dart';
+
+import 'functional_test_lib.dart';
+
+@JSExport()
+class Valid {
+  JSArray superMethod(JSAny param) => throw UnimplementedError();
+
+  JSArray get getSet => throw UnimplementedError();
+  set getSet(JSAny val) => throw UnimplementedError();
+  JSArray method(JSAny param) => throw UnimplementedError();
+  Params interopTypeMethod(Supertype param) => throw UnimplementedError();
+  JSArray genericMethod(JSAny param) => throw UnimplementedError();
+}
+
+@JSExport()
+class Invalid {
+  JSAny superMethod(JSArray param) => throw UnimplementedError();
+
+  @JSExport('getSet') // Rename to avoid getter/setter type conflicts.
+  JSAny get getter => throw UnimplementedError();
+  set getSet(JSArray val) => throw UnimplementedError();
+  JSAny method(JSArray param) => throw UnimplementedError();
+  Supersupertype interopTypeMethod(Params param) => throw UnimplementedError();
+  JSAny genericMethod(JSArray param) => throw UnimplementedError();
+}
+
+@JSExport()
+class InvalidContravariant {
+  JSArray superMethod(JSArray param) => throw UnimplementedError();
+
+  JSArray get getSet => throw UnimplementedError();
+  set getSet(JSArray val) => throw UnimplementedError();
+  JSArray method(JSArray param) => throw UnimplementedError();
+  Params interopTypeMethod(Params param) => throw UnimplementedError();
+  JSArray genericMethod(JSArray param) => throw UnimplementedError();
+}
+
+@JSExport()
+class InvalidCovariant {
+  JSAny superMethod(JSAny param) => throw UnimplementedError();
+
+  JSAny get getSet => throw UnimplementedError();
+  set getSet(JSAny val) => throw UnimplementedError();
+  JSAny method(JSAny param) => throw UnimplementedError();
+  Supersupertype interopTypeMethod(Supersupertype param) =>
+      throw UnimplementedError();
+  JSAny genericMethod(JSAny param) => throw UnimplementedError();
+}
+
+void main() {
+  createStaticInteropMock<
+//^
+// [web] Type argument 'Params<JSArray, Params<JSObject, Supertype<JSObject>>>' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.
+// [web] Type argument 'ParamsImpl<JSArray, JSArray, Params<JSObject, Supertype<JSObject>>>' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.
+          Params<JSArray, Params>,
+          ParamsImpl<JSArray, JSArray, Params>>(
+      ParamsImpl<JSArray, JSArray, Params>());
+  createStaticInteropMock<Params<JSObject, Supertype>,
+          ParamsImpl<JSObject, JSObject, Supertype>>(
+      ParamsImpl<JSObject, JSObject, Supertype>());
+
+  // Note that this is fine, but might fail at runtime due to runtime covariant
+  // checks. This is no different than casting a `List<JSObject>` to `List` and
+  // trying to add a `JSString`. On the JS backends, this will fail, and on
+  // dart2wasm, this will succeed because all JS types get erased to JSValue.
+  createStaticInteropMock<Params, ParamsImpl>(
+      ParamsImpl<JSArray, JSArray, Supertype>());
+  createStaticInteropMock<Params, ParamsImpl>(ParamsImpl());
+  createStaticInteropMock<Params, Valid>(Valid());
+
+  createStaticInteropMock<Params, Invalid>(Invalid());
+//^
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'genericMethod': ParamsExtension.genericMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'getSet': ParamsExtension.getSet (FunctionType(JSObject Function())), ParamsExtension.getSet= (FunctionType(void Function(JSObject))).
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'interopTypeMethod': ParamsExtension.interopTypeMethod (FunctionType(Supertype<JSObject> Function(Supertype<JSObject>))).
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'method': ParamsExtension.method (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'superMethod': SupertypeExtension.superMethod (FunctionType(JSObject Function(JSObject))).
+  createStaticInteropMock<Params, InvalidContravariant>(InvalidContravariant());
+//^
+// [web] Dart class 'InvalidContravariant' does not have any members that implement any of the following extension member(s) with export name 'genericMethod': ParamsExtension.genericMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidContravariant' does not have any members that implement any of the following extension member(s) with export name 'interopTypeMethod': ParamsExtension.interopTypeMethod (FunctionType(Supertype<JSObject> Function(Supertype<JSObject>))).
+// [web] Dart class 'InvalidContravariant' does not have any members that implement any of the following extension member(s) with export name 'method': ParamsExtension.method (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidContravariant' does not have any members that implement any of the following extension member(s) with export name 'superMethod': SupertypeExtension.superMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidContravariant' has a getter, but does not have a setter to implement any of the following extension member(s) with export name 'getSet': ParamsExtension.getSet= (FunctionType(void Function(JSObject))).
+  createStaticInteropMock<Params, InvalidCovariant>(InvalidCovariant());
+//^
+// [web] Dart class 'InvalidCovariant' does not have any members that implement any of the following extension member(s) with export name 'genericMethod': ParamsExtension.genericMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidCovariant' does not have any members that implement any of the following extension member(s) with export name 'interopTypeMethod': ParamsExtension.interopTypeMethod (FunctionType(Supertype<JSObject> Function(Supertype<JSObject>))).
+// [web] Dart class 'InvalidCovariant' does not have any members that implement any of the following extension member(s) with export name 'method': ParamsExtension.method (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidCovariant' does not have any members that implement any of the following extension member(s) with export name 'superMethod': SupertypeExtension.superMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidCovariant' has a setter, but does not have a getter to implement any of the following extension member(s) with export name 'getSet': ParamsExtension.getSet (FunctionType(JSObject Function())).
+}
diff --git a/tests/lib/js/export/validation_test.dart b/tests/lib/js/export/validation_test.dart
index af2fab8..5577778 100644
--- a/tests/lib/js/export/validation_test.dart
+++ b/tests/lib/js/export/validation_test.dart
@@ -179,9 +179,37 @@
   createDartExport(ClassWithValue());
 }
 
+// `JSExport` classes can't export methods that define type parameters as those
+// type parameters will never be instantiated through interop. Class type
+// parameters are okay, however.
+@JSExport()
+class GenericAll {
+//    ^
+// [web] Class 'GenericAll' has no exportable members in the class or the inheritance chain.
+  void defineTypeParam<T extends int>() {}
+  T useTypeParam<T extends Object>(T t) => t;
+}
+
+class GenericSome<U> {
+  @JSExport()
+  void defineTypeParam<T extends int>() {}
+  //   ^
+  // [web] Member 'defineTypeParam' is not a concrete instance member or declares type parameters, and therefore can't be exported.
+  T useTypeParam<T extends Object>(T t) => t;
+  @JSExport()
+  U useClassParam(U u) => u;
+}
+
+void testClassWithGenerics() {
+  createDartExport(GenericAll());
+  createDartExport(GenericSome());
+  createDartExport(GenericSome<int>());
+}
+
 void main() {
   testNumberOfExports();
   testUseDartInterface();
   testCollisions();
   testClassExportWithValue();
+  testClassWithGenerics();
 }
diff --git a/tests/lib_2/js/export/static_interop_mock/functional_test_lib.dart b/tests/lib_2/js/export/static_interop_mock/functional_test_lib.dart
index ba50b89..2994caf 100644
--- a/tests/lib_2/js/export/static_interop_mock/functional_test_lib.dart
+++ b/tests/lib_2/js/export/static_interop_mock/functional_test_lib.dart
@@ -6,8 +6,12 @@
 // (final and not), getters, and setters are tested along with potential
 // renames.
 
+@JS()
+library functional_test_lib;
+
+import 'dart:js_interop';
+
 import 'package:expect/minitest.dart';
-import 'package:js/js.dart';
 import 'package:js/js_util.dart';
 
 @JS()
@@ -21,7 +25,7 @@
   @JS('_rename')
   external String rename();
   external String optionalConcat(String foo, String bar,
-      [String boo = '', String? baz]);
+      [String boo, String? baz]);
 }
 
 @JSExport()
@@ -84,6 +88,58 @@
   String _differentNameSameRename = 'initialized';
 }
 
+@JS()
+@staticInterop
+class Supersupertype {}
+
+@JS()
+@staticInterop
+class Supertype<T extends JSObject> implements Supersupertype {}
+
+extension SupertypeExtension<T extends JSObject> on Supertype<T> {
+  external T superMethod(T param);
+}
+
+// Note that even though `Supertype` is instantiated with `JSArray`, we still
+// require users to implement `superMethod` using the `JSObject` bound.
+// Functionally, this means `superMethod` needs to accept a `JSObject` (or a
+// supertype), even though an object typed `Params` can never actually call
+// `superMethod` with a type less specific than `JSArray`. This is probably
+// rare, but can be a bit frustrating. A similar analysis can be made for the
+// type parameters on `Params`. A user may want to mock only a
+// `Params<JSArray, Params>`, but we require them to mock a
+// `Params<JSObject, Supertype>` as those are the bounds. Unlike the
+// `superMethod` case however, we have an error check when trying to pass in
+// such a type to `createStaticInteropMock` to reduce confusion.
+@JS()
+@staticInterop
+class Params<T extends JSObject, U extends Supertype>
+    extends Supertype<JSArray> {
+  external factory Params();
+}
+
+extension ParamsExtension<T extends JSObject, U extends Supertype>
+    on Params<T, U> {
+  external T get getSet;
+  external set getSet(T val);
+  external T method(T param);
+  external U interopTypeMethod(U param);
+  external V genericMethod<V extends JSObject>(V param);
+}
+
+late JSObject _jsObject;
+
+@JSExport()
+class ParamsImpl<S extends JSObject, T extends JSObject, U extends Supertype> {
+  S superMethod(S param) => param;
+
+  T get getSet => _jsObject as T;
+  set getSet(T val) => _jsObject = val;
+  T method(T param) => param;
+  U interopTypeMethod(U param) => param;
+  JSObject genericMethod(JSObject param) => param;
+}
+
 void test([Object? proto]) {
   var jsMethods =
       createStaticInteropMock<Methods, MethodsDart>(MethodsDart(), proto);
@@ -138,4 +194,18 @@
   expect(jsGetSet.renamedGetSet, 'dartModified');
   expect(jsGetSet.sameNameDifferentRename, 'dartModifiedGet');
   expect(jsGetSet.differentNameSameRenameGet, 'dartModified');
+  // Calling members with type parameters.
+  final jsParams = createStaticInteropMock<Params, ParamsImpl>(ParamsImpl());
+  final jsArray = JSArray();
+  expect(jsParams.superMethod(jsArray), jsArray);
+  _jsObject = newObject<JSObject>();
+  expect(jsParams.getSet, _jsObject);
+  final newJsObject = newObject<JSObject>();
+  jsParams.getSet = newJsObject;
+  expect(jsParams.getSet, newJsObject);
+  expect(jsParams.method(_jsObject), _jsObject);
+  expect(jsParams.interopTypeMethod(_jsObject as Supertype), _jsObject);
+  expect(jsParams.genericMethod(_jsObject), _jsObject);
+  expect(jsParams.genericMethod<JSObject>(_jsObject), _jsObject);
+  expect(jsParams.genericMethod<JSArray>(jsArray), jsArray);
 }
diff --git a/tests/lib_2/js/export/static_interop_mock/type_parameter_static_test.dart b/tests/lib_2/js/export/static_interop_mock/type_parameter_static_test.dart
new file mode 100644
index 0000000..9d933ce
--- /dev/null
+++ b/tests/lib_2/js/export/static_interop_mock/type_parameter_static_test.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2023, 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.
+
+// Test that `createStaticInteropMock` correctly instantiates to bounds for type
+// parameters.
+
+import 'dart:js_interop';
+import 'package:js/js_util.dart';
+
+import 'functional_test_lib.dart';
+
+@JSExport()
+class Valid {
+  JSArray superMethod(JSAny param) => throw UnimplementedError();
+
+  JSArray get getSet => throw UnimplementedError();
+  set getSet(JSAny val) => throw UnimplementedError();
+  JSArray method(JSAny param) => throw UnimplementedError();
+  Params interopTypeMethod(Supertype param) => throw UnimplementedError();
+  JSArray genericMethod(JSAny param) => throw UnimplementedError();
+}
+
+@JSExport()
+class Invalid {
+  JSAny superMethod(JSArray param) => throw UnimplementedError();
+
+  @JSExport('getSet') // Rename to avoid getter/setter type conflicts.
+  JSAny get getter => throw UnimplementedError();
+  set getSet(JSArray val) => throw UnimplementedError();
+  JSAny method(JSArray param) => throw UnimplementedError();
+  Supersupertype interopTypeMethod(Params param) => throw UnimplementedError();
+  JSAny genericMethod(JSArray param) => throw UnimplementedError();
+}
+
+@JSExport()
+class InvalidContravariant {
+  JSArray superMethod(JSArray param) => throw UnimplementedError();
+
+  JSArray get getSet => throw UnimplementedError();
+  set getSet(JSArray val) => throw UnimplementedError();
+  JSArray method(JSArray param) => throw UnimplementedError();
+  Params interopTypeMethod(Params param) => throw UnimplementedError();
+  JSArray genericMethod(JSArray param) => throw UnimplementedError();
+}
+
+@JSExport()
+class InvalidCovariant {
+  JSAny superMethod(JSAny param) => throw UnimplementedError();
+
+  JSAny get getSet => throw UnimplementedError();
+  set getSet(JSAny val) => throw UnimplementedError();
+  JSAny method(JSAny param) => throw UnimplementedError();
+  Supersupertype interopTypeMethod(Supersupertype param) =>
+      throw UnimplementedError();
+  JSAny genericMethod(JSAny param) => throw UnimplementedError();
+}
+
+void main() {
+  createStaticInteropMock<
+//^
+// [web] Type argument 'Params<JSArray, Params<JSObject, Supertype<JSObject>>>' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.
+// [web] Type argument 'ParamsImpl<JSArray, JSArray, Params<JSObject, Supertype<JSObject>>>' has type parameters that do not match their bound. createStaticInteropMock requires instantiating all type parameters to their bound to ensure mocking conformance.
+          Params<JSArray, Params>,
+          ParamsImpl<JSArray, JSArray, Params>>(
+      ParamsImpl<JSArray, JSArray, Params>());
+  createStaticInteropMock<Params<JSObject, Supertype>,
+          ParamsImpl<JSObject, JSObject, Supertype>>(
+      ParamsImpl<JSObject, JSObject, Supertype>());
+
+  // Note that this is fine, but might fail at runtime due to runtime covariant
+  // checks. This is no different than casting a `List<JSObject>` to `List` and
+  // trying to add a `JSString`. On the JS backends, this will fail, and on
+  // dart2wasm, this will succeed because all JS types get erased to JSValue.
+  createStaticInteropMock<Params, ParamsImpl>(
+      ParamsImpl<JSArray, JSArray, Supertype>());
+  createStaticInteropMock<Params, ParamsImpl>(ParamsImpl());
+  createStaticInteropMock<Params, Valid>(Valid());
+
+  createStaticInteropMock<Params, Invalid>(Invalid());
+//^
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'genericMethod': ParamsExtension.genericMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'getSet': ParamsExtension.getSet (FunctionType(JSObject Function())), ParamsExtension.getSet= (FunctionType(void Function(JSObject))).
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'interopTypeMethod': ParamsExtension.interopTypeMethod (FunctionType(Supertype<JSObject> Function(Supertype<JSObject>))).
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'method': ParamsExtension.method (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'Invalid' does not have any members that implement any of the following extension member(s) with export name 'superMethod': SupertypeExtension.superMethod (FunctionType(JSObject Function(JSObject))).
+  createStaticInteropMock<Params, InvalidContravariant>(InvalidContravariant());
+//^
+// [web] Dart class 'InvalidContravariant' does not have any members that implement any of the following extension member(s) with export name 'genericMethod': ParamsExtension.genericMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidContravariant' does not have any members that implement any of the following extension member(s) with export name 'interopTypeMethod': ParamsExtension.interopTypeMethod (FunctionType(Supertype<JSObject> Function(Supertype<JSObject>))).
+// [web] Dart class 'InvalidContravariant' does not have any members that implement any of the following extension member(s) with export name 'method': ParamsExtension.method (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidContravariant' does not have any members that implement any of the following extension member(s) with export name 'superMethod': SupertypeExtension.superMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidContravariant' has a getter, but does not have a setter to implement any of the following extension member(s) with export name 'getSet': ParamsExtension.getSet= (FunctionType(void Function(JSObject))).
+  createStaticInteropMock<Params, InvalidCovariant>(InvalidCovariant());
+//^
+// [web] Dart class 'InvalidCovariant' does not have any members that implement any of the following extension member(s) with export name 'genericMethod': ParamsExtension.genericMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidCovariant' does not have any members that implement any of the following extension member(s) with export name 'interopTypeMethod': ParamsExtension.interopTypeMethod (FunctionType(Supertype<JSObject> Function(Supertype<JSObject>))).
+// [web] Dart class 'InvalidCovariant' does not have any members that implement any of the following extension member(s) with export name 'method': ParamsExtension.method (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidCovariant' does not have any members that implement any of the following extension member(s) with export name 'superMethod': SupertypeExtension.superMethod (FunctionType(JSObject Function(JSObject))).
+// [web] Dart class 'InvalidCovariant' has a setter, but does not have a getter to implement any of the following extension member(s) with export name 'getSet': ParamsExtension.getSet (FunctionType(JSObject Function())).
+}
diff --git a/tests/lib_2/js/export/validation_test.dart b/tests/lib_2/js/export/validation_test.dart
index af2fab8..5577778 100644
--- a/tests/lib_2/js/export/validation_test.dart
+++ b/tests/lib_2/js/export/validation_test.dart
@@ -179,9 +179,37 @@
   createDartExport(ClassWithValue());
 }
 
+// `JSExport` classes can't export methods that define type parameters as those
+// type parameters will never be instantiated through interop. Class type
+// parameters are okay, however.
+@JSExport()
+class GenericAll {
+//    ^
+// [web] Class 'GenericAll' has no exportable members in the class or the inheritance chain.
+  void defineTypeParam<T extends int>() {}
+  T useTypeParam<T extends Object>(T t) => t;
+}
+
+class GenericSome<U> {
+  @JSExport()
+  void defineTypeParam<T extends int>() {}
+  //   ^
+  // [web] Member 'defineTypeParam' is not a concrete instance member or declares type parameters, and therefore can't be exported.
+  T useTypeParam<T extends Object>(T t) => t;
+  @JSExport()
+  U useClassParam(U u) => u;
+}
+
+void testClassWithGenerics() {
+  createDartExport(GenericAll());
+  createDartExport(GenericSome());
+  createDartExport(GenericSome<int>());
+}
+
 void main() {
   testNumberOfExports();
   testUseDartInterface();
   testCollisions();
   testClassExportWithValue();
+  testClassWithGenerics();
 }