From b11ab9fc23762cd62102ea8870e5d239ee07786e Mon Sep 17 00:00:00 2001 From: Srujan Gaddam Date: Tue, 16 Apr 2024 18:01:04 +0000 Subject: [PATCH] [beta][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 Bug: https://github.com/dart-lang/sdk/issues/55359 Change-Id: Ibf7125fea6da7722549f3c87aafdf881132b104f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362195 Commit-Queue: Srujan Gaddam Reviewed-by: Kevin Chisholm --- 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 ++++ ...al_static_member_with_namespaces_test.dart | 102 ---------- ...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, 394 insertions(+), 362 deletions(-) create mode 100644 tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces.dart delete mode 100644 tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces_test.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/pkg/dart2wasm/lib/js/interop_specializer.dart b/pkg/dart2wasm/lib/js/interop_specializer.dart index 7acec36af828..f4250d20fc1e 100644 --- a/pkg/dart2wasm/lib/js/interop_specializer.dart +++ b/pkg/dart2wasm/lib/js/interop_specializer.dart @@ -341,19 +341,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) { @@ -362,8 +354,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 @@ -420,7 +417,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); @@ -433,7 +431,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)) { @@ -462,7 +461,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 c2d321083b60..ff2b1d40da22 100644 --- a/pkg/dart2wasm/lib/js/interop_transformer.dart +++ b/pkg/dart2wasm/lib/js/interop_transformer.dart @@ -56,7 +56,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 6e53886e3257..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,23 +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. +// 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; @@ -34,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. @@ -88,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/extension_type/external_static_member_with_namespaces_test.dart b/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces_test.dart deleted file mode 100644 index d7d02fd51f98..000000000000 --- a/tests/lib/js/static_interop_test/extension_type/external_static_member_with_namespaces_test.dart +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2023, 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'; -import 'dart:js_util' as js_util; - -import 'package:expect/minitest.dart'; - -@JS() -external void eval(String code); - -@JS('library3.ExternalStatic') -extension type ExternalStatic._(JSObject obj) implements Object { - 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; - @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(); -} - -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(a, b) { - var len = arguments.length; - this.a = len < 1 ? 0 : a; - this.b = len < 2 ? '' : b; - } - 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'; - ''' - ]); - - // Constructors. - void testExternalConstructorCall(ExternalStatic externalStatic) { - expect(js_util.getProperty(externalStatic, 'a'), 0); - expect(js_util.getProperty(externalStatic, 'b'), ''); - } - - testExternalConstructorCall(ExternalStatic()); - testExternalConstructorCall(ExternalStatic.factory()); - testExternalConstructorCall(ExternalStatic.multipleArgs(0, '')); - testExternalConstructorCall(ExternalStatic.differentArgs(0)); - testExternalConstructorCall(ExternalStatic.nonExternal()); - - // 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'); -} 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]);