Skip to content

Commit 8ad4ca1

Browse files
srujzsCommit Queue
authored and
Commit Queue
committed
[stable][dart2wasm] Use node's enclosing library annotation for lowerings
Closes #55359 The current library's annotation is used for the interop lowerings in dart2wasm. For most members, this is okay because we emit the equivalent JS code for the member when we visit the procedure and not when we visit the invocation. However, for methods, the invocation determines the resulting JS call due to the existence of optional parameters. In that case, if the invocation was not in the same library as the interop member declaration, it results in using the wrong library's annotation value. Adds tests for this case and does some cleanup of existing tests. Specifically: - Adds a consistent naming scheme for test libraries that are namespaced. - Adds code to delete the non-namespaced declarations so that the namespaced interop methods don't accidentally call those declarations. - Removes differentArgsMethod which was already tested in js_default_test. - Removes use of js_util in favor of js_interop_unsafe. Cherry-pick: https://dart-review.googlesource.com/c/sdk/+/361241 Cherry-pick-request: #55430 Change-Id: I37661ed200c6db367e3f29f50b0877834f4c1639 Bug: #55359 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362190 Reviewed-by: Kevin Chisholm <[email protected]> Commit-Queue: Srujan Gaddam <[email protected]>
1 parent c09cb46 commit 8ad4ca1

10 files changed

+403
-261
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 3.3.4
2+
3+
This is a patch release that:
4+
5+
- Fixes an issue with JS interop in dart2wasm where JS interop methods that used
6+
the enclosing library's `@JS` annotation were actually using the invocation's
7+
enclosing library's `@JS` annotation. (issue [#55359])
8+
9+
[#55359]: https://github.com/dart-lang/sdk/issues/55359
10+
111
## 3.3.3 - 2024-03-27
212

313
This is a patch release that:

pkg/dart2wasm/lib/js/interop_specializer.dart

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -346,19 +346,11 @@ class InteropSpecializerFactory {
346346
final MethodCollector _methodCollector;
347347
final Map<Procedure, Map<int, Procedure>> _overloadedProcedures = {};
348348
final Map<Procedure, Map<String, Procedure>> _jsObjectLiteralMethods = {};
349-
late String _libraryJSString;
350349
late final ExtensionIndex _extensionIndex;
351350

352351
InteropSpecializerFactory(this._staticTypeContext, this._util,
353352
this._methodCollector, this._extensionIndex);
354353

355-
void enterLibrary(Library library) {
356-
_libraryJSString = getJSName(library);
357-
if (_libraryJSString.isNotEmpty) {
358-
_libraryJSString = '$_libraryJSString.';
359-
}
360-
}
361-
362354
String _getJSString(Annotatable a, String initial) {
363355
String selectorString = getJSName(a);
364356
if (selectorString.isEmpty) {
@@ -367,8 +359,13 @@ class InteropSpecializerFactory {
367359
return selectorString;
368360
}
369361

370-
String _getTopLevelJSString(Annotatable a, String initial) =>
371-
'$_libraryJSString${_getJSString(a, initial)}';
362+
String _getTopLevelJSString(
363+
Annotatable a, String writtenName, Library enclosingLibrary) {
364+
final name = _getJSString(a, writtenName);
365+
final libraryName = getJSName(enclosingLibrary);
366+
if (libraryName.isEmpty) return name;
367+
return '$libraryName.$name';
368+
}
372369

373370
/// Get the `_Specializer` for the non-constructor [node] with its
374371
/// associated [jsString] name, and the [invocation] it's used in if this is
@@ -425,7 +422,8 @@ class InteropSpecializerFactory {
425422
if (node.enclosingClass != null &&
426423
hasJSInteropAnnotation(node.enclosingClass!)) {
427424
final cls = node.enclosingClass!;
428-
final clsString = _getTopLevelJSString(cls, cls.name);
425+
final clsString =
426+
_getTopLevelJSString(cls, cls.name, cls.enclosingLibrary);
429427
if (node.isFactory) {
430428
return _getSpecializerForConstructor(
431429
hasAnonymousAnnotation(cls), node, clsString, invocation);
@@ -438,7 +436,8 @@ class InteropSpecializerFactory {
438436
final nodeDescriptor = _extensionIndex.getExtensionTypeDescriptor(node);
439437
if (nodeDescriptor != null) {
440438
final cls = _extensionIndex.getExtensionType(node)!;
441-
final clsString = _getTopLevelJSString(cls, cls.name);
439+
final clsString =
440+
_getTopLevelJSString(cls, cls.name, node.enclosingLibrary);
442441
final kind = nodeDescriptor.kind;
443442
if ((kind == ExtensionTypeMemberKind.Constructor ||
444443
kind == ExtensionTypeMemberKind.Factory)) {
@@ -467,7 +466,9 @@ class InteropSpecializerFactory {
467466
}
468467
} else if (hasJSInteropAnnotation(node)) {
469468
return _getSpecializerForMember(
470-
node, _getTopLevelJSString(node, node.name.text), invocation);
469+
node,
470+
_getTopLevelJSString(node, node.name.text, node.enclosingLibrary),
471+
invocation);
471472
}
472473
return null;
473474
}

pkg/dart2wasm/lib/js/interop_transformer.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ class InteropTransformer extends Transformer {
5555

5656
@override
5757
Library visitLibrary(Library lib) {
58-
_interopSpecializerFactory.enterLibrary(lib);
5958
_methodCollector.enterLibrary(lib);
6059
_staticTypeContext.enterLibrary(lib);
6160
lib.transformChildren(this);

tests/lib/js/static_interop_test/extension_type/external_static_member_test.dart

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@
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.
44

5-
// SharedOptions=--enable-experiment=inline-class
5+
// TODO(srujzs): There's a decent amount of code duplication in this test. We
6+
// should combine this with
7+
// tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart.
68

79
@JS()
810
library external_static_member_test;
911

1012
import 'dart:js_interop';
11-
import 'dart:js_util' as js_util;
13+
import 'dart:js_interop_unsafe';
1214

1315
import 'package:expect/minitest.dart';
1416

17+
import 'external_static_member_with_namespaces.dart' as namespace;
18+
1519
@JS()
1620
external void eval(String code);
1721

1822
@JS()
19-
extension type ExternalStatic._(JSObject obj) implements Object {
23+
extension type ExternalStatic._(JSObject obj) implements JSObject {
2024
external ExternalStatic();
2125
external factory ExternalStatic.factory();
2226
external ExternalStatic.multipleArgs(double a, String b);
23-
external ExternalStatic.differentArgs(double a, [String b = '']);
2427
ExternalStatic.nonExternal() : this.obj = ExternalStatic() as JSObject;
2528

2629
external static String field;
@@ -36,39 +39,20 @@ extension type ExternalStatic._(JSObject obj) implements Object {
3639
external static set renamedGetSet(String val);
3740

3841
external static String method();
39-
external static String differentArgsMethod(String a, [String b = '']);
4042
@JS('method')
4143
external static String renamedMethod();
4244
}
4345

44-
void main() {
45-
eval('''
46-
globalThis.ExternalStatic = function ExternalStatic(a, b) {
47-
var len = arguments.length;
48-
this.a = len < 1 ? 0 : a;
49-
this.b = len < 2 ? '' : b;
50-
}
51-
globalThis.ExternalStatic.method = function() {
52-
return 'method';
53-
}
54-
globalThis.ExternalStatic.differentArgsMethod = function(a, b) {
55-
return a + b;
56-
}
57-
globalThis.ExternalStatic.field = 'field';
58-
globalThis.ExternalStatic.finalField = 'finalField';
59-
globalThis.ExternalStatic.getSet = 'getSet';
60-
''');
61-
46+
void testStaticMembers() {
6247
// Constructors.
6348
void testExternalConstructorCall(ExternalStatic externalStatic) {
64-
expect(js_util.getProperty(externalStatic, 'a'), 0);
65-
expect(js_util.getProperty(externalStatic, 'b'), '');
49+
expect((externalStatic['a'] as JSNumber).toDartInt, 0);
50+
expect((externalStatic['b'] as JSString).toDart, '');
6651
}
6752

6853
testExternalConstructorCall(ExternalStatic());
6954
testExternalConstructorCall(ExternalStatic.factory());
7055
testExternalConstructorCall(ExternalStatic.multipleArgs(0, ''));
71-
testExternalConstructorCall(ExternalStatic.differentArgs(0));
7256
testExternalConstructorCall(ExternalStatic.nonExternal());
7357

7458
// Fields.
@@ -90,6 +74,69 @@ void main() {
9074

9175
// Methods.
9276
expect(ExternalStatic.method(), 'method');
93-
expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined');
9477
expect(ExternalStatic.renamedMethod(), 'method');
9578
}
79+
80+
void testNamespacedStaticMembers() {
81+
// Constructors.
82+
void testExternalConstructorCall(namespace.ExternalStatic externalStatic) {
83+
expect((externalStatic['a'] as JSNumber).toDartInt, 0);
84+
expect((externalStatic['b'] as JSString).toDart, '');
85+
}
86+
87+
testExternalConstructorCall(namespace.ExternalStatic());
88+
testExternalConstructorCall(namespace.ExternalStatic.factory());
89+
testExternalConstructorCall(namespace.ExternalStatic.multipleArgs(0, ''));
90+
testExternalConstructorCall(namespace.ExternalStatic.nonExternal());
91+
92+
// Fields.
93+
expect(namespace.ExternalStatic.field, 'field');
94+
namespace.ExternalStatic.field = 'modified';
95+
expect(namespace.ExternalStatic.field, 'modified');
96+
expect(namespace.ExternalStatic.renamedField, 'modified');
97+
namespace.ExternalStatic.renamedField = 'renamedField';
98+
expect(namespace.ExternalStatic.renamedField, 'renamedField');
99+
expect(namespace.ExternalStatic.finalField, 'finalField');
100+
101+
// Getters and setters.
102+
expect(namespace.ExternalStatic.getSet, 'getSet');
103+
namespace.ExternalStatic.getSet = 'modified';
104+
expect(namespace.ExternalStatic.getSet, 'modified');
105+
expect(namespace.ExternalStatic.renamedGetSet, 'modified');
106+
namespace.ExternalStatic.renamedGetSet = 'renamedGetSet';
107+
expect(namespace.ExternalStatic.renamedGetSet, 'renamedGetSet');
108+
109+
// Methods.
110+
expect(namespace.ExternalStatic.method(), 'method');
111+
expect(namespace.ExternalStatic.renamedMethod(), 'method');
112+
}
113+
114+
void main() {
115+
eval('''
116+
globalThis.ExternalStatic = function ExternalStatic(a, b) {
117+
var len = arguments.length;
118+
this.a = len < 1 ? 0 : a;
119+
this.b = len < 2 ? '' : b;
120+
}
121+
globalThis.ExternalStatic.method = function() {
122+
return 'method';
123+
}
124+
globalThis.ExternalStatic.field = 'field';
125+
globalThis.ExternalStatic.finalField = 'finalField';
126+
globalThis.ExternalStatic.getSet = 'getSet';
127+
''');
128+
testStaticMembers();
129+
eval('''
130+
var library3 = {};
131+
var library2 = {library3: library3};
132+
var library1 = {library2: library2};
133+
globalThis.library1 = library1;
134+
135+
library3.ExternalStatic = globalThis.ExternalStatic;
136+
library3.ExternalStatic.field = 'field';
137+
library3.ExternalStatic.finalField = 'finalField';
138+
library3.ExternalStatic.getSet = 'getSet';
139+
delete globalThis.ExternalStatic;
140+
''');
141+
testNamespacedStaticMembers();
142+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@JS('library1.library2')
6+
library external_static_member_with_namespaces_test;
7+
8+
import 'dart:js_interop';
9+
10+
@JS()
11+
external void eval(String code);
12+
13+
@JS('library3.ExternalStatic')
14+
extension type ExternalStatic._(JSObject obj) implements JSObject {
15+
external ExternalStatic();
16+
external factory ExternalStatic.factory();
17+
external ExternalStatic.multipleArgs(double a, String b);
18+
ExternalStatic.nonExternal() : this.obj = ExternalStatic() as JSObject;
19+
20+
external static String field;
21+
@JS('field')
22+
external static String renamedField;
23+
external static final String finalField;
24+
25+
external static String get getSet;
26+
external static set getSet(String val);
27+
@JS('getSet')
28+
external static String get renamedGetSet;
29+
@JS('getSet')
30+
external static set renamedGetSet(String val);
31+
32+
external static String method();
33+
@JS('method')
34+
external static String renamedMethod();
35+
}

0 commit comments

Comments
 (0)