Skip to content

Commit 1f6d4ae

Browse files
srujzsCommit Queue
authored and
Commit Queue
committed
[dart:js_interop] Add literal constructors for inline classes
Adds @objectliteral annotation to denote object literal constructors, and implements it in all the backends. For dart2js, this involves modifying the SSA and for dart2wasm, we create a one-per-shape forwarding procedure to a specialized JS method that returns the literal. This also modifies @anonymous semantics in dart2wasm to be consistent with the other backends. CoreLibraryReviewExempt: Backend-specific, just adding annotation. Change-Id: I4d7a9ea9ed097f4f378709b40f8bd74f02e26b23 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283922 Commit-Queue: Srujan Gaddam <[email protected]> Reviewed-by: Joshua Litt <[email protected]>
1 parent 4cd0ca7 commit 1f6d4ae

File tree

14 files changed

+340
-85
lines changed

14 files changed

+340
-85
lines changed

pkg/_js_interop_checks/lib/js_interop_checks.dart

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,16 @@ class JsInteropChecks extends RecursiveVisitor {
318318
}
319319

320320
// Check JS Interop positional and named parameters.
321-
var isAnonymousFactory = _classHasAnonymousAnnotation && node.isFactory;
322-
if (isAnonymousFactory) {
323-
// ignore: unnecessary_null_comparison
324-
if (node.function != null &&
325-
node.function.positionalParameters.isNotEmpty) {
326-
var firstPositionalParam = node.function.positionalParameters[0];
321+
var isObjectLiteralFactory =
322+
_classHasAnonymousAnnotation && node.isFactory ||
323+
node.isInlineClassMember && hasObjectLiteralAnnotation(node);
324+
if (isObjectLiteralFactory) {
325+
var positionalParams = node.function.positionalParameters;
326+
if (node.isInlineClassMember) {
327+
positionalParams = positionalParams.skip(1).toList();
328+
}
329+
if (node.function.positionalParameters.isNotEmpty) {
330+
var firstPositionalParam = positionalParams[0];
327331
_diagnosticsReporter.report(
328332
messageJsInteropAnonymousFactoryPositionalParameters,
329333
firstPositionalParam.fileOffset,

pkg/_js_interop_checks/lib/src/js_interop.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ bool hasJSExportAnnotation(Annotatable a) =>
4040
bool hasNativeAnnotation(Annotatable a) =>
4141
a.annotations.any(_isNativeAnnotation);
4242

43+
/// Returns true iff the node has an `@ObjectLiteral(...)` annotation from
44+
/// `dart:js_interop`.
45+
bool hasObjectLiteralAnnotation(Annotatable a) =>
46+
a.annotations.any(_isObjectLiteralAnnotation);
47+
4348
/// If [a] has a `@JS('...')` annotation, returns the value inside the
4449
/// parentheses.
4550
///
@@ -100,6 +105,7 @@ String getJSExportName(Annotatable a) {
100105
final _packageJs = Uri.parse('package:js/js.dart');
101106
final _internalJs = Uri.parse('dart:_js_annotations');
102107
final _jsHelper = Uri.parse('dart:_js_helper');
108+
final _jsInterop = Uri.parse('dart:js_interop');
103109

104110
/// Returns true if [value] is the interop annotation whose class is
105111
/// [annotationClassName] from `package:js` or from `dart:_js_annotations`.
@@ -141,6 +147,15 @@ bool _isNativeAnnotation(Expression value) {
141147
c.enclosingLibrary.importUri == _jsHelper;
142148
}
143149

150+
/// Returns true if [value] is the `ObjectLiteral` annotation from
151+
/// `dart:js_interop`.
152+
bool _isObjectLiteralAnnotation(Expression value) {
153+
var c = annotationClass(value);
154+
return c != null &&
155+
c.name == 'ObjectLiteral' &&
156+
c.enclosingLibrary.importUri == _jsInterop;
157+
}
158+
144159
/// Returns the class of the instance referred to by metadata annotation [node].
145160
///
146161
/// For example:

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import '../js_interop.dart'
1414
hasInternalJSInteropAnnotation,
1515
hasJSInteropAnnotation,
1616
hasNativeAnnotation,
17+
hasObjectLiteralAnnotation,
1718
hasStaticInteropAnnotation,
1819
hasTrustTypesAnnotation;
1920

@@ -174,8 +175,9 @@ class JsUtilOptimizer extends Transformer {
174175
if (node.isInlineClassMember) {
175176
var kind =
176177
_inlineExtensionIndex.getInlineDescriptor(node.reference)?.kind;
177-
return kind == InlineClassMemberKind.Constructor ||
178-
kind == InlineClassMemberKind.Factory;
178+
return (kind == InlineClassMemberKind.Constructor ||
179+
kind == InlineClassMemberKind.Factory) &&
180+
!hasObjectLiteralAnnotation(node);
179181
} else {
180182
return node.kind == ProcedureKind.Factory &&
181183
node.enclosingClass != null &&

pkg/compiler/lib/src/common/names.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ class Uris {
265265
static final Uri dart__js_annotations =
266266
Uri(scheme: 'dart', path: '_js_annotations');
267267

268+
/// The URI for 'dart:js_interop'.
269+
static final Uri dart__js_interop = Uri(scheme: 'dart', path: 'js_interop');
270+
268271
/// The URI for 'package:meta/dart2js.dart'.
269272
static final Uri package_meta_dart2js =
270273
Uri(scheme: 'package', path: 'meta/dart2js.dart');

pkg/compiler/lib/src/ir/annotations.dart

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class IrAnnotationData {
1818
final Map<ir.Class, String> _jsInteropClassNames = {};
1919
final Set<ir.Class> _anonymousJsInteropClasses = {};
2020
final Map<ir.Member, String> _jsInteropMemberNames = {};
21+
final Set<ir.Member> _jsInteropObjectLiterals = {};
2122

2223
final Map<ir.Member, List<PragmaAnnotationData>> _memberPragmaAnnotations =
2324
{};
@@ -52,6 +53,10 @@ class IrAnnotationData {
5253
bool isAnonymousJsInteropClass(ir.Class node) =>
5354
_anonymousJsInteropClasses.contains(node);
5455

56+
// Returns `true` if [node] is annotated with `@ObjectLiteral`.
57+
bool isJsInteropObjectLiteral(ir.Member node) =>
58+
_jsInteropObjectLiterals.contains(node);
59+
5560
// Returns the text from the `@JS(<text>)` annotation of [node], if any.
5661
String? getJsInteropMemberName(ir.Member node) => _jsInteropMemberNames[node];
5762

@@ -74,11 +79,15 @@ class IrAnnotationData {
7479
});
7580
}
7681

77-
void forEachJsInteropMember(void Function(ir.Member, String?) f) {
82+
void forEachJsInteropMember(
83+
void Function(ir.Member, String?,
84+
{required bool isJsInteropObjectLiteral})
85+
f) {
7886
_jsInteropLibraryNames.forEach((ir.Library library, _) {
7987
for (ir.Member member in library.members) {
8088
if (member.isExternal) {
81-
f(member, _jsInteropMemberNames[member] ?? member.name.text);
89+
f(member, _jsInteropMemberNames[member] ?? member.name.text,
90+
isJsInteropObjectLiteral: isJsInteropObjectLiteral(member));
8291
}
8392
}
8493
});
@@ -89,7 +98,7 @@ class IrAnnotationData {
8998
if (member.isExternal) {
9099
name ??= member.name.text;
91100
}
92-
f(member, name);
101+
f(member, name, isJsInteropObjectLiteral: false);
93102
}
94103
});
95104
}
@@ -142,6 +151,12 @@ IrAnnotationData processAnnotations(ModularCore modularCore) {
142151
data._jsInteropMemberNames[member] = jsName;
143152
}
144153

154+
bool isJsInteropObjectLiteralMember =
155+
_isJsInteropObjectLiteral(constant);
156+
if (isJsInteropObjectLiteralMember) {
157+
data._jsInteropObjectLiterals.add(member);
158+
}
159+
145160
bool isNativeMember = _isNativeMember(constant);
146161
if (isNativeMember) {
147162
data._nativeMembers.add(member);
@@ -318,6 +333,12 @@ bool _isAnonymousJsInterop(ir.Constant constant) {
318333
Uris.dart__js_annotations);
319334
}
320335

336+
bool _isJsInteropObjectLiteral(ir.Constant constant) {
337+
return constant is ir.InstanceConstant &&
338+
constant.classNode.name == 'ObjectLiteral' &&
339+
constant.classNode.enclosingLibrary.importUri == Uris.dart__js_interop;
340+
}
341+
321342
class PragmaAnnotationData {
322343
// TODO(johnniwinther): Support non 'dart2js:' pragma names if necessary.
323344
final String suffix;

pkg/compiler/lib/src/js_backend/native_data.dart

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class NativeBasicDataBuilder {
3535
/// The JavaScript members implemented via typed JavaScript interop.
3636
final Map<MemberEntity, String> _jsInteropMembers = {};
3737

38+
/// The JavaScript interop members annotated with `@ObjectLiteral`.
39+
final Set<MemberEntity> _jsInteropObjectLiterals = {};
40+
3841
/// Sets the native tag info for [cls].
3942
///
4043
/// The tag info string contains comma-separated 'words' which are either
@@ -99,14 +102,16 @@ class NativeBasicDataBuilder {
99102

100103
/// Marks [element] as an explicit part of js interop and sets the explicit js
101104
/// interop [name] for the member [element].
102-
void markAsJsInteropMember(MemberEntity element, String name) {
105+
void markAsJsInteropMember(MemberEntity element, String name,
106+
{required bool isJsInteropObjectLiteral}) {
103107
assert(
104108
!_closed,
105109
failedAt(
106110
element,
107111
"NativeBasicDataBuilder is closed. "
108112
"Trying to mark $element as a js-interop member."));
109113
_jsInteropMembers[element] = name;
114+
if (isJsInteropObjectLiteral) _jsInteropObjectLiterals.add(element);
110115
}
111116

112117
/// Creates the [NativeBasicData] object for the data collected in this
@@ -120,7 +125,8 @@ class NativeBasicDataBuilder {
120125
_jsInteropLibraries,
121126
_jsInteropClasses,
122127
_anonymousJsInteropClasses,
123-
_jsInteropMembers);
128+
_jsInteropMembers,
129+
_jsInteropObjectLiterals);
124130
}
125131

126132
void reopenForTesting() {
@@ -156,14 +162,18 @@ class NativeBasicData {
156162
/// The JavaScript members implemented via typed JavaScript interop.
157163
final Map<MemberEntity, String?> _jsInteropMembers;
158164

165+
/// JavaScript interop constructors annotated with `@ObjectLiteral`.
166+
final Set<MemberEntity> _jsInteropObjectLiterals;
167+
159168
NativeBasicData(
160169
this._env,
161170
this._isAllowInteropUsed,
162171
this._nativeClassTagInfo,
163172
this._jsInteropLibraries,
164173
this._jsInteropClasses,
165174
this._anonymousJsInteropClasses,
166-
this._jsInteropMembers);
175+
this._jsInteropMembers,
176+
this._jsInteropObjectLiterals);
167177

168178
factory NativeBasicData.fromIr(
169179
KernelToElementMap map, IrAnnotationData data) {
@@ -173,6 +183,7 @@ class NativeBasicData {
173183
Map<ClassEntity, String> jsInteropClasses = {};
174184
Set<ClassEntity> anonymousJsInteropClasses = {};
175185
Map<MemberEntity, String?> jsInteropMembers = {};
186+
Set<MemberEntity> jsInteropObjectLiterals = {};
176187

177188
data.forEachNativeClass((ir.Class node, String text) {
178189
nativeClassTagInfo[map.getClass(node)] = NativeClassTag(text);
@@ -189,17 +200,27 @@ class NativeBasicData {
189200
anonymousJsInteropClasses.add(cls);
190201
}
191202
});
192-
data.forEachJsInteropMember((ir.Member node, String? name) {
203+
data.forEachJsInteropMember((ir.Member node, String? name,
204+
{required bool isJsInteropObjectLiteral}) {
193205
// TODO(49428): Are there other members that we should ignore here?
194206
// There are non-external and unannotated members because the source code
195207
// doesn't contain them. (e.g. default constructor) Does it make sense to
196208
// consider these valid JS members?
197209
if (memberIsIgnorable(node)) return;
198210
jsInteropMembers[map.getMember(node)] = name;
211+
if (isJsInteropObjectLiteral)
212+
jsInteropObjectLiterals.add(map.getMember(node));
199213
});
200214

201-
return NativeBasicData(env, false, nativeClassTagInfo, jsInteropLibraries,
202-
jsInteropClasses, anonymousJsInteropClasses, jsInteropMembers);
215+
return NativeBasicData(
216+
env,
217+
false,
218+
nativeClassTagInfo,
219+
jsInteropLibraries,
220+
jsInteropClasses,
221+
anonymousJsInteropClasses,
222+
jsInteropMembers,
223+
jsInteropObjectLiterals);
203224
}
204225

205226
/// Deserializes a [NativeBasicData] object from [source].
@@ -220,6 +241,7 @@ class NativeBasicData {
220241
Set<ClassEntity> anonymousJsInteropClasses = source.readClasses().toSet();
221242
Map<MemberEntity, String?> jsInteropMembers = source
222243
.readMemberMap((MemberEntity member) => source.readStringOrNull());
244+
Set<MemberEntity> jsInteropObjectLiterals = source.readMembers().toSet();
223245
source.end(tag);
224246
return NativeBasicData(
225247
elementEnvironment,
@@ -228,7 +250,8 @@ class NativeBasicData {
228250
jsInteropLibraries,
229251
jsInteropClasses,
230252
anonymousJsInteropClasses,
231-
jsInteropMembers);
253+
jsInteropMembers,
254+
jsInteropObjectLiterals);
232255
}
233256

234257
/// Serializes this [NativeBasicData] to [sink].
@@ -244,6 +267,7 @@ class NativeBasicData {
244267
sink.writeClasses(_anonymousJsInteropClasses);
245268
sink.writeMemberMap(_jsInteropMembers,
246269
(MemberEntity member, String? name) => sink.writeStringOrNull(name));
270+
sink.writeMembers(_jsInteropObjectLiterals);
247271
sink.end(tag);
248272
}
249273

@@ -348,14 +372,17 @@ class NativeBasicData {
348372
map.toBackendClassSet(_anonymousJsInteropClasses);
349373
Map<MemberEntity, String?> jsInteropMembers =
350374
map.toBackendMemberMap(_jsInteropMembers, identity);
375+
Set<MemberEntity> jsInteropObjectLiterals =
376+
map.toBackendMemberSet(_jsInteropObjectLiterals);
351377
return NativeBasicData(
352378
environment,
353379
isAllowInteropUsed,
354380
nativeClassTagInfo,
355381
jsInteropLibraries,
356382
jsInteropClasses,
357383
anonymousJsInteropClasses,
358-
jsInteropMembers);
384+
jsInteropMembers,
385+
jsInteropObjectLiterals);
359386
}
360387
}
361388

@@ -569,11 +596,20 @@ class NativeData implements NativeBasicData {
569596
Map<MemberEntity, String?> get _jsInteropMembers =>
570597
_nativeBasicData._jsInteropMembers;
571598

599+
@override
600+
Set<MemberEntity> get _jsInteropObjectLiterals =>
601+
_nativeBasicData._jsInteropObjectLiterals;
602+
572603
/// Returns `true` if [element] has an `@Anonymous` annotation.
573604
bool isAnonymousJsInteropClass(ClassEntity element) {
574605
return _anonymousJsInteropClasses.contains(element);
575606
}
576607

608+
/// Returns `true` if [element] has an `@ObjectLiteral` annotation.
609+
bool isJsInteropObjectLiteral(MemberEntity element) {
610+
return _jsInteropObjectLiterals.contains(element);
611+
}
612+
577613
@override
578614
bool isNativeClass(ClassEntity element) =>
579615
_nativeBasicData.isNativeClass(element);

pkg/compiler/lib/src/kernel/native_basic_data.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ class KernelAnnotationProcessor {
8686
/*reporter.reportErrorMessage(
8787
function, MessageKind.JS_INTEROP_NON_EXTERNAL_MEMBER);*/
8888
} else {
89-
_nativeBasicDataBuilder.markAsJsInteropMember(function, memberName);
89+
_nativeBasicDataBuilder.markAsJsInteropMember(function, memberName,
90+
isJsInteropObjectLiteral:
91+
annotationData.isJsInteropObjectLiteral(memberNode));
9092
// TODO(johnniwinther): It is unclear whether library can be
9193
// implicitly js-interop. For now we allow it.
9294
isJsLibrary = true;
@@ -128,7 +130,8 @@ class KernelAnnotationProcessor {
128130
// TODO(johnniwinther): The documentation states that explicit
129131
// member name annotations are not allowed on instance members.
130132
_nativeBasicDataBuilder.markAsJsInteropMember(
131-
function, memberName);
133+
function, memberName,
134+
isJsInteropObjectLiteral: false);
132135
}
133136
}
134137
});
@@ -145,7 +148,8 @@ class KernelAnnotationProcessor {
145148
// TODO(johnniwinther): The documentation states that explicit
146149
// member name annotations are not allowed on instance members.
147150
_nativeBasicDataBuilder.markAsJsInteropMember(
148-
constructor, memberName);
151+
constructor, memberName,
152+
isJsInteropObjectLiteral: false);
149153
}
150154
});
151155
}

0 commit comments

Comments
 (0)