[pkg:js] Disallow external extension members with type parameters

Bug: https://github.com/dart-lang/sdk/issues/49350

Checks to see that external extension members on `@staticInterop` types
do not declare or use a type parameter.

Change-Id: Id8646b599094b748c5490810b64d872065676014
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/254103
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Joshua Litt <joshualitt@google.com>
This commit is contained in:
Srujan Gaddam 2022-10-18 17:47:29 +00:00 committed by Commit Queue
parent b6526beeab
commit 521cbcdd47
7 changed files with 231 additions and 4 deletions

View file

@ -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 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

View file

@ -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<Null>
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<Null> codeJsInteropStaticInteropGenerativeConstructor =
messageJsInteropStaticInteropGenerativeConstructor;

View file

@ -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<Message, LocatedMessage> _diagnosticsReporter;
final Map<String, Class> _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;
}
}

View file

@ -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

View file

@ -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."

View file

@ -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> = T Function();
extension E1<T> 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<T> 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<T> nestedT);
// ^
// [web] `@staticInterop` classes cannot have external extension members with type parameters.
external List<Map<T, T>> returnNestedT();
// ^
// [web] `@staticInterop` classes cannot have external extension members with type parameters.
external void consumeNestedT(Set<TypedefT<T>> nestedT);
// ^
// [web] `@staticInterop` classes cannot have external extension members with type parameters.
// Test type parameters that are declared by the member.
external U returnU<U>();
// ^
// [web] `@staticInterop` classes cannot have external extension members with type parameters.
external void consumeU<U>(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<int> fieldList;
external List<int> get getList;
external set setList(List<int> list);
external List<int> returnList();
external void consumeList(List<int> 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<T> on ExtensionWithTypeParams {
external void noTypeParams();
external void declareTypeParam<U>(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() {}

View file

@ -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> = T Function();
extension E1<T> 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<T> nestedT);
// [error column 16]
// [web] `@staticInterop` classes cannot have external extension members with type parameters.
external List<Map<T, T>> returnNestedT();
// [error column 28]
// [web] `@staticInterop` classes cannot have external extension members with type parameters.
external void consumeNestedT(Set<TypedefT<T>> 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<U>();
// [error column 14]
// [web] `@staticInterop` classes cannot have external extension members with type parameters.
external void consumeU<U>(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<int> get getList;
external set setList(List<int> list);
external List<int> returnList();
external void consumeList(List<int> list);
}
void main() {}