[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 <sigmund@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
This commit is contained in:
Stephen Adams 2023-08-10 14:42:38 +00:00 committed by Commit Queue
parent aba5663d0a
commit 7f08f8e494
9 changed files with 151 additions and 7 deletions

View file

@ -568,9 +568,15 @@ abstract class CommonElements {
late final ClassEntity jsUnknownJavaScriptObjectClass = late final ClassEntity jsUnknownJavaScriptObjectClass =
_findInterceptorsClass('UnknownJavaScriptObject'); _findInterceptorsClass('UnknownJavaScriptObject');
late final ClassEntity jsJavaScriptBigIntClass =
_findInterceptorsClass('JavaScriptBigInt');
late final ClassEntity jsJavaScriptFunctionClass = late final ClassEntity jsJavaScriptFunctionClass =
_findInterceptorsClass('JavaScriptFunction'); _findInterceptorsClass('JavaScriptFunction');
late final ClassEntity jsJavaScriptSymbolClass =
_findInterceptorsClass('JavaScriptSymbol');
InterfaceType get jsJavaScriptFunctionType => InterfaceType get jsJavaScriptFunctionType =>
_getRawType(jsJavaScriptFunctionClass); _getRawType(jsJavaScriptFunctionClass);

View file

@ -395,7 +395,9 @@ class BackendImpacts {
_commonElements.jsJavaScriptObjectClass, _commonElements.jsJavaScriptObjectClass,
_commonElements.jsLegacyJavaScriptObjectClass, _commonElements.jsLegacyJavaScriptObjectClass,
_commonElements.jsPlainJavaScriptObjectClass, _commonElements.jsPlainJavaScriptObjectClass,
_commonElements.jsJavaScriptFunctionClass _commonElements.jsJavaScriptBigIntClass,
_commonElements.jsJavaScriptFunctionClass,
_commonElements.jsJavaScriptSymbolClass
], ],
features: EnumSet<BackendFeature>.fromValues([ features: EnumSet<BackendFeature>.fromValues([
BackendFeature.needToInitializeDispatchProperty, BackendFeature.needToInitializeDispatchProperty,
@ -460,7 +462,9 @@ class BackendImpacts {
_commonElements.jsJavaScriptObjectClass, _commonElements.jsJavaScriptObjectClass,
_commonElements.jsLegacyJavaScriptObjectClass, _commonElements.jsLegacyJavaScriptObjectClass,
_commonElements.jsPlainJavaScriptObjectClass, _commonElements.jsPlainJavaScriptObjectClass,
_commonElements.jsJavaScriptFunctionClass _commonElements.jsJavaScriptBigIntClass,
_commonElements.jsJavaScriptFunctionClass,
_commonElements.jsJavaScriptSymbolClass
], ],
); );

View file

@ -308,8 +308,12 @@ class CodegenEnqueuerListener extends EnqueuerListener {
registerInstantiation(_commonElements.jsPlainJavaScriptObjectClass); registerInstantiation(_commonElements.jsPlainJavaScriptObjectClass);
} else if (cls == _commonElements.jsUnknownJavaScriptObjectClass) { } else if (cls == _commonElements.jsUnknownJavaScriptObjectClass) {
registerInstantiation(_commonElements.jsUnknownJavaScriptObjectClass); registerInstantiation(_commonElements.jsUnknownJavaScriptObjectClass);
} else if (cls == _commonElements.jsJavaScriptBigIntClass) {
registerInstantiation(_commonElements.jsJavaScriptBigIntClass);
} else if (cls == _commonElements.jsJavaScriptFunctionClass) { } else if (cls == _commonElements.jsJavaScriptFunctionClass) {
registerInstantiation(_commonElements.jsJavaScriptFunctionClass); registerInstantiation(_commonElements.jsJavaScriptFunctionClass);
} else if (cls == _commonElements.jsJavaScriptSymbolClass) {
registerInstantiation(_commonElements.jsJavaScriptSymbolClass);
} else if (cls == _commonElements.jsIndexingBehaviorInterface) { } else if (cls == _commonElements.jsIndexingBehaviorInterface) {
_impacts.jsIndexingBehavior _impacts.jsIndexingBehavior
.registerImpact(impactBuilder, _elementEnvironment); .registerImpact(impactBuilder, _elementEnvironment);

View file

@ -400,9 +400,13 @@ class ResolutionEnqueuerListener extends EnqueuerListener {
} else if (cls == _commonElements.jsUnknownJavaScriptObjectClass) { } else if (cls == _commonElements.jsUnknownJavaScriptObjectClass) {
_addInterceptors( _addInterceptors(
_commonElements.jsUnknownJavaScriptObjectClass, impactBuilder); _commonElements.jsUnknownJavaScriptObjectClass, impactBuilder);
} else if (cls == _commonElements.jsJavaScriptBigIntClass) {
_addInterceptors(_commonElements.jsJavaScriptBigIntClass, impactBuilder);
} else if (cls == _commonElements.jsJavaScriptFunctionClass) { } else if (cls == _commonElements.jsJavaScriptFunctionClass) {
_addInterceptors( _addInterceptors(
_commonElements.jsJavaScriptFunctionClass, impactBuilder); _commonElements.jsJavaScriptFunctionClass, impactBuilder);
} else if (cls == _commonElements.jsJavaScriptSymbolClass) {
_addInterceptors(_commonElements.jsJavaScriptSymbolClass, impactBuilder);
} else if (_nativeData.isNativeOrExtendsNative(cls)) { } else if (_nativeData.isNativeOrExtendsNative(cls)) {
_interceptorData.addInterceptorsForNativeClassMembers(cls); _interceptorData.addInterceptorsForNativeClassMembers(cls);
} else if (cls == _commonElements.jsIndexingBehaviorInterface) { } else if (cls == _commonElements.jsIndexingBehaviorInterface) {

View file

@ -74,8 +74,12 @@ class InterceptorStubGenerator {
condition = js('(typeof receiver) == "string"'); condition = js('(typeof receiver) == "string"');
} else if (cls == _commonElements.jsNullClass) { } else if (cls == _commonElements.jsNullClass) {
condition = js('receiver == null'); condition = js('receiver == null');
} else if (cls == _commonElements.jsJavaScriptBigIntClass) {
condition = js('(typeof receiver) == "bigint"');
} else if (cls == _commonElements.jsJavaScriptFunctionClass) { } else if (cls == _commonElements.jsJavaScriptFunctionClass) {
condition = js('(typeof receiver) == "function"'); condition = js('(typeof receiver) == "function"');
} else if (cls == _commonElements.jsJavaScriptSymbolClass) {
condition = js('(typeof receiver) == "symbol"');
} else { } else {
throw 'internal error'; throw 'internal error';
} }
@ -92,7 +96,9 @@ class InterceptorStubGenerator {
bool hasNative = false; bool hasNative = false;
bool anyNativeClasses = _nativeCodegenEnqueuer.hasInstantiatedNativeClasses; bool anyNativeClasses = _nativeCodegenEnqueuer.hasInstantiatedNativeClasses;
bool hasJavaScriptFunction = false; bool hasJavaScriptFunction = false;
bool hasJavaScriptBigInt = false;
bool hasJavaScriptObject = false; bool hasJavaScriptObject = false;
bool hasJavaScriptSymbol = false;
for (ClassEntity cls in classes) { for (ClassEntity cls in classes) {
if (cls == _commonElements.jsArrayClass || if (cls == _commonElements.jsArrayClass ||
@ -112,8 +118,12 @@ class InterceptorStubGenerator {
hasNumber = true; hasNumber = true;
else if (cls == _commonElements.jsStringClass) else if (cls == _commonElements.jsStringClass)
hasString = true; hasString = true;
else if (cls == _commonElements.jsJavaScriptBigIntClass)
hasJavaScriptBigInt = true;
else if (cls == _commonElements.jsJavaScriptFunctionClass) else if (cls == _commonElements.jsJavaScriptFunctionClass)
hasJavaScriptFunction = true; hasJavaScriptFunction = true;
else if (cls == _commonElements.jsJavaScriptSymbolClass)
hasJavaScriptSymbol = true;
else if (cls == _commonElements.jsJavaScriptObjectClass) else if (cls == _commonElements.jsJavaScriptObjectClass)
hasJavaScriptObject = true; hasJavaScriptObject = true;
else { else {
@ -190,11 +200,21 @@ class InterceptorStubGenerator {
// If a program `hasNative` then we will insert a check for // If a program `hasNative` then we will insert a check for
// `JavaScriptFunction` in the `hasNative` block of the interceptor logic. // `JavaScriptFunction` in the `hasNative` block of the interceptor logic.
// Otherwise, we have to insert a specific check for `JavScriptFunction. // Otherwise, we have to insert a specific check for `JavaScriptFunction.
if (hasJavaScriptFunction && !hasNative) { if (!hasNative) {
if (hasJavaScriptFunction) {
statements.add( statements.add(
buildInterceptorCheck(_commonElements.jsJavaScriptFunctionClass)); buildInterceptorCheck(_commonElements.jsJavaScriptFunctionClass));
} }
if (hasJavaScriptSymbol) {
statements.add(
buildInterceptorCheck(_commonElements.jsJavaScriptSymbolClass));
}
if (hasJavaScriptBigInt) {
statements.add(
buildInterceptorCheck(_commonElements.jsJavaScriptBigIntClass));
}
}
if (hasJavaScriptObject && !hasNative) { if (hasJavaScriptObject && !hasNative) {
statements.add(js.statement(r''' statements.add(js.statement(r'''
@ -215,12 +235,16 @@ class InterceptorStubGenerator {
statements.add(js.statement(r'''{ statements.add(js.statement(r'''{
if (typeof receiver != "object") { if (typeof receiver != "object") {
if (typeof receiver == "function" ) return #; if (typeof receiver == "function" ) return #;
if (typeof receiver == "symbol" ) return #;
if (typeof receiver == "bigint" ) return #;
return receiver; return receiver;
} }
if (receiver instanceof #) return receiver; if (receiver instanceof #) return receiver;
return #(receiver); return #(receiver);
}''', [ }''', [
interceptorFor(_commonElements.jsJavaScriptFunctionClass), interceptorFor(_commonElements.jsJavaScriptFunctionClass),
interceptorFor(_commonElements.jsJavaScriptSymbolClass),
interceptorFor(_commonElements.jsJavaScriptBigIntClass),
_emitter.constructorAccess(_commonElements.objectClass), _emitter.constructorAccess(_commonElements.objectClass),
_emitter _emitter
.staticFunctionAccess(_commonElements.getNativeInterceptorMethod) .staticFunctionAccess(_commonElements.getNativeInterceptorMethod)

View file

@ -92,7 +92,8 @@ abstract class NativeEnqueuer {
type == _commonElements.stringType || type == _commonElements.stringType ||
type == _commonElements.nullType || type == _commonElements.nullType ||
type == _commonElements.boolType || type == _commonElements.boolType ||
type == _commonElements.jsJavaScriptFunctionType || type.element == _commonElements.jsJavaScriptBigIntClass ||
type.element == _commonElements.jsJavaScriptSymbolClass ||
type == _commonElements.jsJavaScriptObjectType || type == _commonElements.jsJavaScriptObjectType ||
_dartTypes.isSubtype(type, _dartTypes.isSubtype(type,
_elementEnvironment.getRawType(_commonElements.jsArrayClass))) { _elementEnvironment.getRawType(_commonElements.jsArrayClass))) {

View file

@ -464,3 +464,41 @@ final class JavaScriptFunction extends LegacyJavaScriptObject
return 'JavaScript function for ${dartClosure.toString()}'; 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);
}

View file

@ -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)}');
}

View file

@ -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')}');
}