From 8ad4ca1484085414beb44be99889fd99312ed4f7 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Tue, 16 Apr 2024 18:01:00 +0000 Subject: [PATCH] [stable][dart2wasm] Use node's enclosing library annotation for lowerings Closes https://github.com/dart-lang/sdk/issues/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: https://github.com/dart-lang/sdk/issues/55430 Change-Id: I37661ed200c6db367e3f29f50b0877834f4c1639 Bug: https://github.com/dart-lang/sdk/issues/55359 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362190 Reviewed-by: Kevin Chisholm Commit-Queue: Srujan Gaddam --- CHANGELOG.md | 10 + pkg/dart2wasm/lib/js/interop_specializer.dart | 27 +-- pkg/dart2wasm/lib/js/interop_transformer.dart | 1 - .../external_static_member_test.dart | 101 +++++++--- ...xternal_static_member_with_namespaces.dart | 35 ++++ ...external_static_member_lowerings_test.dart | 143 ++++++++++---- ...atic_member_lowerings_with_namespaces.dart | 64 +++++++ ...member_lowerings_with_namespaces_test.dart | 174 ------------------ .../static_interop_test/js_default_test.dart | 93 +++++++++- ...y.dart => js_default_with_namespaces.dart} | 16 +- 10 files changed, 403 insertions(+), 261 deletions(-) create mode 100644 tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces.dart create mode 100644 tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces.dart delete mode 100644 tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart rename tests/lib/js/static_interop_test/{js_default_other_library.dart => js_default_with_namespaces.dart} (56%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04df376ea3c8..e352872f329d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 3.3.4 + +This is a patch release that: + +- Fixes an issue with JS interop in dart2wasm where JS interop methods that used + the enclosing library's `@JS` annotation were actually using the invocation's + enclosing library's `@JS` annotation. (issue [#55359]) + +[#55359]: https://github.com/dart-lang/sdk/issues/55359 + ## 3.3.3 - 2024-03-27 This is a patch release that: diff --git a/pkg/dart2wasm/lib/js/interop_specializer.dart b/pkg/dart2wasm/lib/js/interop_specializer.dart index ccc40561bbd8..63a1f94139ee 100644 --- a/pkg/dart2wasm/lib/js/interop_specializer.dart +++ b/pkg/dart2wasm/lib/js/interop_specializer.dart @@ -346,19 +346,11 @@ class InteropSpecializerFactory { final MethodCollector _methodCollector; final Map> _overloadedProcedures = {}; final Map> _jsObjectLiteralMethods = {}; - late String _libraryJSString; late final ExtensionIndex _extensionIndex; InteropSpecializerFactory(this._staticTypeContext, this._util, this._methodCollector, this._extensionIndex); - void enterLibrary(Library library) { - _libraryJSString = getJSName(library); - if (_libraryJSString.isNotEmpty) { - _libraryJSString = '$_libraryJSString.'; - } - } - String _getJSString(Annotatable a, String initial) { String selectorString = getJSName(a); if (selectorString.isEmpty) { @@ -367,8 +359,13 @@ class InteropSpecializerFactory { return selectorString; } - String _getTopLevelJSString(Annotatable a, String initial) => - '$_libraryJSString${_getJSString(a, initial)}'; + String _getTopLevelJSString( + Annotatable a, String writtenName, Library enclosingLibrary) { + final name = _getJSString(a, writtenName); + final libraryName = getJSName(enclosingLibrary); + if (libraryName.isEmpty) return name; + return '$libraryName.$name'; + } /// Get the `_Specializer` for the non-constructor [node] with its /// associated [jsString] name, and the [invocation] it's used in if this is @@ -425,7 +422,8 @@ class InteropSpecializerFactory { if (node.enclosingClass != null && hasJSInteropAnnotation(node.enclosingClass!)) { final cls = node.enclosingClass!; - final clsString = _getTopLevelJSString(cls, cls.name); + final clsString = + _getTopLevelJSString(cls, cls.name, cls.enclosingLibrary); if (node.isFactory) { return _getSpecializerForConstructor( hasAnonymousAnnotation(cls), node, clsString, invocation); @@ -438,7 +436,8 @@ class InteropSpecializerFactory { final nodeDescriptor = _extensionIndex.getExtensionTypeDescriptor(node); if (nodeDescriptor != null) { final cls = _extensionIndex.getExtensionType(node)!; - final clsString = _getTopLevelJSString(cls, cls.name); + final clsString = + _getTopLevelJSString(cls, cls.name, node.enclosingLibrary); final kind = nodeDescriptor.kind; if ((kind == ExtensionTypeMemberKind.Constructor || kind == ExtensionTypeMemberKind.Factory)) { @@ -467,7 +466,9 @@ class InteropSpecializerFactory { } } else if (hasJSInteropAnnotation(node)) { return _getSpecializerForMember( - node, _getTopLevelJSString(node, node.name.text), invocation); + node, + _getTopLevelJSString(node, node.name.text, node.enclosingLibrary), + invocation); } return null; } diff --git a/pkg/dart2wasm/lib/js/interop_transformer.dart b/pkg/dart2wasm/lib/js/interop_transformer.dart index 79cc4aeb0e1f..6c122d168f03 100644 --- a/pkg/dart2wasm/lib/js/interop_transformer.dart +++ b/pkg/dart2wasm/lib/js/interop_transformer.dart @@ -55,7 +55,6 @@ class InteropTransformer extends Transformer { @override Library visitLibrary(Library lib) { - _interopSpecializerFactory.enterLibrary(lib); _methodCollector.enterLibrary(lib); _staticTypeContext.enterLibrary(lib); lib.transformChildren(this); diff --git a/tests/lib/js/static_interop_test/extension_type/external_static_member_test.dart b/tests/lib/js/static_interop_test/extension_type/external_static_member_test.dart index 7f2821b9a3d5..e20e9e8ddf76 100644 --- a/tests/lib/js/static_interop_test/extension_type/external_static_member_test.dart +++ b/tests/lib/js/static_interop_test/extension_type/external_static_member_test.dart @@ -2,25 +2,28 @@ // 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. -// SharedOptions=--enable-experiment=inline-class +// TODO(srujzs): There's a decent amount of code duplication in this test. We +// should combine this with +// tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart. @JS() library external_static_member_test; import 'dart:js_interop'; -import 'dart:js_util' as js_util; +import 'dart:js_interop_unsafe'; import 'package:expect/minitest.dart'; +import 'external_static_member_with_namespaces.dart' as namespace; + @JS() external void eval(String code); @JS() -extension type ExternalStatic._(JSObject obj) implements Object { +extension type ExternalStatic._(JSObject obj) implements JSObject { external ExternalStatic(); external factory ExternalStatic.factory(); external ExternalStatic.multipleArgs(double a, String b); - external ExternalStatic.differentArgs(double a, [String b = '']); ExternalStatic.nonExternal() : this.obj = ExternalStatic() as JSObject; external static String field; @@ -36,39 +39,20 @@ extension type ExternalStatic._(JSObject obj) implements Object { external static set renamedGetSet(String val); external static String method(); - external static String differentArgsMethod(String a, [String b = '']); @JS('method') external static String renamedMethod(); } -void main() { - eval(''' - globalThis.ExternalStatic = function ExternalStatic(a, b) { - var len = arguments.length; - this.a = len < 1 ? 0 : a; - this.b = len < 2 ? '' : b; - } - globalThis.ExternalStatic.method = function() { - return 'method'; - } - globalThis.ExternalStatic.differentArgsMethod = function(a, b) { - return a + b; - } - globalThis.ExternalStatic.field = 'field'; - globalThis.ExternalStatic.finalField = 'finalField'; - globalThis.ExternalStatic.getSet = 'getSet'; - '''); - +void testStaticMembers() { // Constructors. void testExternalConstructorCall(ExternalStatic externalStatic) { - expect(js_util.getProperty(externalStatic, 'a'), 0); - expect(js_util.getProperty(externalStatic, 'b'), ''); + expect((externalStatic['a'] as JSNumber).toDartInt, 0); + expect((externalStatic['b'] as JSString).toDart, ''); } testExternalConstructorCall(ExternalStatic()); testExternalConstructorCall(ExternalStatic.factory()); testExternalConstructorCall(ExternalStatic.multipleArgs(0, '')); - testExternalConstructorCall(ExternalStatic.differentArgs(0)); testExternalConstructorCall(ExternalStatic.nonExternal()); // Fields. @@ -90,6 +74,69 @@ void main() { // Methods. expect(ExternalStatic.method(), 'method'); - expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined'); expect(ExternalStatic.renamedMethod(), 'method'); } + +void testNamespacedStaticMembers() { + // Constructors. + void testExternalConstructorCall(namespace.ExternalStatic externalStatic) { + expect((externalStatic['a'] as JSNumber).toDartInt, 0); + expect((externalStatic['b'] as JSString).toDart, ''); + } + + testExternalConstructorCall(namespace.ExternalStatic()); + testExternalConstructorCall(namespace.ExternalStatic.factory()); + testExternalConstructorCall(namespace.ExternalStatic.multipleArgs(0, '')); + testExternalConstructorCall(namespace.ExternalStatic.nonExternal()); + + // Fields. + expect(namespace.ExternalStatic.field, 'field'); + namespace.ExternalStatic.field = 'modified'; + expect(namespace.ExternalStatic.field, 'modified'); + expect(namespace.ExternalStatic.renamedField, 'modified'); + namespace.ExternalStatic.renamedField = 'renamedField'; + expect(namespace.ExternalStatic.renamedField, 'renamedField'); + expect(namespace.ExternalStatic.finalField, 'finalField'); + + // Getters and setters. + expect(namespace.ExternalStatic.getSet, 'getSet'); + namespace.ExternalStatic.getSet = 'modified'; + expect(namespace.ExternalStatic.getSet, 'modified'); + expect(namespace.ExternalStatic.renamedGetSet, 'modified'); + namespace.ExternalStatic.renamedGetSet = 'renamedGetSet'; + expect(namespace.ExternalStatic.renamedGetSet, 'renamedGetSet'); + + // Methods. + expect(namespace.ExternalStatic.method(), 'method'); + expect(namespace.ExternalStatic.renamedMethod(), 'method'); +} + +void main() { + eval(''' + globalThis.ExternalStatic = function ExternalStatic(a, b) { + var len = arguments.length; + this.a = len < 1 ? 0 : a; + this.b = len < 2 ? '' : b; + } + globalThis.ExternalStatic.method = function() { + return 'method'; + } + globalThis.ExternalStatic.field = 'field'; + globalThis.ExternalStatic.finalField = 'finalField'; + globalThis.ExternalStatic.getSet = 'getSet'; + '''); + testStaticMembers(); + eval(''' + var library3 = {}; + var library2 = {library3: library3}; + var library1 = {library2: library2}; + globalThis.library1 = library1; + + library3.ExternalStatic = globalThis.ExternalStatic; + library3.ExternalStatic.field = 'field'; + library3.ExternalStatic.finalField = 'finalField'; + library3.ExternalStatic.getSet = 'getSet'; + delete globalThis.ExternalStatic; + '''); + testNamespacedStaticMembers(); +} diff --git a/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces.dart b/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces.dart new file mode 100644 index 000000000000..42ea0518efa2 --- /dev/null +++ b/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces.dart @@ -0,0 +1,35 @@ +// Copyright (c) 2024, 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('library1.library2') +library external_static_member_with_namespaces_test; + +import 'dart:js_interop'; + +@JS() +external void eval(String code); + +@JS('library3.ExternalStatic') +extension type ExternalStatic._(JSObject obj) implements JSObject { + external ExternalStatic(); + external factory ExternalStatic.factory(); + external ExternalStatic.multipleArgs(double a, String b); + ExternalStatic.nonExternal() : this.obj = ExternalStatic() as JSObject; + + external static String field; + @JS('field') + external static String renamedField; + external static final String finalField; + + external static String get getSet; + external static set getSet(String val); + @JS('getSet') + external static String get renamedGetSet; + @JS('getSet') + external static set renamedGetSet(String val); + + external static String method(); + @JS('method') + external static String renamedMethod(); +} diff --git a/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart b/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart index 730d93ff7147..a13946890cb9 100644 --- a/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart +++ b/tests/lib/js/static_interop_test/external_static_member_lowerings_test.dart @@ -9,6 +9,8 @@ import 'dart:js_interop'; import 'package:expect/minitest.dart'; +import 'external_static_member_lowerings_with_namespaces.dart' as namespace; + @JS() external void eval(String code); @@ -33,7 +35,6 @@ class ExternalStatic { external static set renamedGetSet(String val); external static String method(); - external static String differentArgsMethod(String a, [String b = '']); @JS('method') external static String renamedMethod(); } @@ -63,41 +64,9 @@ external set renamedGetSet(String val); // Top-level methods. @JS() external String method(); -@JS() -external String differentArgsMethod(String a, [String b = '']); @JS('method') external String renamedMethod(); -void main() { - eval(''' - globalThis.ExternalStatic = function ExternalStatic(initialValue) { - this.initialValue = initialValue; - } - globalThis.ExternalStatic.method = function() { - return 'method'; - } - globalThis.ExternalStatic.differentArgsMethod = function(a, b) { - return a + b; - } - globalThis.ExternalStatic.field = 'field'; - globalThis.ExternalStatic.finalField = 'finalField'; - globalThis.ExternalStatic.getSet = 'getSet'; - - globalThis.field = 'field'; - globalThis.finalField = 'finalField'; - globalThis.getSet = 'getSet'; - globalThis.method = function() { - return 'method'; - } - globalThis.differentArgsMethod = function(a, b) { - return a + b; - } - '''); - testClassStaticMembers(); - testTopLevelMembers(); - testFactories(); -} - void testClassStaticMembers() { // Fields. expect(ExternalStatic.field, 'field'); @@ -118,7 +87,6 @@ void testClassStaticMembers() { // Methods. expect(ExternalStatic.method(), 'method'); - expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined'); expect(ExternalStatic.renamedMethod(), 'method'); } @@ -142,7 +110,6 @@ void testTopLevelMembers() { // Methods. expect(method(), 'method'); - expect(differentArgsMethod('method'), 'methodundefined'); expect(renamedMethod(), 'method'); } @@ -155,3 +122,109 @@ void testFactories() { externalStatic = ExternalStatic.named(); expect(externalStatic.initialValue, null); } + +void testNamespacedClassStaticMembers() { + // Fields. + expect(namespace.ExternalStatic.field, 'field'); + namespace.ExternalStatic.field = 'modified'; + expect(namespace.ExternalStatic.field, 'modified'); + expect(namespace.ExternalStatic.renamedField, 'modified'); + namespace.ExternalStatic.renamedField = 'renamedField'; + expect(namespace.ExternalStatic.renamedField, 'renamedField'); + expect(namespace.ExternalStatic.finalField, 'finalField'); + + // Getters and setters. + expect(namespace.ExternalStatic.getSet, 'getSet'); + namespace.ExternalStatic.getSet = 'modified'; + expect(namespace.ExternalStatic.getSet, 'modified'); + expect(namespace.ExternalStatic.renamedGetSet, 'modified'); + namespace.ExternalStatic.renamedGetSet = 'renamedGetSet'; + expect(namespace.ExternalStatic.renamedGetSet, 'renamedGetSet'); + + // Methods. + expect(namespace.ExternalStatic.method(), 'method'); + expect(namespace.ExternalStatic.renamedMethod(), 'method'); +} + +void testNamespacedTopLevelMembers() { + // Fields. + expect(namespace.field, 'field'); + namespace.field = 'modified'; + expect(namespace.field, 'modified'); + expect(namespace.renamedField, 'modified'); + namespace.renamedField = 'renamedField'; + expect(namespace.renamedField, 'renamedField'); + expect(namespace.finalField, 'finalField'); + + // Getters and setters. + expect(namespace.getSet, 'getSet'); + namespace.getSet = 'modified'; + expect(namespace.getSet, 'modified'); + expect(namespace.renamedGetSet, 'modified'); + namespace.renamedGetSet = 'renamedGetSet'; + expect(namespace.renamedGetSet, 'renamedGetSet'); + + // Methods. + expect(namespace.method(), 'method'); + expect(namespace.renamedMethod(), 'method'); +} + +void testNamespacedFactories() { + // Non-object literal factories. + var initialized = 'initialized'; + + var externalStatic = namespace.ExternalStatic(initialized); + expect(externalStatic.initialValue, initialized); + externalStatic = namespace.ExternalStatic.named(); + expect(externalStatic.initialValue, null); +} + +void main() { + eval(''' + globalThis.ExternalStatic = function ExternalStatic(initialValue) { + this.initialValue = initialValue; + } + globalThis.ExternalStatic.field = 'field'; + globalThis.ExternalStatic.finalField = 'finalField'; + globalThis.ExternalStatic.getSet = 'getSet'; + globalThis.ExternalStatic.method = function() { + return 'method'; + } + + globalThis.field = 'field'; + globalThis.finalField = 'finalField'; + globalThis.getSet = 'getSet'; + globalThis.method = function() { + return 'method'; + } + '''); + testClassStaticMembers(); + testTopLevelMembers(); + testFactories(); + // Move declarations to a namespace and delete the top-level ones to test that + // we use the declaration's enclosing library's annotation and not the current + // library's. + eval(''' + var library3 = {}; + var library2 = {library3: library3}; + var library1 = {library2: library2}; + globalThis.library1 = library1; + + library3.ExternalStatic = globalThis.ExternalStatic; + library3.ExternalStatic.field = 'field'; + library3.ExternalStatic.finalField = 'finalField'; + library3.ExternalStatic.getSet = 'getSet'; + delete globalThis.ExternalStatic; + library3.field = 'field'; + library3.finalField = 'finalField'; + library3.getSet = 'getSet'; + library3.method = globalThis.method; + delete globalThis.field; + delete globalThis.finalField; + delete globalThis.getSet; + delete globalThis.method; + '''); + testNamespacedClassStaticMembers(); + testNamespacedTopLevelMembers(); + testNamespacedFactories(); +} diff --git a/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces.dart b/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces.dart new file mode 100644 index 000000000000..8c515ebde28b --- /dev/null +++ b/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces.dart @@ -0,0 +1,64 @@ +// Copyright (c) 2024, 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. + +// Like `external_static_member_lowerings_test.dart`, but uses the namespaces +// in the `@JS` annotations instead. + +@JS('library1.library2') +library external_static_member_lowerings_with_namespaces_test; + +import 'dart:js_interop'; + +@JS('library3.ExternalStatic') +@staticInterop +class ExternalStatic { + external factory ExternalStatic(String initialValue); + external factory ExternalStatic.named( + [String initialValue = 'uninitialized']); + // External redirecting factories are not allowed. + + external static String field; + @JS('field') + external static String renamedField; + external static final String finalField; + + external static String get getSet; + external static set getSet(String val); + @JS('getSet') + external static String get renamedGetSet; + @JS('getSet') + external static set renamedGetSet(String val); + + external static String method(); + @JS('method') + external static String renamedMethod(); +} + +extension ExternalStaticExtension on ExternalStatic { + external String? get initialValue; +} + +// Top-level fields. +@JS('library3.field') +external String field; +@JS('library3.field') +external String renamedField; +@JS('library3.finalField') +external final String finalField; + +// Top-level getters and setters. +@JS('library3.getSet') +external String get getSet; +@JS('library3.getSet') +external set getSet(String val); +@JS('library3.getSet') +external String get renamedGetSet; +@JS('library3.getSet') +external set renamedGetSet(String val); + +// Top-level methods. +@JS('library3.method') +external String method(); +@JS('library3.method') +external String renamedMethod(); diff --git a/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart b/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart deleted file mode 100644 index 676220a16fbb..000000000000 --- a/tests/lib/js/static_interop_test/external_static_member_lowerings_with_namespaces_test.dart +++ /dev/null @@ -1,174 +0,0 @@ -// 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. - -// Like `external_static_member_lowerings_test.dart`, but uses the namespaces -// in the `@JS` annotations instead. - -@JS('library1.library2') -library external_static_member_lowerings_with_namespaces_test; - -import 'dart:js_interop'; -import 'dart:js_util' as js_util; - -import 'package:expect/minitest.dart'; - -@JS('library3.ExternalStatic') -@staticInterop -class ExternalStatic { - external factory ExternalStatic(String initialValue); - external factory ExternalStatic.named( - [String initialValue = 'uninitialized']); - // External redirecting factories are not allowed. - - external static String field; - @JS('field') - external static String renamedField; - external static final String finalField; - - external static String get getSet; - external static set getSet(String val); - @JS('getSet') - external static String get renamedGetSet; - @JS('getSet') - external static set renamedGetSet(String val); - - external static String method(); - external static String differentArgsMethod(String a, [String b = '']); - @JS('method') - external static String renamedMethod(); -} - -extension on ExternalStatic { - external String? get initialValue; -} - -void main() { - // Use `callMethod` instead of top-level external to `eval` since the library - // is namespaced. - js_util.callMethod(js_util.globalThis, 'eval', [ - ''' - var library3 = {}; - var library2 = {library3: library3}; - var library1 = {library2: library2}; - globalThis.library1 = library1; - - library3.ExternalStatic = function ExternalStatic(initialValue) { - this.initialValue = initialValue; - } - library3.ExternalStatic.method = function() { - return 'method'; - } - library3.ExternalStatic.differentArgsMethod = function(a, b) { - return a + b; - } - library3.ExternalStatic.field = 'field'; - library3.ExternalStatic.finalField = 'finalField'; - library3.ExternalStatic.getSet = 'getSet'; - - library2.field = 'field'; - library2.getSet = 'getSet'; - library2.method = function() { - return 'method'; - } - library2.differentArgsMethod = function(a, b) { - return a + b; - } - library3.namespacedField = 'namespacedField'; - library3.namespacedGetSet = 'namespacedGetSet'; - library3.namespacedMethod = function() { - return 'namespacedMethod'; - } - - ''' - ]); - testClassStaticMembers(); - testTopLevelMembers(); - testFactories(); -} - -// Top-level fields. -@JS() -external String field; -@JS('library3.namespacedField') -external String namespacedField; -@JS('field') -external final String finalField; - -// Top-level getters and setters. -@JS() -external String get getSet; -@JS() -external set getSet(String val); -@JS('library3.namespacedGetSet') -external String get namespacedGetSet; -@JS('library3.namespacedGetSet') -external set namespacedGetSet(String val); - -// Top-level methods. -@JS() -external String method(); -@JS() -external String differentArgsMethod(String a, [String b = '']); -@JS('library3.namespacedMethod') -external String namespacedMethod(); - -void testClassStaticMembers() { - // Fields. - expect(ExternalStatic.field, 'field'); - ExternalStatic.field = 'modified'; - expect(ExternalStatic.field, 'modified'); - expect(ExternalStatic.renamedField, 'modified'); - ExternalStatic.renamedField = 'renamedField'; - expect(ExternalStatic.renamedField, 'renamedField'); - expect(ExternalStatic.finalField, 'finalField'); - - // Getters and setters. - expect(ExternalStatic.getSet, 'getSet'); - ExternalStatic.getSet = 'modified'; - expect(ExternalStatic.getSet, 'modified'); - expect(ExternalStatic.renamedGetSet, 'modified'); - ExternalStatic.renamedGetSet = 'renamedGetSet'; - expect(ExternalStatic.renamedGetSet, 'renamedGetSet'); - - // Methods. - expect(ExternalStatic.method(), 'method'); - expect(ExternalStatic.differentArgsMethod('method'), 'methodundefined'); - expect(ExternalStatic.renamedMethod(), 'method'); -} - -void testTopLevelMembers() { - // Test a variety of renaming and namespacing to make sure we're handling '.' - // correctly. - // Fields. - expect(field, 'field'); - field = 'modified'; - expect(field, 'modified'); - expect(namespacedField, 'namespacedField'); - namespacedField = 'modified'; - expect(namespacedField, 'modified'); - expect(finalField, 'modified'); - - // Getters and setters. - expect(getSet, 'getSet'); - getSet = 'modified'; - expect(getSet, 'modified'); - expect(namespacedGetSet, 'namespacedGetSet'); - namespacedGetSet = 'modified'; - expect(namespacedGetSet, 'modified'); - - // Methods. - expect(method(), 'method'); - expect(differentArgsMethod('method'), 'methodundefined'); - expect(namespacedMethod(), 'namespacedMethod'); -} - -void testFactories() { - // Non-object literal factories. - var initialized = 'initialized'; - - var externalStatic = ExternalStatic(initialized); - expect(externalStatic.initialValue, initialized); - externalStatic = ExternalStatic.named(); - expect(externalStatic.initialValue, null); -} diff --git a/tests/lib/js/static_interop_test/js_default_test.dart b/tests/lib/js/static_interop_test/js_default_test.dart index 90f1c23f248e..c87df208bf60 100644 --- a/tests/lib/js/static_interop_test/js_default_test.dart +++ b/tests/lib/js/static_interop_test/js_default_test.dart @@ -12,7 +12,7 @@ import 'dart:js_interop'; import 'package:expect/minitest.dart'; -import 'js_default_other_library.dart' as other; +import 'js_default_with_namespaces.dart' as namespace; @JS() external void eval(String code); @@ -34,6 +34,20 @@ extension SimpleObjectExtension on SimpleObject { external JSNumber oneOptional(JSNumber n1, [JSNumber n2]); } +@JS('SimpleObject') +extension type SimpleObject2._(JSObject _) implements JSObject { + external factory SimpleObject2(); + external factory SimpleObject2.twoOptional([JSNumber n1, JSNumber n2]); + external factory SimpleObject2.oneOptional(JSNumber n1, [JSNumber n2]); + + external static JSNumber twoOptionalStatic([JSNumber n1, JSNumber n2]); + external static JSNumber oneOptionalStatic(JSNumber n1, [JSNumber n2]); + + external JSNumber get initialArguments; + external JSNumber twoOptional([JSNumber n1, JSNumber n2]); + external JSNumber oneOptional(JSNumber n1, [JSNumber n2]); +} + @JS() external JSNumber twoOptional([JSNumber n1, JSNumber n2]); @@ -75,30 +89,77 @@ void testCurrentLibrary() { expect(1, s.oneOptional(4.0.toJS).toDartInt); expect(2, s.oneOptional(4.0.toJS, 5.0.toJS).toDartInt); + + // Test extension type factories. + expect(0, SimpleObject2.twoOptional().initialArguments.toDartInt); + expect(1, SimpleObject2.twoOptional(4.0.toJS).initialArguments.toDartInt); + expect(2, + SimpleObject2.twoOptional(4.0.toJS, 5.0.toJS).initialArguments.toDartInt); + + expect(1, SimpleObject2.oneOptional(4.0.toJS).initialArguments.toDartInt); + expect(2, + SimpleObject2.oneOptional(4.0.toJS, 5.0.toJS).initialArguments.toDartInt); + + // Test extension type static methods. + expect(0, SimpleObject2.twoOptionalStatic().toDartInt); + expect(1, SimpleObject2.twoOptionalStatic(4.0.toJS).toDartInt); + expect(2, SimpleObject2.twoOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt); + + expect(1, SimpleObject2.oneOptionalStatic(4.0.toJS).toDartInt); + expect(2, SimpleObject2.oneOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt); + + // Test extension type methods. + final s2 = SimpleObject2(); + expect(0, s2.twoOptional().toDartInt); + expect(1, s2.twoOptional(4.0.toJS).toDartInt); + expect(2, s2.twoOptional(4.0.toJS, 5.0.toJS).toDartInt); + + expect(1, s2.oneOptional(4.0.toJS).toDartInt); + expect(2, s2.oneOptional(4.0.toJS, 5.0.toJS).toDartInt); } void testOtherLibrary() { // Test top level methods. - expect(1, other.oneOptional(4.0.toJS).toDartInt); - expect(2, other.oneOptional(4.0.toJS, 5.0.toJS).toDartInt); + expect(1, namespace.oneOptional(4.0.toJS).toDartInt); + expect(2, namespace.oneOptional(4.0.toJS, 5.0.toJS).toDartInt); // Test factories. - expect( - 1, other.SimpleObject.oneOptional(4.0.toJS).initialArguments.toDartInt); + expect(1, + namespace.SimpleObject.oneOptional(4.0.toJS).initialArguments.toDartInt); expect( 2, - other.SimpleObject.oneOptional(4.0.toJS, 5.0.toJS) + namespace.SimpleObject.oneOptional(4.0.toJS, 5.0.toJS) .initialArguments .toDartInt); // Test static methods. - expect(1, other.SimpleObject.oneOptionalStatic(4.0.toJS).toDartInt); - expect(2, other.SimpleObject.oneOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt); + expect(1, namespace.SimpleObject.oneOptionalStatic(4.0.toJS).toDartInt); + expect(2, + namespace.SimpleObject.oneOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt); // Test extension methods. - final s = other.SimpleObject(); + final s = namespace.SimpleObject(); expect(1, s.oneOptional(4.0.toJS).toDartInt); expect(2, s.oneOptional(4.0.toJS, 5.0.toJS).toDartInt); + + // Test extension type factories. + expect(1, + namespace.SimpleObject.oneOptional(4.0.toJS).initialArguments.toDartInt); + expect( + 2, + namespace.SimpleObject.oneOptional(4.0.toJS, 5.0.toJS) + .initialArguments + .toDartInt); + + // Test extension type static methods. + expect(1, namespace.SimpleObject.oneOptionalStatic(4.0.toJS).toDartInt); + expect(2, + namespace.SimpleObject.oneOptionalStatic(4.0.toJS, 5.0.toJS).toDartInt); + + // Test extension type methods. + final s2 = namespace.SimpleObject(); + expect(1, s2.oneOptional(4.0.toJS).toDartInt); + expect(2, s2.oneOptional(4.0.toJS, 5.0.toJS).toDartInt); } void main() { @@ -127,5 +188,19 @@ void main() { } '''); testCurrentLibrary(); + // Move the declarations to a namespace and delete the declarations on + // globalThis to make sure we incorporate the library prefix in + // invocation-level lowering. + eval(''' + var library1 = {}; + globalThis.library1 = library1; + + library1.twoOptional = globalThis.twoOptional; + library1.oneOptional = globalThis.oneOptional; + delete globalThis.twoOptional; + delete globalThis.oneOptional; + library1.SimpleObject = globalThis.SimpleObject; + delete globalThis.SimpleObject; + '''); testOtherLibrary(); } diff --git a/tests/lib/js/static_interop_test/js_default_other_library.dart b/tests/lib/js/static_interop_test/js_default_with_namespaces.dart similarity index 56% rename from tests/lib/js/static_interop_test/js_default_other_library.dart rename to tests/lib/js/static_interop_test/js_default_with_namespaces.dart index fa769a3c39e9..a42b9785cc26 100644 --- a/tests/lib/js/static_interop_test/js_default_other_library.dart +++ b/tests/lib/js/static_interop_test/js_default_with_namespaces.dart @@ -1,8 +1,9 @@ -// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2024, 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. -library js_default_other_library; +@JS('library1') +library js_default_with_namespaces; import 'dart:js_interop'; @@ -20,5 +21,16 @@ extension SimpleObjectExtension on SimpleObject { external JSNumber oneOptional(JSNumber n1, [JSNumber n2]); } +@JS('SimpleObject') +extension type SimpleObject2._(JSObject _) implements JSObject { + external factory SimpleObject2(); + external factory SimpleObject2.oneOptional(JSNumber n1, [JSNumber n2]); + + external static JSNumber oneOptionalStatic(JSNumber n1, [JSNumber n2]); + + external JSNumber get initialArguments; + external JSNumber oneOptional(JSNumber n1, [JSNumber n2]); +} + @JS() external JSNumber oneOptional(JSNumber n1, [JSNumber n2]);