diff --git a/CHANGELOG.md b/CHANGELOG.md index 4286394d25b..2f5350bce17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,12 +112,17 @@ #### `package:js` -- **Breaking change**: Classes with the preview annotation `@staticInterop` are - now disallowed from using `external` generative constructors. Use - `external factory`s for these classes instead. See [#48730][] for more - details. +- **Breaking changes to the preview feature `@staticInterop`**: + - Classes with this annotation are now disallowed from using `external` + generative constructors. Use `external factory`s for these classes instead, + and the behavior should be identical. See [#48730][] for more details. + - Classes with this annotation's external extension members are now disallowed + from using type parameters e.g. `external void method(T t)`. Use a + non-`external` extension method for type parameters instead. See [#49350][] + for more details. [#48730]: https://github.com/dart-lang/sdk/issues/48730 +[#49350]: https://github.com/dart-lang/sdk/issues/49350 ### Tools 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 bcca4e58391..2ec952dc7d1 100644 --- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart +++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart @@ -7121,6 +7121,22 @@ const MessageCode messageJsInteropOperatorsNotSupported = const MessageCode( problemMessage: r"""JS interop classes do not support operator methods.""", correctionMessage: r"""Try replacing this with a normal method."""); +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code + codeJsInteropStaticInteropExternalExtensionMembersWithTypeParameters = + messageJsInteropStaticInteropExternalExtensionMembersWithTypeParameters; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode + messageJsInteropStaticInteropExternalExtensionMembersWithTypeParameters = + const MessageCode( + "JsInteropStaticInteropExternalExtensionMembersWithTypeParameters", + problemMessage: + r"""`@staticInterop` classes cannot have external extension members with type parameters.""", + correctionMessage: + r"""Try using a Dart extension member if you need type parameters instead."""); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const Code 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 0e2418e291b..2e96dcaae1a 100644 --- a/pkg/_js_interop_checks/lib/js_interop_checks.dart +++ b/pkg/_js_interop_checks/lib/js_interop_checks.dart @@ -19,6 +19,7 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart' messageJsInteropNonExternalConstructor, messageJsInteropNonExternalMember, messageJsInteropOperatorsNotSupported, + messageJsInteropStaticInteropExternalExtensionMembersWithTypeParameters, messageJsInteropStaticInteropGenerativeConstructor, templateJsInteropDartClassExtendsJSClass, templateJsInteropStaticInteropWithInstanceMembers, @@ -34,6 +35,7 @@ class JsInteropChecks extends RecursiveVisitor { final CoreTypes _coreTypes; final DiagnosticReporter _diagnosticsReporter; final Map _nativeClasses; + final _TypeParameterVisitor _typeParameterVisitor = _TypeParameterVisitor(); bool _classHasJSAnnotation = false; bool _classHasAnonymousAnnotation = false; bool _classHasStaticInteropAnnotation = false; @@ -301,6 +303,26 @@ class JsInteropChecks extends RecursiveVisitor { procedure.name.text.length, procedure.fileUri); } + + if (procedure.isExternal && + procedure.isExtensionMember && + _isStaticInteropExtensionMember(procedure)) { + // If the extension has type parameters of its own, it copies those type + // parameters to the procedure's type parameters (in the front) as well. + // Ignore these for the analysis. + var extensionTypeParams = + _libraryExtensionsIndex![procedure.reference]!.typeParameters; + var procedureTypeParams = List.from(procedure.function.typeParameters); + procedureTypeParams.removeRange(0, extensionTypeParams.length); + if (procedureTypeParams.isNotEmpty || + _typeParameterVisitor.usesTypeParameters(procedure)) { + _diagnosticsReporter.report( + messageJsInteropStaticInteropExternalExtensionMembersWithTypeParameters, + procedure.fileOffset, + procedure.name.text.length, + procedure.fileUri); + } + } } @override @@ -450,6 +472,12 @@ class JsInteropChecks extends RecursiveVisitor { return _checkExtensionMember(member, hasJSInteropAnnotation); } + /// Returns whether given extension [member] is in an extension that is on a + /// `@staticInterop` class. + bool _isStaticInteropExtensionMember(Member member) { + return _checkExtensionMember(member, hasStaticInteropAnnotation); + } + /// Returns whether given extension [member] is in an extension on a Native /// class. bool _isNativeExtensionMember(Member member) { @@ -471,3 +499,18 @@ class JsInteropChecks extends RecursiveVisitor { return onType is InterfaceType && validateExtensionClass(onType.classNode); } } + +class _TypeParameterVisitor extends RecursiveVisitor { + bool _visitedTypeParameterType = false; + + bool usesTypeParameters(Node node) { + _visitedTypeParameterType = false; + node.accept(this); + return _visitedTypeParameterType; + } + + @override + void visitTypeParameterType(TypeParameterType node) { + _visitedTypeParameterType = true; + } +} diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status index cc0ea9a937e..6eee561eca7 100644 --- a/pkg/front_end/messages.status +++ b/pkg/front_end/messages.status @@ -579,6 +579,8 @@ JsInteropNonExternalMember/analyzerCode: Fail # Web compiler specific JsInteropNonExternalMember/example: Fail # Web compiler specific JsInteropOperatorsNotSupported/analyzerCode: Fail # Web compiler specific JsInteropOperatorsNotSupported/example: Fail # Web compiler specific +JsInteropStaticInteropExternalExtensionMembersWithTypeParameters/analyzerCode: Fail # Web compiler specific +JsInteropStaticInteropExternalExtensionMembersWithTypeParameters/example: Fail # Web compiler specific JsInteropStaticInteropGenerativeConstructor/analyzerCode: Fail # Web compiler specific JsInteropStaticInteropGenerativeConstructor/example: Fail # Web compiler specific JsInteropStaticInteropMockExternalExtensionMemberConflict/analyzerCode: Fail # Web compiler specific diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index 2269c6f82c8..82818f6c52a 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -5266,6 +5266,10 @@ JsInteropStaticInteropMockNotStaticInteropType: problemMessage: "First type argument '#type' is not a `@staticInterop` type." correctionMessage: "Use a `@staticInterop` class instead." +JsInteropStaticInteropExternalExtensionMembersWithTypeParameters: + problemMessage: "`@staticInterop` classes cannot have external extension members with type parameters." + correctionMessage: "Try using a Dart extension member if you need type parameters instead." + JsInteropStaticInteropWithInstanceMembers: problemMessage: "JS interop class '#name' with `@staticInterop` annotation cannot declare instance members." correctionMessage: "Try moving the instance member to a static extension." diff --git a/tests/lib/js/static_interop_test/external_extension_member_type_parameters_static_test.dart b/tests/lib/js/static_interop_test/external_extension_member_type_parameters_static_test.dart new file mode 100644 index 00000000000..3fbb3a8fb3e --- /dev/null +++ b/tests/lib/js/static_interop_test/external_extension_member_type_parameters_static_test.dart @@ -0,0 +1,89 @@ +// Copyright (c) 2022, 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. + +@JS() +library external_extension_member_type_parameters_static_test; + +import 'package:js/js.dart'; + +@JS() +@staticInterop +class Uninstantiated {} + +typedef TypedefT = T Function(); + +extension E1 on Uninstantiated { + // Test simple type parameters. + external T fieldT; + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external T get getT; + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external set setT(T t); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external T returnT(); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external void consumeT(T t); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + + // Test type parameters in a nested type context. + external List fieldNestedT; + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external void Function(T) get getNestedT; + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external set setNestedT(TypedefT nestedT); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external List> returnNestedT(); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external void consumeNestedT(Set> nestedT); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + + // Test type parameters that are declared by the member. + external U returnU(); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external void consumeU(U u); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. +} + +@JS() +@staticInterop +class Instantiated {} + +extension E2 on Instantiated { + // Test generic types where there all the type parameters are instantiated. + external List fieldList; + external List get getList; + external set setList(List list); + external List returnList(); + external void consumeList(List list); +} + +// Extension members that don't declare or use type parameters should not be +// affected by whether their extension declares a type parameter. +@JS() +@staticInterop +class ExtensionWithTypeParams {} + +extension E3 on ExtensionWithTypeParams { + external void noTypeParams(); + external void declareTypeParam(U u); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external void useTypeParam(T t); + // ^ + // [web] `@staticInterop` classes cannot have external extension members with type parameters. +} + +void main() {} diff --git a/tests/lib_2/js/static_interop_test/external_extension_member_type_parameters_static_test.dart b/tests/lib_2/js/static_interop_test/external_extension_member_type_parameters_static_test.dart new file mode 100644 index 00000000000..c9722db71db --- /dev/null +++ b/tests/lib_2/js/static_interop_test/external_extension_member_type_parameters_static_test.dart @@ -0,0 +1,68 @@ +// Copyright (c) 2022, 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. + +// @dart = 2.9 + +@JS() +library external_extension_member_type_parameters_static_test; + +import 'package:js/js.dart'; + +@JS() +@staticInterop +class Uninstantiated {} + +typedef TypedefT = T Function(); + +extension E1 on Uninstantiated { + // Test simple type parameters. + external T get getT; + // [error column 18] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external set setT(T t); + // [error column 16] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external T returnT(); + // [error column 14] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external void consumeT(T t); + // [error column 17] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + + // Test type parameters in a nested type context. + external void Function(T) get getNestedT; + // [error column 33] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external set setNestedT(TypedefT nestedT); + // [error column 16] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external List> returnNestedT(); + // [error column 28] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external void consumeNestedT(Set> nestedT); + // [error column 17] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + + // Test type parameters that are declared by the member. + external U returnU(); + // [error column 14] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. + external void consumeU(U u); + // [error column 17] + // [web] `@staticInterop` classes cannot have external extension members with type parameters. +} + +@JS() +@staticInterop +class Instantiated {} + +extension E2 on Instantiated { + // Test generic types where there all the type parameters are instantiated. + external List get getList; + external set setList(List list); + external List returnList(); + external void consumeList(List list); +} + +void main() {}