[dart:js_interop] Fix checking of type parameters in interop APIs

Addresses some comments in https://github.com/dart-lang/sdk/issues/54192

- With the addition of `nonTypeVariableBound`, we no longer need a
recursive checker.
- We also should account for `StructuralParameterTypes`.
- The current checks did not check type parameters for functions
converted via `toJS`, so that is handled as well.
- Functions that declare type parameters cannot be called in JS
correctly, so an error is added when users try to convert them.
- Some errors are reworded/modified.

Change-Id: Ic05220883ec4ad8653963acd901d339426cba6c6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/339346
Reviewed-by: Sigmund Cherem <sigmund@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 a93f73b..c65db5b 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -8632,6 +8632,18 @@
         r"""Try removing the 'external' keyword or adding a JS interop annotation.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeJsInteropFunctionToJSTypeParameters =
+    messageJsInteropFunctionToJSTypeParameters;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageJsInteropFunctionToJSTypeParameters = const MessageCode(
+    "JsInteropFunctionToJSTypeParameters",
+    problemMessage:
+        r"""Functions converted via `toJS` cannot declare type parameters.""",
+    correctionMessage:
+        r"""Remove the declared type parameters from the function.""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeJsInteropInvalidStaticClassMemberName =
     messageJsInteropInvalidStaticClassMemberName;
 
@@ -8836,21 +8848,6 @@
     correctionMessage: r"""Try replacing this with a normal method.""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null>
-    codeJsInteropStaticInteropExternalMemberWithInvalidTypeParameters =
-    messageJsInteropStaticInteropExternalMemberWithInvalidTypeParameters;
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode
-    messageJsInteropStaticInteropExternalMemberWithInvalidTypeParameters =
-    const MessageCode(
-        "JsInteropStaticInteropExternalMemberWithInvalidTypeParameters",
-        problemMessage:
-            r"""External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.""",
-        correctionMessage:
-            r"""Try adding a valid bound to the type parameters used in this member.""");
-
-// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeJsInteropStaticInteropGenerativeConstructor =
     messageJsInteropStaticInteropGenerativeConstructor;
 
diff --git a/pkg/_js_interop_checks/lib/js_interop_checks.dart b/pkg/_js_interop_checks/lib/js_interop_checks.dart
index 3005f22..447aa94 100644
--- a/pkg/_js_interop_checks/lib/js_interop_checks.dart
+++ b/pkg/_js_interop_checks/lib/js_interop_checks.dart
@@ -16,13 +16,13 @@
         messageJsInteropExternalExtensionMemberOnTypeInvalid,
         messageJsInteropExternalExtensionMemberWithStaticDisallowed,
         messageJsInteropExternalMemberNotJSAnnotated,
+        messageJsInteropFunctionToJSTypeParameters,
         messageJsInteropInvalidStaticClassMemberName,
         messageJsInteropNamedParameters,
         messageJsInteropNonExternalConstructor,
         messageJsInteropNonExternalMember,
         messageJsInteropOperatorCannotBeRenamed,
         messageJsInteropOperatorsNotSupported,
-        messageJsInteropStaticInteropExternalMemberWithInvalidTypeParameters,
         messageJsInteropStaticInteropGenerativeConstructor,
         messageJsInteropStaticInteropParameterInitializersAreIgnored,
         messageJsInteropStaticInteropSyntheticConstructor,
@@ -68,7 +68,6 @@
   final Map<String, Class> _nativeClasses;
   final JsInteropDiagnosticReporter _reporter;
   final StatefulStaticTypeContext _staticTypeContext;
-  late _TypeParameterBoundChecker _typeParameterBoundChecker;
   bool _classHasJSAnnotation = false;
   bool _classHasAnonymousAnnotation = false;
   bool _classHasStaticInteropAnnotation = false;
@@ -134,7 +133,6 @@
             TypeEnvironment(_coreTypes, hierarchy)) {
     _extensionIndex =
         ExtensionIndex(_coreTypes, _staticTypeContext.typeEnvironment);
-    _typeParameterBoundChecker = _TypeParameterBoundChecker(_extensionIndex);
   }
 
   /// Determines if given [member] is an external extension member that needs to
@@ -375,7 +373,6 @@
               ((hasDartJSInteropAnnotation(annotatable) ||
                   annotatable is ExtensionTypeDeclaration))) {
             // Checks for dart:js_interop APIs only.
-            _checkStaticInteropMemberUsesValidTypeParameters(node);
             final function = node.function;
             _reportProcedureIfNotAllowedType(function.returnType, node);
             for (final parameter in function.positionalParameters) {
@@ -697,16 +694,18 @@
   /// Checks that [node], which is a call to 'Function.toJS', is called with a
   /// valid function type.
   void _checkFunctionToJSCall(StaticInvocation node) {
+    void report(Message message) => _reporter.report(
+        message, node.fileOffset, node.name.text.length, node.location?.file);
+
     final argument = node.arguments.positional.single;
     final functionType = argument.getStaticType(_staticTypeContext);
     if (functionType is! FunctionType) {
-      _reporter.report(
-          templateJsInteropFunctionToJSRequiresStaticType.withArguments(
-              functionType, true),
-          node.fileOffset,
-          node.name.text.length,
-          node.location?.file);
+      report(templateJsInteropFunctionToJSRequiresStaticType.withArguments(
+          functionType, true));
     } else {
+      if (functionType.typeParameters.isNotEmpty) {
+        report(messageJsInteropFunctionToJSTypeParameters);
+      }
       _reportStaticInvocationIfNotAllowedType(functionType.returnType, node);
       for (final parameter in functionType.positionalParameters) {
         _reportStaticInvocationIfNotAllowedType(parameter, node);
@@ -812,16 +811,6 @@
     }
   }
 
-  void _checkStaticInteropMemberUsesValidTypeParameters(Procedure node) {
-    if (_typeParameterBoundChecker.containsInvalidTypeBound(node)) {
-      _reporter.report(
-          messageJsInteropStaticInteropExternalMemberWithInvalidTypeParameters,
-          node.fileOffset,
-          node.name.text.length,
-          node.fileUri);
-    }
-  }
-
   /// If [procedure] is a generated procedure that represents a relevant
   /// tear-off, return the torn-off member.
   ///
@@ -894,9 +883,20 @@
     // provide JS types equivalents, but likely only if we have implicit
     // conversions between Dart types and JS types.
 
-    // Type parameter types are checked elsewhere.
-    if (type is VoidType || type is NullType || type is TypeParameterType) {
-      return true;
+    if (type is VoidType || type is NullType) return true;
+    if (type is TypeParameterType || type is StructuralParameterType) {
+      final bound = type.nonTypeVariableBound;
+      final isStaticInteropBound =
+          bound is InterfaceType && hasStaticInteropAnnotation(bound.classNode);
+      final isInteropExtensionTypeBound = bound is ExtensionType &&
+          _extensionIndex
+              .isInteropExtensionType(bound.extensionTypeDeclaration);
+      // TODO(srujzs): We may want to support type parameters with primitive
+      // bounds that are themselves allowed e.g. `num`. If so, we should handle
+      // that change in dart2wasm.
+      if (isStaticInteropBound || isInteropExtensionTypeBound) {
+        return true;
+      }
     }
     if (type is InterfaceType) {
       final cls = type.classNode;
@@ -940,55 +940,6 @@
           type, node, node.name, node.location?.file);
 }
 
-/// Visitor used to check that all usages of type parameter types of an external
-/// static interop member is a valid static interop type.
-class _TypeParameterBoundChecker extends RecursiveVisitor {
-  final ExtensionIndex _extensionIndex;
-
-  _TypeParameterBoundChecker(this._extensionIndex);
-
-  bool _containsInvalidTypeBound = false;
-
-  bool containsInvalidTypeBound(Procedure node) {
-    _containsInvalidTypeBound = false;
-    final function = node.function;
-    for (final param in function.positionalParameters) {
-      param.accept(this);
-    }
-    function.returnType.accept(this);
-    return _containsInvalidTypeBound;
-  }
-
-  @override
-  void visitInterfaceType(InterfaceType node) {
-    final cls = node.classNode;
-    if (hasStaticInteropAnnotation(cls)) return;
-    super.visitInterfaceType(node);
-  }
-
-  @override
-  void visitExtensionType(ExtensionType node) {
-    if (_extensionIndex.isInteropExtensionType(node.extensionTypeDeclaration)) {
-      return;
-    }
-    super.visitExtensionType(node);
-  }
-
-  @override
-  void visitTypeParameterType(TypeParameterType node) {
-    final bound = node.bound;
-    if (bound is ExtensionType &&
-        !_extensionIndex
-            .isInteropExtensionType(bound.extensionTypeDeclaration)) {
-      _containsInvalidTypeBound = true;
-    }
-    if (bound is InterfaceType &&
-        !hasStaticInteropAnnotation(bound.classNode)) {
-      _containsInvalidTypeBound = true;
-    }
-  }
-}
-
 class JsInteropDiagnosticReporter {
   bool hasJsInteropErrors = false;
   final DiagnosticReporter<Message, LocatedMessage> _reporter;
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 c6e06c3..5330bfc 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
@@ -4082,7 +4082,7 @@
             Message Function(DartType _type, bool isNonNullableByDefault)>(
         "JsInteropFunctionToJSRequiresStaticType",
         problemMessageTemplate:
-            r"""`Function.toJS` requires a statically known function type, but Type '#type' is not a function type, e.g., `void Function()`.""",
+            r"""`Function.toJS` requires a statically known function type, but Type '#type' is not a precise function type, e.g., `void Function()`.""",
         correctionMessageTemplate:
             r"""Insert an explicit cast to the expected function type.""",
         withArguments: _withArgumentsJsInteropFunctionToJSRequiresStaticType);
@@ -4102,7 +4102,7 @@
   String type = typeParts.join();
   return new Message(codeJsInteropFunctionToJSRequiresStaticType,
       problemMessage:
-          """`Function.toJS` requires a statically known function type, but Type '${type}' is not a function type, e.g., `void Function()`.""" +
+          """`Function.toJS` requires a statically known function type, but Type '${type}' is not a precise function type, e.g., `void Function()`.""" +
               labeler.originMessages,
       correctionMessage: """Insert an explicit cast to the expected function type.""",
       arguments: {'type': _type});
@@ -4114,7 +4114,7 @@
             Message Function(DartType _type, bool isNonNullableByDefault)>(
         "JsInteropStaticInteropExternalTypeViolation",
         problemMessageTemplate:
-            r"""Type '#type' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.""",
+            r"""Type '#type' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.""",
         correctionMessageTemplate: r"""Use one of the valid types instead.""",
         withArguments:
             _withArgumentsJsInteropStaticInteropExternalTypeViolation);
@@ -4134,7 +4134,7 @@
   String type = typeParts.join();
   return new Message(codeJsInteropStaticInteropExternalTypeViolation,
       problemMessage:
-          """Type '${type}' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.""" +
+          """Type '${type}' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.""" +
               labeler.originMessages,
       correctionMessage: """Use one of the valid types instead.""",
       arguments: {'type': _type});
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 5ffd916..ba7ad60 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -629,6 +629,8 @@
 JsInteropExternalMemberNotJSAnnotated/example: Fail # Web compiler specific
 JsInteropFunctionToJSRequiresStaticType/analyzerCode: Fail # Web compiler specific
 JsInteropFunctionToJSRequiresStaticType/example: Fail # Web compiler specific
+JsInteropFunctionToJSTypeParameters/analyzerCode: Fail # Web compiler specific
+JsInteropFunctionToJSTypeParameters/example: Fail # Web compiler specific
 JsInteropInvalidStaticClassMemberName/analyzerCode: Fail
 JsInteropInvalidStaticClassMemberName/example: Fail
 JsInteropJSClassExtendsDartClass/analyzerCode: Fail # Web compiler specific
@@ -649,8 +651,6 @@
 JsInteropOperatorCannotBeRenamed/example: Fail # Web compiler specific
 JsInteropOperatorsNotSupported/analyzerCode: Fail # Web compiler specific
 JsInteropOperatorsNotSupported/example: Fail # Web compiler specific
-JsInteropStaticInteropExternalMemberWithInvalidTypeParameters/analyzerCode: Fail # Web compiler specific
-JsInteropStaticInteropExternalMemberWithInvalidTypeParameters/example: Fail # Web compiler specific
 JsInteropStaticInteropExternalTypeViolation/analyzerCode: Fail # Web compiler specific
 JsInteropStaticInteropExternalTypeViolation/example: Fail # Web compiler specific
 JsInteropStaticInteropGenerativeConstructor/analyzerCode: Fail # Web compiler specific
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 9611850..0902253 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5793,6 +5793,14 @@
   problemMessage: "Only JS interop members may be 'external'."
   correctionMessage: "Try removing the 'external' keyword or adding a JS interop annotation."
 
+JsInteropFunctionToJSRequiresStaticType:
+  problemMessage: "`Function.toJS` requires a statically known function type, but Type '#type' is not a precise function type, e.g., `void Function()`."
+  correctionMessage: "Insert an explicit cast to the expected function type."
+
+JsInteropFunctionToJSTypeParameters:
+  problemMessage: "Functions converted via `toJS` cannot declare type parameters."
+  correctionMessage: "Remove the declared type parameters from the function."
+
 JsInteropInvalidStaticClassMemberName:
   problemMessage: "JS interop static class members cannot have '.' in their JS name."
 
@@ -5832,12 +5840,8 @@
   problemMessage: "Class '#name' does not have an `@staticInterop` annotation, but has supertype '#name2', which does."
   correctionMessage: "Try marking '#name' as a `@staticInterop` class, or don't inherit '#name2'."
 
-JsInteropStaticInteropExternalMemberWithInvalidTypeParameters:
-  problemMessage: "External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types."
-  correctionMessage: "Try adding a valid bound to the type parameters used in this member."
-
 JsInteropStaticInteropExternalTypeViolation:
-  problemMessage: "Type '#type' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types."
+  problemMessage: "Type '#type' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type."
   correctionMessage: "Use one of the valid types instead."
 
 JsInteropStaticInteropGenerativeConstructor:
@@ -5897,10 +5901,6 @@
   problemMessage: "Library '#name' is forbidden when strict mode is enabled."
   correctionMessage: "Remove the import of a forbidden library."
 
-JsInteropFunctionToJSRequiresStaticType:
-  problemMessage: "`Function.toJS` requires a statically known function type, but Type '#type' is not a function type, e.g., `void Function()`."
-  correctionMessage: "Insert an explicit cast to the expected function type."
-
 NonNullableInNullAware:
   problemMessage: "Operand of null-aware operation '#name' has type '#type' which excludes null."
   severity: WARNING
diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt
index e0878a7..2fa697a 100644
--- a/pkg/front_end/test/spell_checking_list_messages.txt
+++ b/pkg/front_end/test/spell_checking_list_messages.txt
@@ -123,6 +123,7 @@
 tearoff
 this.namedconstructor
 this.x
+tojs
 trusttypes
 type3.#name
 u
diff --git a/tests/lib/js/static_interop_test/external_member_type_parameters_static_test.dart b/tests/lib/js/static_interop_test/external_member_type_parameters_static_test.dart
index 3116e41..9270252 100644
--- a/tests/lib/js/static_interop_test/external_member_type_parameters_static_test.dart
+++ b/tests/lib/js/static_interop_test/external_member_type_parameters_static_test.dart
@@ -18,7 +18,7 @@
 @JS()
 external T invalidTopLevel<T>(T t);
 //         ^
-// [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+// [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
 typedef Typedef<T> = T Function();
 
@@ -29,7 +29,7 @@
 class Uninstantiated<W, X extends Instantiated?> {
   external factory Uninstantiated(W w);
   //               ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'W' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external factory Uninstantiated.named(X x);
 }
 
@@ -37,35 +37,35 @@
     on Uninstantiated {
   external T fieldT;
   //         ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external U fieldU;
   external V fieldV;
 
   T get getTDart => throw UnimplementedError();
   external T get getT;
   //             ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external U get getU;
   external V get getV;
 
   set setTDart(T t) => throw UnimplementedError();
   external set setT(T t);
   //           ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external set setU(U u);
   external set setV(V v);
 
   T returnTDart() => throw UnimplementedError();
   external T returnT();
   //         ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external U returnU();
   external V returnV();
 
   void consumeTDart(T t) => throw UnimplementedError();
   external void consumeT(T t);
   //            ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external void consumeU(U u);
   external void consumeV(V v);
 
@@ -80,7 +80,7 @@
   W returnWDart<W>() => throw UnimplementedError();
   external W returnW<W>();
   //         ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'W' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external X returnX<X extends JSArray>();
 }
 
@@ -88,41 +88,41 @@
     V extends InstantiatedExtensionType>._(JSObject _) {
   external UninstantiatedExtensionType(T t);
   //       ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external factory UninstantiatedExtensionType.fact(U u);
 
   // Test simple type parameters.
   external T fieldT;
   //         ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external U fieldU;
   external V fieldV;
 
   T get getTDart => throw UnimplementedError();
   external T get getT;
   //             ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external U get getU;
   external V get getV;
 
   set setTDart(T t) => throw UnimplementedError();
   external set setT(T t);
   //           ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external set setU(U u);
   external set setV(V v);
 
   T returnTDart() => throw UnimplementedError();
   external T returnT();
   //         ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external U returnU();
   external V returnV();
 
   void consumeTDart(T t) => throw UnimplementedError();
   external void consumeT(T t);
   //            ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external void consumeU(U u);
   external void consumeV(V v);
 
@@ -137,7 +137,7 @@
   W returnWDart<W>() => throw UnimplementedError();
   external W returnW<W>();
   //         ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'W' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external X returnX<X extends JSArray>();
 }
 
@@ -145,7 +145,7 @@
     V extends InstantiatedExtensionType> on UninstantiatedExtensionType<T, U, V> {
   external T get extensionGetT;
   //             ^
-  // [web] External static interop members can only use type parameters that extend either a static interop type or one of the 'dart:js_interop' types.
+  // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
   external U get extensionGetU;
   external V get extensionGetV;
 }
diff --git a/tests/lib/js/static_interop_test/strict_mode_test.dart b/tests/lib/js/static_interop_test/strict_mode_test.dart
index e85820d..b5948a6 100644
--- a/tests/lib/js/static_interop_test/strict_mode_test.dart
+++ b/tests/lib/js/static_interop_test/strict_mode_test.dart
@@ -16,41 +16,41 @@
 class JSClass {
   external factory JSClass(List<int> baz);
   //               ^
-  // [web] Type 'List<int>' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  // [web] Type 'List<int>' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
   external factory JSClass.other(Object blu);
   //               ^
-  // [web] Type 'Object' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  // [web] Type 'Object' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
   external static dynamic foo();
   //                      ^
-  // [web] Type 'dynamic' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  // [web] Type 'dynamic' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
   external static Function get fooGet;
   //                           ^
-  // [web] Type 'Function' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  // [web] Type 'Function' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
   external static set fooSet(void Function() bar);
   //                  ^
-  // [web] Type 'void Function()' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  // [web] Type 'void Function()' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 }
 
 extension JSClassExtension on JSClass {
   external dynamic extFoo();
   //               ^
-  // [web] Type 'dynamic' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  // [web] Type 'dynamic' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
   external JSClass extFoo2(List<Object?> bar);
   //               ^
-  // [web] Type 'List<Object?>' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  // [web] Type 'List<Object?>' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
   external Function get extFooGet;
   //                    ^
-  // [web] Type 'Function' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  // [web] Type 'Function' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
   external set extFooSet(void Function() bar);
   //           ^
-  // [web] Type 'void Function()' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  // [web] Type 'void Function()' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 }
 
 @JS()
@@ -65,20 +65,48 @@
 @JS()
 external void useStaticInteropExtensionType(ExtensionType foo);
 
+void declareTypeParameter<T extends JSAny?>() {}
+
+T declareAndUseTypeParameter<T extends JSAny?>(T t) => t;
+
+T declareAndUseInvalidTypeParameter<T>(T t) => t;
+
 void main() {
-  jsFunctionTest(((double foo) => 4.0.toJS).toJS);
+  ((double foo) => 4.0.toJS).toJS;
 
-  jsFunctionTest(((JSNumber foo) => 4.0).toJS);
+  ((JSNumber foo) => 4.0).toJS;
 
-  jsFunctionTest(((List foo) => 4.0).toJS);
-  //                                 ^
-  // [web] Type 'List<dynamic>' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  ((List foo) => 4.0).toJS;
+  //                  ^
+  // [web] Type 'List<dynamic>' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
-  jsFunctionTest(((JSNumber foo) => () {}).toJS);
-  //                                       ^
-  // [web] Type 'Null Function()' is not a valid type for external `dart:js_interop` APIs. The only valid types are: @staticInterop types, JS types from `dart:js_interop`, void, bool, num, double, int, String, and any extension type that erases to one of these types.
+  ((JSNumber foo) => () {}).toJS;
+  //                        ^
+  // [web] Type 'Null Function()' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
 
-  jsFunctionTest(((((JSNumber foo) => 4.0) as dynamic) as Function).toJS);
-  //                                                                ^
-  // [web] `Function.toJS` requires a statically known function type, but Type 'Function' is not a function type, e.g., `void Function()`.
-}
\ No newline at end of file
+  ((((JSNumber foo) => 4.0) as dynamic) as Function).toJS;
+  //                                                 ^
+  // [web] `Function.toJS` requires a statically known function type, but Type 'Function' is not a precise function type, e.g., `void Function()`.
+
+  void typeParametersTest<T extends JSAny, U extends ExtensionType,
+      V extends JSClass, W, Y>() {
+    ((T t) => t).toJS;
+    ((U u) => u).toJS;
+    ((V v) => v).toJS;
+    ((W w) => w as Y).toJS;
+    //                ^
+    // [web] Type 'W' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
+    // [web] Type 'Y' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
+
+    declareTypeParameter.toJS;
+    //                   ^
+    // [web] Functions converted via `toJS` cannot declare type parameters.
+    declareAndUseTypeParameter.toJS;
+    //                         ^
+    // [web] Functions converted via `toJS` cannot declare type parameters.
+    declareAndUseInvalidTypeParameter.toJS;
+    //                                ^
+    // [web] Functions converted via `toJS` cannot declare type parameters.
+    // [web] Type 'T' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
+  }
+}