1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-08 12:06:26 +00:00

[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 =
_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);

View File

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

View File

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

View File

@ -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) {

View File

@ -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)

View File

@ -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))) {

View File

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

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