Skip to content

Commit 1232260

Browse files
srujzsCommit Queue
authored and
Commit Queue
committed
[dart:js_interop] Add ExternalDartReference
Closes #55187 Adds a faster way for users to pass opaque Dart values to JS without the need for boxing like in JSBoxedDartObject. This does mean, however, that this new type can't be a JS type, and therefore cannot have interop members declared on it. Refactors existing code to handle that distinction. CoreLibraryReviewExempt: Backend-specific library that's been reviewed by both dart2wasm and JS compiler teams. Change-Id: Ia86f1fe3476512fc0e5f382e05739713b687f092 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358224 Reviewed-by: Sigmund Cherem <[email protected]>
1 parent bcf64bd commit 1232260

File tree

18 files changed

+275
-45
lines changed

18 files changed

+275
-45
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,15 @@ advantage of these improvements, set your package's
4646
- Fixes an issue with several comparison operators in `JSAnyOperatorExtension`
4747
that were declared to return `JSBoolean` but really returned `bool`. This led
4848
to runtime errors when trying to use the return values. The implementation now
49-
returns a `JSBoolean` to align with the interface. See issues [#55024] for
49+
returns a `JSBoolean` to align with the interface. See issue [#55024] for
5050
more details.
51+
- Added `ExternalDartReference` and related conversion functions
52+
`toExternalReference` and `toDartObject`. This is a faster alternative to
53+
`JSBoxedDartObject`, but with fewer safety guarantees and fewer
54+
interoperability capabilities. See [#55187] for more details.
5155

5256
[#55024]: https://github.com/dart-lang/sdk/issues/55024
57+
[#55187]: https://github.com/dart-lang/sdk/issues/55187
5358

5459
### Tools
5560

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10698,7 +10698,7 @@ const Template<Message Function(String string2)>
1069810698
problemMessageTemplate:
1069910699
r"""External JS interop member contains invalid types in its function signature: '#string2'.""",
1070010700
correctionMessageTemplate:
10701-
r"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
10701+
r"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
1070210702
withArguments:
1070310703
_withArgumentsJsInteropStaticInteropExternalFunctionTypeViolation,
1070410704
);
@@ -10719,7 +10719,7 @@ Message _withArgumentsJsInteropStaticInteropExternalFunctionTypeViolation(
1071910719
problemMessage:
1072010720
"""External JS interop member contains invalid types in its function signature: '${string2}'.""",
1072110721
correctionMessage:
10722-
"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
10722+
"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
1072310723
arguments: {
1072410724
'string2': string2,
1072510725
},
@@ -10938,7 +10938,7 @@ const Template<Message Function(String string2)>
1093810938
problemMessageTemplate:
1093910939
r"""Function converted via 'toJS' contains invalid types in its function signature: '#string2'.""",
1094010940
correctionMessageTemplate:
10941-
r"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
10941+
r"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
1094210942
withArguments: _withArgumentsJsInteropStaticInteropToJSFunctionTypeViolation,
1094310943
);
1094410944

@@ -10958,7 +10958,7 @@ Message _withArgumentsJsInteropStaticInteropToJSFunctionTypeViolation(
1095810958
problemMessage:
1095910959
"""Function converted via 'toJS' contains invalid types in its function signature: '${string2}'.""",
1096010960
correctionMessage:
10961-
"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
10961+
"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
1096210962
arguments: {
1096310963
'string2': string2,
1096410964
},

pkg/_js_interop_checks/lib/js_interop_checks.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,9 @@ class JsInteropChecks extends RecursiveVisitor {
927927
// If it can be used as a representation type of an interop extension type,
928928
// it is okay to be used on an external member.
929929
if (extensionIndex.isAllowedRepresentationType(type)) return true;
930+
// ExternalDartReference is allowed on interop members even though it's not
931+
// an interop type.
932+
if (extensionIndex.isExternalDartReferenceType(type)) return true;
930933
if (type is InterfaceType) {
931934
final cls = type.classNode;
932935
// Primitive types are okay.
@@ -939,8 +942,8 @@ class JsInteropChecks extends RecursiveVisitor {
939942
}
940943
} else if (type is ExtensionType) {
941944
// Extension types that wrap other allowed types are also okay. Interop
942-
// extension types are handled above, so this is essentially for extension
943-
// types on primitives.
945+
// extension types and ExternalDartReference are handled above, so this is
946+
// essentially for extension types on primitives.
944947
return _isAllowedExternalType(type.extensionTypeErasure);
945948
}
946949
return false;

pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@ class ExtensionIndex {
697697
final Map<Reference, ExtensionTypeMemberDescriptor>
698698
_extensionTypeMemberIndex = {};
699699
final Map<Reference, Reference> _extensionTypeTearOffIndex = {};
700+
final Set<Reference> _externalDartReferences = {};
700701
final Class? _javaScriptObject;
701702
final Set<Library> _processedExtensionLibraries = {};
702703
final Set<Library> _processedExtensionTypeLibraries = {};
@@ -1013,5 +1014,23 @@ class ExtensionIndex {
10131014
}
10141015

10151016
bool isJSType(ExtensionTypeDeclaration decl) =>
1016-
decl.enclosingLibrary.importUri.toString() == 'dart:js_interop';
1017+
decl.enclosingLibrary.importUri.toString() == 'dart:js_interop' &&
1018+
decl.name.startsWith('JS');
1019+
1020+
bool isExternalDartReference(ExtensionTypeDeclaration decl) =>
1021+
decl.enclosingLibrary.importUri.toString() == 'dart:js_interop' &&
1022+
decl.name == 'ExternalDartReference';
1023+
1024+
bool isExternalDartReferenceType(DartType type) {
1025+
if (type is ExtensionType) {
1026+
final decl = type.extensionTypeDeclaration;
1027+
if (_externalDartReferences.contains(decl.reference)) return true;
1028+
if (isExternalDartReference(decl) ||
1029+
isExternalDartReferenceType(decl.declaredRepresentationType)) {
1030+
_externalDartReferences.add(decl.reference);
1031+
return true;
1032+
}
1033+
}
1034+
return false;
1035+
}
10171036
}

pkg/dart2wasm/lib/js/callback_specializer.dart

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
4-
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
5-
show ExtensionIndex;
4+
65
import 'package:kernel/ast.dart';
76
import 'package:kernel/type_environment.dart';
87

@@ -14,10 +13,9 @@ class CallbackSpecializer {
1413
final StatefulStaticTypeContext _staticTypeContext;
1514
final MethodCollector _methodCollector;
1615
final CoreTypesUtil _util;
17-
final ExtensionIndex _extensionIndex;
1816

19-
CallbackSpecializer(this._staticTypeContext, this._util,
20-
this._methodCollector, this._extensionIndex);
17+
CallbackSpecializer(
18+
this._staticTypeContext, this._util, this._methodCollector);
2119

2220
bool _needsArgumentsLength(FunctionType type) =>
2321
type.requiredParameterCount < type.positionalParameters.length;
@@ -33,8 +31,7 @@ class CallbackSpecializer {
3331
DartType callbackParameterType = function.positionalParameters[i];
3432
Expression expression;
3533
VariableGet v = VariableGet(positionalParameters[i]);
36-
if (_extensionIndex.isStaticInteropType(callbackParameterType) &&
37-
boxExternRef) {
34+
if (_util.isJSValueType(callbackParameterType) && boxExternRef) {
3835
expression = _createJSValue(v);
3936
if (!callbackParameterType.isPotentiallyNullable) {
4037
expression = NullCheck(expression);

pkg/dart2wasm/lib/js/interop_transformer.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ class InteropTransformer extends Transformer {
3636

3737
InteropTransformer._(this._staticTypeContext, this._util,
3838
this._methodCollector, extensionIndex)
39-
: _callbackSpecializer = CallbackSpecializer(
40-
_staticTypeContext, _util, _methodCollector, extensionIndex),
39+
: _callbackSpecializer =
40+
CallbackSpecializer(_staticTypeContext, _util, _methodCollector),
4141
_inlineExpander =
4242
InlineExpander(_staticTypeContext, _util, _methodCollector),
4343
_interopSpecializerFactory = InteropSpecializerFactory(

pkg/dart2wasm/lib/js/util.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,12 @@ class CoreTypesUtil {
6868
wasmExternRefClass.getThisType(coreTypes, Nullability.nullable);
6969

7070
Procedure jsifyTarget(DartType type) =>
71-
_extensionIndex.isStaticInteropType(type)
72-
? jsValueUnboxTarget
73-
: jsifyRawTarget;
71+
isJSValueType(type) ? jsValueUnboxTarget : jsifyRawTarget;
72+
73+
/// Return whether [type] erases to a `JSValue`.
74+
bool isJSValueType(DartType type) =>
75+
_extensionIndex.isStaticInteropType(type) ||
76+
_extensionIndex.isExternalDartReferenceType(type);
7477

7578
void annotateProcedure(
7679
Procedure procedure, String pragmaOptionString, AnnotationType type) {
@@ -101,7 +104,7 @@ class CoreTypesUtil {
101104
return invokeOneArg(dartifyRawTarget, invocation);
102105
} else {
103106
Expression expression;
104-
if (_extensionIndex.isStaticInteropType(returnType)) {
107+
if (isJSValueType(returnType)) {
105108
// TODO(joshualitt): Expose boxed `JSNull` and `JSUndefined` to Dart
106109
// code after migrating existing users of js interop on Dart2Wasm.
107110
// expression = _createJSValue(invocation);

pkg/front_end/lib/src/fasta/codes/fasta_codes_cfe_generated.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4723,7 +4723,7 @@ const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
47234723
problemMessageTemplate:
47244724
r"""External JS interop member contains an invalid type: '#type'.""",
47254725
correctionMessageTemplate:
4726-
r"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
4726+
r"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
47274727
withArguments:
47284728
_withArgumentsJsInteropStaticInteropExternalAccessorTypeViolation,
47294729
);
@@ -4747,7 +4747,7 @@ Message _withArgumentsJsInteropStaticInteropExternalAccessorTypeViolation(
47474747
"""External JS interop member contains an invalid type: '${type}'.""" +
47484748
labeler.originMessages,
47494749
correctionMessage:
4750-
"""Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
4750+
"""Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type.""",
47514751
arguments: {
47524752
'type': _type,
47534753
},

pkg/front_end/messages.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5932,15 +5932,15 @@ JsInteropNonStaticWithStaticInteropSupertype:
59325932
# to avoid duplication?
59335933
JsInteropStaticInteropExternalAccessorTypeViolation:
59345934
problemMessage: "External JS interop member contains an invalid type: '#type'."
5935-
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
5935+
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
59365936

59375937
JsInteropStaticInteropExternalFunctionTypeViolation:
59385938
problemMessage: "External JS interop member contains invalid types in its function signature: '#string2'."
5939-
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
5939+
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
59405940

59415941
JsInteropStaticInteropToJSFunctionTypeViolation:
59425942
problemMessage: "Function converted via 'toJS' contains invalid types in its function signature: '#string2'."
5943-
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
5943+
correctionMessage: "Use one of these valid types instead: JS types from 'dart:js_interop', ExternalDartReference, void, bool, num, double, int, String, extension types that erase to one of these types, '@staticInterop' types, 'dart:html' types when compiling to JS, or a type parameter that is a subtype of a valid non-primitive type."
59445944

59455945
JsInteropStaticInteropGenerativeConstructor:
59465946
problemMessage: "`@staticInterop` classes should not contain any generative constructors."

pkg/front_end/test/spell_checking_list_messages.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ erase
5454
exhaustively
5555
exportable
5656
extensiontype
57+
externaldartreference
5758
f
5859
ffi
5960
finality
@@ -84,8 +85,8 @@ loadlibrary
8485
macro
8586
member(s)
8687
migrate
87-
modifier
8888
mocking
89+
modifier
8990
n
9091
name.#name
9192
name.stack

sdk/lib/_internal/js_shared/lib/js_interop_patch.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,22 @@ extension ObjectToJSBoxedDartObject on Object {
118118
}
119119
}
120120

121+
/// [ExternalDartReference] <-> [Object]
122+
@patch
123+
extension ExternalDartReferenceToObject on ExternalDartReference {
124+
@patch
125+
@pragma('dart2js:prefer-inline')
126+
Object get toDartObject => this;
127+
}
128+
129+
@patch
130+
extension ObjectToExternalDartReference on Object {
131+
@patch
132+
@pragma('dart2js:prefer-inline')
133+
ExternalDartReference get toExternalReference =>
134+
this as ExternalDartReference;
135+
}
136+
121137
/// [JSPromise] -> [Future].
122138
@patch
123139
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {

sdk/lib/_internal/js_shared/lib/js_types.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ typedef JSSymbolRepType = interceptors.JavaScriptSymbol;
6464

6565
typedef JSBigIntRepType = interceptors.JavaScriptBigInt;
6666

67-
/// [JSVoid] is just a typedef for [void]. While we could just use
68-
/// `JSUndefined`, in the future we may be able to use this to elide `return`s
69-
/// in JS trampolines.
67+
// While this type is not a JS type, it is here for convenience so we don't need
68+
// to create a new shared library.
69+
typedef ExternalDartReferenceRepType = Object;
70+
71+
// JSVoid is just a typedef for void.
7072
typedef JSVoidRepType = void;

sdk/lib/_internal/wasm/lib/js_interop_patch.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,21 @@ extension ObjectToJSBoxedDartObject on Object {
136136
}
137137
}
138138

139+
/// [ExternalDartReference] <-> [Object]
140+
@patch
141+
extension ExternalDartReferenceToObject on ExternalDartReference {
142+
@patch
143+
Object get toDartObject =>
144+
jsObjectToDartObject((this as JSValue).toExternRef);
145+
}
146+
147+
@patch
148+
extension ObjectToExternalDartReference on Object {
149+
@patch
150+
ExternalDartReference get toExternalReference =>
151+
_boxNonNullable<ExternalDartReference>(jsObjectFromDartObject(this));
152+
}
153+
139154
/// [JSPromise] -> [Future].
140155
@patch
141156
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {

sdk/lib/_internal/wasm/lib/js_types.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ typedef JSSymbolRepType = js.JSValue;
7575

7676
typedef JSBigIntRepType = js.JSValue;
7777

78-
/// [JSVoid] is just a typedef for [void]. While we could just use
79-
/// `JSUndefined`, in the future we may be able to use this to elide `return`s
80-
/// in JS trampolines.
78+
// While this type is not a JS type, it is here for convenience so we don't need
79+
// to create a new shared library.
80+
typedef ExternalDartReferenceRepType = js.JSValue;
81+
82+
// JSVoid is just a typedef for void. While we could just use JSUndefined, in
83+
// the future we may be able to use this to elide `return`s in JS trampolines.
8184
typedef JSVoidRepType = void;

0 commit comments

Comments
 (0)