From 7f08f8e494989127175886def3e7ad570120dde0 Mon Sep 17 00:00:00 2001 From: Stephen Adams Date: Thu, 10 Aug 2023 14:42:38 +0000 Subject: [PATCH] [dart2js] Add interceptors for JavaScript Symbol and BigInt The interceptors provide a Dart `toString` method that uses the JavaScript `toString` method. Issue: #53106 Change-Id: I1cf1df9e24fb4fd2d79679f1f014f39f083be7e9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/319563 Reviewed-by: Sigmund Cherem Commit-Queue: Stephen Adams --- pkg/compiler/lib/src/common/elements.dart | 6 +++ .../lib/src/js_backend/backend_impact.dart | 8 +++- .../lib/src/js_backend/codegen_listener.dart | 4 ++ .../src/js_backend/resolution_listener.dart | 4 ++ .../interceptor_stub_generator.dart | 32 ++++++++++++++-- pkg/compiler/lib/src/native/enqueue.dart | 3 +- .../js_runtime/lib/interceptors.dart | 38 +++++++++++++++++++ tests/web/native/javascript_bigint_test.dart | 32 ++++++++++++++++ tests/web/native/javascript_symbol_test.dart | 31 +++++++++++++++ 9 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 tests/web/native/javascript_bigint_test.dart create mode 100644 tests/web/native/javascript_symbol_test.dart diff --git a/pkg/compiler/lib/src/common/elements.dart b/pkg/compiler/lib/src/common/elements.dart index 4410256a70f..03afdfc3d64 100644 --- a/pkg/compiler/lib/src/common/elements.dart +++ b/pkg/compiler/lib/src/common/elements.dart @@ -568,9 +568,15 @@ abstract class CommonElements { late final ClassEntity jsUnknownJavaScriptObjectClass = _findInterceptorsClass('UnknownJavaScriptObject'); + late final ClassEntity jsJavaScriptBigIntClass = + _findInterceptorsClass('JavaScriptBigInt'); + late final ClassEntity jsJavaScriptFunctionClass = _findInterceptorsClass('JavaScriptFunction'); + late final ClassEntity jsJavaScriptSymbolClass = + _findInterceptorsClass('JavaScriptSymbol'); + InterfaceType get jsJavaScriptFunctionType => _getRawType(jsJavaScriptFunctionClass); diff --git a/pkg/compiler/lib/src/js_backend/backend_impact.dart b/pkg/compiler/lib/src/js_backend/backend_impact.dart index db883e58355..66c476b76c4 100644 --- a/pkg/compiler/lib/src/js_backend/backend_impact.dart +++ b/pkg/compiler/lib/src/js_backend/backend_impact.dart @@ -395,7 +395,9 @@ class BackendImpacts { _commonElements.jsJavaScriptObjectClass, _commonElements.jsLegacyJavaScriptObjectClass, _commonElements.jsPlainJavaScriptObjectClass, - _commonElements.jsJavaScriptFunctionClass + _commonElements.jsJavaScriptBigIntClass, + _commonElements.jsJavaScriptFunctionClass, + _commonElements.jsJavaScriptSymbolClass ], features: EnumSet.fromValues([ BackendFeature.needToInitializeDispatchProperty, @@ -460,7 +462,9 @@ class BackendImpacts { _commonElements.jsJavaScriptObjectClass, _commonElements.jsLegacyJavaScriptObjectClass, _commonElements.jsPlainJavaScriptObjectClass, - _commonElements.jsJavaScriptFunctionClass + _commonElements.jsJavaScriptBigIntClass, + _commonElements.jsJavaScriptFunctionClass, + _commonElements.jsJavaScriptSymbolClass ], ); diff --git a/pkg/compiler/lib/src/js_backend/codegen_listener.dart b/pkg/compiler/lib/src/js_backend/codegen_listener.dart index 4ec981dade4..c3308ac0d17 100644 --- a/pkg/compiler/lib/src/js_backend/codegen_listener.dart +++ b/pkg/compiler/lib/src/js_backend/codegen_listener.dart @@ -308,8 +308,12 @@ class CodegenEnqueuerListener extends EnqueuerListener { registerInstantiation(_commonElements.jsPlainJavaScriptObjectClass); } else if (cls == _commonElements.jsUnknownJavaScriptObjectClass) { registerInstantiation(_commonElements.jsUnknownJavaScriptObjectClass); + } else if (cls == _commonElements.jsJavaScriptBigIntClass) { + registerInstantiation(_commonElements.jsJavaScriptBigIntClass); } else if (cls == _commonElements.jsJavaScriptFunctionClass) { registerInstantiation(_commonElements.jsJavaScriptFunctionClass); + } else if (cls == _commonElements.jsJavaScriptSymbolClass) { + registerInstantiation(_commonElements.jsJavaScriptSymbolClass); } else if (cls == _commonElements.jsIndexingBehaviorInterface) { _impacts.jsIndexingBehavior .registerImpact(impactBuilder, _elementEnvironment); diff --git a/pkg/compiler/lib/src/js_backend/resolution_listener.dart b/pkg/compiler/lib/src/js_backend/resolution_listener.dart index 576d09a7120..245ff199484 100644 --- a/pkg/compiler/lib/src/js_backend/resolution_listener.dart +++ b/pkg/compiler/lib/src/js_backend/resolution_listener.dart @@ -400,9 +400,13 @@ class ResolutionEnqueuerListener extends EnqueuerListener { } else if (cls == _commonElements.jsUnknownJavaScriptObjectClass) { _addInterceptors( _commonElements.jsUnknownJavaScriptObjectClass, impactBuilder); + } else if (cls == _commonElements.jsJavaScriptBigIntClass) { + _addInterceptors(_commonElements.jsJavaScriptBigIntClass, impactBuilder); } else if (cls == _commonElements.jsJavaScriptFunctionClass) { _addInterceptors( _commonElements.jsJavaScriptFunctionClass, impactBuilder); + } else if (cls == _commonElements.jsJavaScriptSymbolClass) { + _addInterceptors(_commonElements.jsJavaScriptSymbolClass, impactBuilder); } else if (_nativeData.isNativeOrExtendsNative(cls)) { _interceptorData.addInterceptorsForNativeClassMembers(cls); } else if (cls == _commonElements.jsIndexingBehaviorInterface) { diff --git a/pkg/compiler/lib/src/js_emitter/interceptor_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/interceptor_stub_generator.dart index f2d6ae1c598..5b9d6087eaa 100644 --- a/pkg/compiler/lib/src/js_emitter/interceptor_stub_generator.dart +++ b/pkg/compiler/lib/src/js_emitter/interceptor_stub_generator.dart @@ -74,8 +74,12 @@ class InterceptorStubGenerator { condition = js('(typeof receiver) == "string"'); } else if (cls == _commonElements.jsNullClass) { condition = js('receiver == null'); + } else if (cls == _commonElements.jsJavaScriptBigIntClass) { + condition = js('(typeof receiver) == "bigint"'); } else if (cls == _commonElements.jsJavaScriptFunctionClass) { condition = js('(typeof receiver) == "function"'); + } else if (cls == _commonElements.jsJavaScriptSymbolClass) { + condition = js('(typeof receiver) == "symbol"'); } else { throw 'internal error'; } @@ -92,7 +96,9 @@ class InterceptorStubGenerator { bool hasNative = false; bool anyNativeClasses = _nativeCodegenEnqueuer.hasInstantiatedNativeClasses; bool hasJavaScriptFunction = false; + bool hasJavaScriptBigInt = false; bool hasJavaScriptObject = false; + bool hasJavaScriptSymbol = false; for (ClassEntity cls in classes) { if (cls == _commonElements.jsArrayClass || @@ -112,8 +118,12 @@ class InterceptorStubGenerator { hasNumber = true; else if (cls == _commonElements.jsStringClass) hasString = true; + else if (cls == _commonElements.jsJavaScriptBigIntClass) + hasJavaScriptBigInt = true; else if (cls == _commonElements.jsJavaScriptFunctionClass) hasJavaScriptFunction = true; + else if (cls == _commonElements.jsJavaScriptSymbolClass) + hasJavaScriptSymbol = true; else if (cls == _commonElements.jsJavaScriptObjectClass) hasJavaScriptObject = true; else { @@ -190,10 +200,20 @@ class InterceptorStubGenerator { // If a program `hasNative` then we will insert a check for // `JavaScriptFunction` in the `hasNative` block of the interceptor logic. - // Otherwise, we have to insert a specific check for `JavScriptFunction. - if (hasJavaScriptFunction && !hasNative) { - statements.add( - buildInterceptorCheck(_commonElements.jsJavaScriptFunctionClass)); + // Otherwise, we have to insert a specific check for `JavaScriptFunction. + if (!hasNative) { + if (hasJavaScriptFunction) { + statements.add( + buildInterceptorCheck(_commonElements.jsJavaScriptFunctionClass)); + } + if (hasJavaScriptSymbol) { + statements.add( + buildInterceptorCheck(_commonElements.jsJavaScriptSymbolClass)); + } + if (hasJavaScriptBigInt) { + statements.add( + buildInterceptorCheck(_commonElements.jsJavaScriptBigIntClass)); + } } if (hasJavaScriptObject && !hasNative) { @@ -215,12 +235,16 @@ class InterceptorStubGenerator { statements.add(js.statement(r'''{ if (typeof receiver != "object") { if (typeof receiver == "function" ) return #; + if (typeof receiver == "symbol" ) return #; + if (typeof receiver == "bigint" ) return #; return receiver; } if (receiver instanceof #) return receiver; return #(receiver); }''', [ interceptorFor(_commonElements.jsJavaScriptFunctionClass), + interceptorFor(_commonElements.jsJavaScriptSymbolClass), + interceptorFor(_commonElements.jsJavaScriptBigIntClass), _emitter.constructorAccess(_commonElements.objectClass), _emitter .staticFunctionAccess(_commonElements.getNativeInterceptorMethod) diff --git a/pkg/compiler/lib/src/native/enqueue.dart b/pkg/compiler/lib/src/native/enqueue.dart index 2317c91b608..bcf9b867479 100644 --- a/pkg/compiler/lib/src/native/enqueue.dart +++ b/pkg/compiler/lib/src/native/enqueue.dart @@ -92,7 +92,8 @@ abstract class NativeEnqueuer { type == _commonElements.stringType || type == _commonElements.nullType || type == _commonElements.boolType || - type == _commonElements.jsJavaScriptFunctionType || + type.element == _commonElements.jsJavaScriptBigIntClass || + type.element == _commonElements.jsJavaScriptSymbolClass || type == _commonElements.jsJavaScriptObjectType || _dartTypes.isSubtype(type, _elementEnvironment.getRawType(_commonElements.jsArrayClass))) { diff --git a/sdk/lib/_internal/js_runtime/lib/interceptors.dart b/sdk/lib/_internal/js_runtime/lib/interceptors.dart index 8efa303b66a..cd4d96c9a52 100644 --- a/sdk/lib/_internal/js_runtime/lib/interceptors.dart +++ b/sdk/lib/_internal/js_runtime/lib/interceptors.dart @@ -464,3 +464,41 @@ final class JavaScriptFunction extends LegacyJavaScriptObject return 'JavaScript function for ${dartClosure.toString()}'; } } + +/// Interceptor for JavaScript BigInt primitive values, i.e. values `x` for +/// which `typeof x == "bigint"`. +final class JavaScriptBigInt extends Interceptor { + const JavaScriptBigInt(); + + // JavaScript BigInt objects don't have any operations that look efficient + // enough to generate a good hash code. + // + // TODO(https://dartbug.com/53162): "bigint" primitive values might be used as + // keys without using the `hashCode`, but that will not help for compound hash + // values, e.g. produced by `Object.hash`. + int get hashCode => 0; + + /// Returns the result of the JavaScript BigInt's `toString` method. + String toString() => JS('String', 'String(#)', this); +} + +/// Interceptor for JavaScript Symbol primitive values, i.e. values `x` for +/// which `typeof x == "symbol"`. +final class JavaScriptSymbol extends Interceptor { + const JavaScriptSymbol(); + + // It is not clear what to do for a Symbol's hashCode. Registered Symbols + // [can't be keys of WeakMaps][1]. Using a property won't work because the + // property will be added to the ephmeral Object wrapper, either being an + // incorrect operation or an error in strict mode. + // + // TODO(https://dartbug.com/53162): "symbol" primitive values might be used as + // keys without using the `hashCode`, but that will not help for compound hash + // values, e.g. produced by `Object.hash`. + // + // [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry + int get hashCode => 0; + + /// Returns the result of the JavaScript Symbol's `toString` method. + String toString() => JS('String', 'String(#)', this); +} diff --git a/tests/web/native/javascript_bigint_test.dart b/tests/web/native/javascript_bigint_test.dart new file mode 100644 index 00000000000..ef124306063 --- /dev/null +++ b/tests/web/native/javascript_bigint_test.dart @@ -0,0 +1,32 @@ +// 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. + +import 'native_testing.dart'; + +import 'dart:_interceptors'; + +dynamic makeBigIntDynamic(String name) native; +JavaScriptBigInt makeBigInt(String name) native; + +void setup() { + JS('', r""" +(function(){ + self.makeBigInt = function(name){return BigInt(name)}; + self.makeBigIntDynamic = function(name){return BigInt(name)}; +})()"""); +} + +main() { + nativeTesting(); + setup(); + const s = '9876543210000000000000123456789'; + + Expect.notEquals(s, makeBigInt(s)); + Expect.notEquals(s, makeBigIntDynamic(s)); + + Expect.equals(s, makeBigInt(s).toString()); + Expect.equals(s, makeBigIntDynamic(s).toString()); + Expect.equals(s, '${makeBigInt(s)}'); + Expect.equals(s, '${makeBigIntDynamic(s)}'); +} diff --git a/tests/web/native/javascript_symbol_test.dart b/tests/web/native/javascript_symbol_test.dart new file mode 100644 index 00000000000..4b866fd04a8 --- /dev/null +++ b/tests/web/native/javascript_symbol_test.dart @@ -0,0 +1,31 @@ +// 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. + +import 'native_testing.dart'; + +import 'dart:_interceptors'; + +dynamic makeSymbolDynamic(String name) native; +JavaScriptSymbol makeSymbol(String name) native; + +void setup() { + JS('', r""" +(function(){ + self.makeSymbol = function(name){return Symbol(name)}; + self.makeSymbolDynamic = function(name){return Symbol(name)}; +})()"""); +} + +main() { + nativeTesting(); + setup(); + + Expect.notEquals('Symbol(foo)', makeSymbol('foo')); + Expect.notEquals('Symbol(foo)', makeSymbolDynamic('foo')); + + Expect.equals('Symbol(foo)', makeSymbol('foo').toString()); + Expect.equals('Symbol(foo)', makeSymbolDynamic('foo').toString()); + Expect.equals('Symbol(foo)', '${makeSymbol('foo')}'); + Expect.equals('Symbol(foo)', '${makeSymbolDynamic('foo')}'); +}