Skip to content

Commit 7d6414a

Browse files
srujzsCommit Queue
authored andcommitted
[dart:js_interop] Restrict external members from using type parameters that don't extend static interop types
Since external APIs can only use primitives and JS types in static interop, we should require that all type parameters on static interop APIs extend another static interop type. This is the minimum required to ensure all type parameters can be erased to JSValue. This only affects dart:js_interop users and replaces the previous type parameter static error check. Change-Id: Ia546874da73c808aa25deb8d54d581db783987df Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/316140 Reviewed-by: Joshua Litt <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Srujan Gaddam <[email protected]>
1 parent 2e23d29 commit 7d6414a

28 files changed

+423
-328
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
`ObjectToJSExportedDartObject` and `JSExportedDartObjectToObject` are renamed
1717
to `ObjectToJSBoxedDartObject` and `JSBoxedDartObjectToObject` in order to
1818
avoid confusion with `@JSExport`.
19+
- **Type parameters in external APIs**:
20+
Type parameters must now be bound to a static interop type or one of the
21+
`dart:js_interop` types like `JSNumber` when used in an external API. This
22+
only affects `dart:js_interop` classes and not `package:js` or other forms of
23+
JS interop.
1924

2025
## 3.1.0
2126

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8182,18 +8182,18 @@ const MessageCode messageJsInteropOperatorsNotSupported = const MessageCode(
81828182

81838183
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
81848184
const Code<Null>
8185-
codeJsInteropStaticInteropExternalExtensionMembersWithTypeParameters =
8186-
messageJsInteropStaticInteropExternalExtensionMembersWithTypeParameters;
8185+
codeJsInteropStaticInteropExternalMemberWithInvalidTypeParameters =
8186+
messageJsInteropStaticInteropExternalMemberWithInvalidTypeParameters;
81878187

81888188
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
81898189
const MessageCode
8190-
messageJsInteropStaticInteropExternalExtensionMembersWithTypeParameters =
8190+
messageJsInteropStaticInteropExternalMemberWithInvalidTypeParameters =
81918191
const MessageCode(
8192-
"JsInteropStaticInteropExternalExtensionMembersWithTypeParameters",
8192+
"JsInteropStaticInteropExternalMemberWithInvalidTypeParameters",
81938193
problemMessage:
8194-
r"""`@staticInterop` classes cannot have external extension members with type parameters.""",
8194+
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.""",
81958195
correctionMessage:
8196-
r"""Try using a Dart extension member if you need type parameters instead.""");
8196+
r"""Try adding a valid bound to the type parameters used in this member.""");
81978197

81988198
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
81998199
const Code<Null> codeJsInteropStaticInteropGenerativeConstructor =

pkg/_js_interop_checks/lib/js_interop_checks.dart

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart'
2222
messageJsInteropNonExternalMember,
2323
messageJsInteropOperatorCannotBeRenamed,
2424
messageJsInteropOperatorsNotSupported,
25-
messageJsInteropStaticInteropExternalExtensionMembersWithTypeParameters,
25+
messageJsInteropStaticInteropExternalMemberWithInvalidTypeParameters,
2626
messageJsInteropStaticInteropGenerativeConstructor,
2727
messageJsInteropStaticInteropParameterInitializersAreIgnored,
2828
messageJsInteropStaticInteropSyntheticConstructor,
@@ -68,7 +68,7 @@ class JsInteropChecks extends RecursiveVisitor {
6868
final Map<String, Class> _nativeClasses;
6969
final JsInteropDiagnosticReporter _reporter;
7070
final StatefulStaticTypeContext _staticTypeContext;
71-
final _TypeParameterVisitor _typeParameterVisitor = _TypeParameterVisitor();
71+
late _TypeParameterBoundChecker _typeParameterBoundChecker;
7272
bool _classHasJSAnnotation = false;
7373
bool _classHasAnonymousAnnotation = false;
7474
bool _classHasStaticInteropAnnotation = false;
@@ -151,6 +151,8 @@ class JsInteropChecks extends RecursiveVisitor {
151151
TypeEnvironment(_coreTypes, hierarchy)) {
152152
_inlineExtensionIndex =
153153
InlineExtensionIndex(_coreTypes, _staticTypeContext.typeEnvironment);
154+
_typeParameterBoundChecker =
155+
_TypeParameterBoundChecker(_inlineExtensionIndex);
154156
}
155157

156158
/// Verifies given [member] is an external extension member on a static
@@ -385,23 +387,29 @@ class JsInteropChecks extends RecursiveVisitor {
385387
(hasDartJSInteropAnnotation(node) ||
386388
_libraryHasDartJSInteropAnnotation)) {
387389
_checkNoParamInitializersForStaticInterop(node.function);
388-
if (node.isExtensionMember) {
389-
final annotatable =
390-
_inlineExtensionIndex.getExtensionAnnotatable(node);
390+
late Annotatable? annotatable;
391+
if (node.isInlineClassMember) {
392+
annotatable = _inlineExtensionIndex.getInlineClass(node);
393+
} else if (node.isExtensionMember) {
394+
annotatable = _inlineExtensionIndex.getExtensionAnnotatable(node);
391395
if (annotatable != null) {
392-
// If a @staticInterop member, check that it uses no type
393-
// parameters.
394-
if (hasStaticInteropAnnotation(annotatable) &&
395-
!isAllowedCustomStaticInteropImplementation(node)) {
396-
_checkStaticInteropMemberUsesNoTypeParameters(node);
397-
}
398396
// We do not support external extension members with the 'static'
399397
// keyword currently.
400398
if (_inlineExtensionIndex.getExtensionDescriptor(node)!.isStatic) {
401399
report(
402400
messageJsInteropExternalExtensionMemberWithStaticDisallowed);
403401
}
404402
}
403+
} else {
404+
annotatable = node.enclosingClass;
405+
}
406+
if (!isAllowedCustomStaticInteropImplementation(node)) {
407+
if (annotatable == null ||
408+
((hasDartJSInteropAnnotation(annotatable) ||
409+
annotatable is InlineClass))) {
410+
// Only restrict type parameters for dart:js_interop.
411+
_checkStaticInteropMemberUsesValidTypeParameters(node);
412+
}
405413
}
406414
}
407415
}
@@ -839,18 +847,10 @@ class JsInteropChecks extends RecursiveVisitor {
839847
}
840848
}
841849

842-
void _checkStaticInteropMemberUsesNoTypeParameters(Procedure node) {
843-
// If the extension has type parameters of its own, it copies those type
844-
// parameters to the procedure's type parameters (in the front) as well.
845-
// Ignore these for the analysis.
846-
final extensionTypeParams =
847-
_inlineExtensionIndex.getExtension(node)!.typeParameters;
848-
final procedureTypeParams = List.from(node.function.typeParameters);
849-
procedureTypeParams.removeRange(0, extensionTypeParams.length);
850-
if (procedureTypeParams.isNotEmpty ||
851-
_typeParameterVisitor.usesTypeParameters(node)) {
850+
void _checkStaticInteropMemberUsesValidTypeParameters(Procedure node) {
851+
if (_typeParameterBoundChecker.containsInvalidTypeBound(node)) {
852852
_reporter.report(
853-
messageJsInteropStaticInteropExternalExtensionMembersWithTypeParameters,
853+
messageJsInteropStaticInteropExternalMemberWithInvalidTypeParameters,
854854
node.fileOffset,
855855
node.name.text.length,
856856
node.fileUri);
@@ -935,7 +935,8 @@ class JsInteropChecks extends RecursiveVisitor {
935935
(type is InterfaceType &&
936936
hasStaticInteropAnnotation(type.classNode)) ||
937937
(type is InlineType &&
938-
hasDartJSInteropAnnotation(type.inlineClass)))) {
938+
_inlineExtensionIndex
939+
.isInteropInlineClass(type.inlineClass)))) {
939940
_reporter.report(
940941
templateJsInteropStrictModeViolation.withArguments(type, true),
941942
node.fileOffset,
@@ -952,19 +953,49 @@ class JsInteropChecks extends RecursiveVisitor {
952953
_reportIfNotJSType(type, node, node.name, node.location?.file);
953954
}
954955

955-
/// Visitor used to check if a particular node uses a type parameter type.
956-
class _TypeParameterVisitor extends RecursiveVisitor {
957-
bool _visitedTypeParameterType = false;
956+
/// Visitor used to check that all usages of type parameter types of an external
957+
/// static interop member is a valid static interop type.
958+
class _TypeParameterBoundChecker extends RecursiveVisitor {
959+
final InlineExtensionIndex _inlineExtensionIndex;
960+
961+
_TypeParameterBoundChecker(this._inlineExtensionIndex);
962+
963+
bool _containsInvalidTypeBound = false;
964+
965+
bool containsInvalidTypeBound(Procedure node) {
966+
_containsInvalidTypeBound = false;
967+
final function = node.function;
968+
for (final param in function.positionalParameters) {
969+
param.accept(this);
970+
}
971+
function.returnType.accept(this);
972+
return _containsInvalidTypeBound;
973+
}
974+
975+
@override
976+
void visitInterfaceType(InterfaceType node) {
977+
final cls = node.classNode;
978+
if (hasStaticInteropAnnotation(cls)) return;
979+
super.visitInterfaceType(node);
980+
}
958981

959-
bool usesTypeParameters(Node node) {
960-
_visitedTypeParameterType = false;
961-
node.accept(this);
962-
return _visitedTypeParameterType;
982+
@override
983+
void visitInlineType(InlineType node) {
984+
if (_inlineExtensionIndex.isInteropInlineClass(node.inlineClass)) return;
985+
super.visitInlineType(node);
963986
}
964987

965988
@override
966989
void visitTypeParameterType(TypeParameterType node) {
967-
_visitedTypeParameterType = true;
990+
final bound = node.bound;
991+
if (bound is InlineType &&
992+
!_inlineExtensionIndex.isInteropInlineClass(bound.inlineClass)) {
993+
_containsInvalidTypeBound = true;
994+
}
995+
if (bound is InterfaceType &&
996+
!hasStaticInteropAnnotation(bound.classNode)) {
997+
_containsInvalidTypeBound = true;
998+
}
968999
}
9691000
}
9701001

pkg/front_end/messages.status

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,8 +619,8 @@ JsInteropOperatorsNotSupported/analyzerCode: Fail # Web compiler specific
619619
JsInteropOperatorsNotSupported/example: Fail # Web compiler specific
620620
JsInteropObjectLiteralConstructorPositionalParameters/analyzerCode: Fail # Web compiler specific
621621
JsInteropObjectLiteralConstructorPositionalParameters/example: Fail # Web compiler specific
622-
JsInteropStaticInteropExternalExtensionMembersWithTypeParameters/analyzerCode: Fail # Web compiler specific
623-
JsInteropStaticInteropExternalExtensionMembersWithTypeParameters/example: Fail # Web compiler specific
622+
JsInteropStaticInteropExternalMemberWithInvalidTypeParameters/analyzerCode: Fail # Web compiler specific
623+
JsInteropStaticInteropExternalMemberWithInvalidTypeParameters/example: Fail # Web compiler specific
624624
JsInteropStaticInteropGenerativeConstructor/analyzerCode: Fail # Web compiler specific
625625
JsInteropStaticInteropGenerativeConstructor/example: Fail # Web compiler specific
626626
JsInteropStaticInteropMockMissingGetterOrSetter/analyzerCode: Fail # Web compiler specific

pkg/front_end/messages.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5552,9 +5552,9 @@ JsInteropNonStaticWithStaticInteropSupertype:
55525552
problemMessage: "Class '#name' does not have an `@staticInterop` annotation, but has supertype '#name2', which does."
55535553
correctionMessage: "Try marking '#name' as a `@staticInterop` class, or don't inherit '#name2'."
55545554

5555-
JsInteropStaticInteropExternalExtensionMembersWithTypeParameters:
5556-
problemMessage: "`@staticInterop` classes cannot have external extension members with type parameters."
5557-
correctionMessage: "Try using a Dart extension member if you need type parameters instead."
5555+
JsInteropStaticInteropExternalMemberWithInvalidTypeParameters:
5556+
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."
5557+
correctionMessage: "Try adding a valid bound to the type parameters used in this member."
55585558

55595559
JsInteropStaticInteropGenerativeConstructor:
55605560
problemMessage: "`@staticInterop` classes should not contain any generative constructors."

pkg/front_end/testcases/dart2js/inline_class/external.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ inline class B {
2323

2424
external A method();
2525

26-
external T genericMethod<T>(T t);
26+
// TODO: Once https://github.com/dart-lang/sdk/issues/53046 is resolved,
27+
// uncomment this, the static variant below, and the usage of the members.
28+
// external T genericMethod<T extends B>(T t);
2729

2830
external B get getter;
2931

@@ -33,7 +35,7 @@ inline class B {
3335

3436
external static A staticMethod();
3537

36-
external static T staticGenericMethod<T>(T t);
38+
// external static T staticGenericMethod<T extends B>(T t);
3739

3840
external static B get staticGetter;
3941

@@ -46,13 +48,13 @@ void method(A a) {
4648
a = b1.field;
4749
b1.field = a;
4850
a = b1.method();
49-
b2 = b2.genericMethod(b2);
51+
// b2 = b2.genericMethod(b2);
5052
b1 = b2.getter;
5153
b1.setter = b2;
5254
a = B.staticField;
5355
B.staticField = a;
5456
a = B.staticMethod();
55-
b2 = B.staticGenericMethod(b2);
57+
// b2 = B.staticGenericMethod(b2);
5658
b1 = B.staticGetter;
5759
B.staticSetter = b2;
5860
}

pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.expect

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,10 @@ inline class B /* declaredRepresentationType = self::A */ {
2222
set field = self::B|set#field;
2323
method method = self::B|method;
2424
tearoff method = self::B|get#method;
25-
method genericMethod = self::B|genericMethod;
26-
tearoff genericMethod = self::B|get#genericMethod;
2725
get getter = self::B|get#getter;
2826
static get staticField = get self::B|staticField;
2927
static set staticField = set self::B|staticField;
3028
static method staticMethod = self::B|staticMethod;
31-
static method staticGenericMethod = self::B|staticGenericMethod;
3229
static get staticGetter = get self::B|staticGetter;
3330
set setter = self::B|set#setter;
3431
static set staticSetter = set self::B|staticSetter;
@@ -48,15 +45,11 @@ external static inline-class-member method B|set#field(lowered self::A #this, se
4845
external static inline-class-member method B|method(lowered final self::B #this) → self::A;
4946
static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
5047
return () → self::A => self::B|method(#this);
51-
external static inline-class-member method B|genericMethod<T extends core::Object? = dynamic>(lowered final self::B #this, self::B|genericMethod::T% t) → self::B|genericMethod::T%;
52-
static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends core::Object? = dynamic>(T%) → T%
53-
return <T extends core::Object? = dynamic>(T% t) → T% => self::B|genericMethod<T%>(#this, t);
5448
external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
5549
external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
5650
external static inline-class-member get B|staticField() → self::A;
5751
external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
5852
external static inline-class-member method B|staticMethod() → self::A;
59-
external static inline-class-member method B|staticGenericMethod<T extends core::Object? = dynamic>(self::B|staticGenericMethod::T% t) → self::B|staticGenericMethod::T%;
6053
external static inline-class-member get B|staticGetter() → self::B;
6154
external static inline-class-member set B|staticSetter(self::B b) → void;
6255
static method method(self::A a) → void {
@@ -65,13 +58,11 @@ static method method(self::A a) → void {
6558
a = self::B|get#field(b1);
6659
self::B|set#field(b1, a);
6760
a = self::B|method(b1);
68-
b2 = self::B|genericMethod<self::B>(b2, b2);
6961
b1 = self::B|get#getter(b2);
7062
self::B|set#setter(b1, b2);
7163
a = self::B|staticField;
7264
self::B|staticField = a;
7365
a = self::B|staticMethod();
74-
b2 = self::B|staticGenericMethod<self::B>(b2);
7566
b1 = self::B|staticGetter;
7667
self::B|staticSetter = b2;
7768
}

pkg/front_end/testcases/dart2js/inline_class/external.dart.strong.transformed.expect

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,10 @@ inline class B /* declaredRepresentationType = self::A */ {
2323
set field = self::B|set#field;
2424
method method = self::B|method;
2525
tearoff method = self::B|get#method;
26-
method genericMethod = self::B|genericMethod;
27-
tearoff genericMethod = self::B|get#genericMethod;
2826
get getter = self::B|get#getter;
2927
static get staticField = get self::B|staticField;
3028
static set staticField = set self::B|staticField;
3129
static method staticMethod = self::B|staticMethod;
32-
static method staticGenericMethod = self::B|staticGenericMethod;
3330
static get staticGetter = get self::B|staticGetter;
3431
set setter = self::B|set#setter;
3532
static set staticSetter = set self::B|staticSetter;
@@ -49,15 +46,11 @@ external static inline-class-member method B|set#field(lowered self::A #this, se
4946
external static inline-class-member method B|method(lowered final self::B #this) → self::A;
5047
static inline-class-member method B|get#method(lowered final self::B #this) → () → self::A
5148
return () → self::A => js_2::_callMethodUnchecked0<self::A>(#this, "method");
52-
external static inline-class-member method B|genericMethod<T extends core::Object? = dynamic>(lowered final self::B #this, self::B|genericMethod::T% t) → self::B|genericMethod::T%;
53-
static inline-class-member method B|get#genericMethod(lowered final self::B #this) → <T extends core::Object? = dynamic>(T%) → T%
54-
return <T extends core::Object? = dynamic>(T% t) → T% => js_2::callMethod<T%>(#this, "genericMethod", <dynamic>[t]);
5549
external static inline-class-member method B|get#getter(lowered final self::B #this) → self::B;
5650
external static inline-class-member method B|set#setter(lowered final self::B #this, self::B b) → void;
5751
external static inline-class-member get B|staticField() → self::A;
5852
external static inline-class-member set B|staticField(self::A #externalFieldValue) → void;
5953
external static inline-class-member method B|staticMethod() → self::A;
60-
external static inline-class-member method B|staticGenericMethod<T extends core::Object? = dynamic>(self::B|staticGenericMethod::T% t) → self::B|staticGenericMethod::T%;
6154
external static inline-class-member get B|staticGetter() → self::B;
6255
external static inline-class-member set B|staticSetter(self::B b) → void;
6356
static method method(self::A a) → void {
@@ -66,13 +59,11 @@ static method method(self::A a) → void {
6659
a = js_2::getProperty<self::A>(b1, "field");
6760
js_2::setProperty<self::A>(b1, "field", a);
6861
a = js_2::_callMethodUnchecked0<self::A>(b1, "method");
69-
b2 = js_2::callMethod<self::B>(b2, "genericMethod", <dynamic>[b2]);
7062
b1 = js_2::getProperty<self::B>(b2, "getter");
7163
js_2::setProperty<self::B>(b1, "setter", b2);
7264
a = js_2::getProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField");
7365
js_2::setProperty<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticField", a);
7466
a = js_2::_callMethodUnchecked0<self::A>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticMethod");
75-
b2 = js_2::callMethod<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGenericMethod", <dynamic>[b2]);
7667
b1 = js_2::getProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticGetter");
7768
js_2::setProperty<self::B>(js_2::_getPropertyTrustType<core::Object>(js_2::globalThis, "B"), "staticSetter", b2);
7869
}
Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
11
@JS()
22
library static_interop;
3-
43
import 'dart:js_interop';
5-
64
@JS()
75
@staticInterop
86
class A {}
9-
107
@JS()
118
inline class B {
129
final A a;
1310
external B(A a);
1411
external B.named(int i);
1512
external A field;
1613
external A method();
17-
external T genericMethod<T>(T t);
1814
external B get getter;
1915
external void set setter(B b);
2016
external static A staticField;
2117
external static A staticMethod();
22-
external static T staticGenericMethod<T>(T t);
2318
external static B get staticGetter;
2419
external static void set staticSetter(B b);
2520
}
26-
2721
void method(A a) {}

pkg/front_end/testcases/dart2js/inline_class/external.dart.textual_outline_modelled.expect

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ inline class B {
1414
external B(A a);
1515
external B.named(int i);
1616
external B get getter;
17-
external T genericMethod<T>(T t);
17+
external T genericMethod<T extends B>(T t);
1818
external static A staticField;
1919
external static A staticMethod();
2020
external static B get staticGetter;
21-
external static T staticGenericMethod<T>(T t);
21+
external static T staticGenericMethod<T extends B>(T t);
2222
external static void set staticSetter(B b);
2323
external void set setter(B b);
2424
final A a;

0 commit comments

Comments
 (0)