Revert "[ddc] Overhauling tearoff equality and identity."

This reverts commit e4c4d0f839.

Reason for revert: Breaks are blocking roll into flutter
https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8720592021329723649/+/u/run_test.dart_for_web_canvaskit_tests_shard_and_subshard_0/stdout

Original change's description:
> [ddc] Overhauling tearoff equality and identity.
>
> Hot reload requires DDC to update how its tearoffs are represented. Tearoffs obey the following conventions:
> * Instance tearoffs are never identical
> * Tearoffs with the same object target and name have the same hash code (even if they resolve to different functions across hot reloads)
> * Two separate tearoffs of the same member are equal
>
> To support this, tearoff equality must not depend on the bound object and method but a composite of the bound object, torn off member name, and the exact class/object from which the member was torn off.
>
> Notable changes:
> * Methods' immediately bound targets are emitted with member signatures. This is required to determine the bound targets for instance and dynamic tearoffs. Bound targets are identified by `libraryUri:class` strings.
> * `applyMixin` passes in a 'true' bound target. This is because mixin applications' members are considered children of their 'on' class (not the mixed in class) wrt equality/hashCode.
> * `bind` is modified to pass in its 'true' bound object to support mixins' super getters.
> * `tearoff` and `staticTearoff` are modified to accept a bound target string (only required for static tearoffs, as they are bound at tearoff-creation-time).
> * Static tearoffs avoid using their bound object for hashcode and equality, as these libraries may be wrapped in proxy objects.
> * Tearoff equality and hashCode are updated to consider bound object, bound name, and its bound method's immediate target.
> * 'noSuchMethod' and 'toString' methods are always accessed through their extension property during signature lookups.
>
>
> Change-Id: Ica5501b6860c605db50aa945bafb6802a7317511
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406723
> Reviewed-by: Nate Biggs <natebiggs@google.com>
> Reviewed-by: Nicholas Shahan <nshahan@google.com>
> Commit-Queue: Mark Zhou <markzipan@google.com>

Change-Id: Ic5694976260189f7215dfa3c2318e9a0656f0de6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/415100
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
This commit is contained in:
Nicholas Shahan
2025-03-12 12:46:38 -07:00
committed by Commit Queue
parent 3d30b2f17e
commit 58ba6006a6
14 changed files with 170 additions and 527 deletions
+107 -153
View File
@@ -75,20 +75,6 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
@override
final classIdentifiers = <Class, js_ast.Identifier>{};
/// Maps every mixin application to a unique identifier.
///
/// A mixin application is represented as a (mixin, class) pair, where
/// 'mixin' is being mixed into 'class'. Anonymous mixins are already
/// unique per mixin application and so pass themselves in as both 'mixin'
/// and 'class'.
///
/// This mapping is used when generating super property getters in mixins.
final Map<(Class, Class), js_ast.Identifier> _mixinCache = {};
/// Records a reference to a mixin application's passed in superclass.
/// (see [_emitMixinStatement]).
final Map<Class, js_ast.Identifier> _mixinSuperclassCache = {};
/// Maps each class `Member` node compiled in the module to the name used for
/// the member in JavaScript.
///
@@ -971,13 +957,6 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
// a simpler representation within instance members of the class.
_currentTypeEnvironment = ClassTypeEnvironment(c.typeParameters);
// Store identifiers for a mixin application's passed in superclass.
// (see [_emitMixinStatement]).
if (c.isMixinDeclaration && !c.isMixinClass) {
_mixinSuperclassCache.putIfAbsent(
c, () => _emitScopedId(getLocalClassName(c.superclass!)));
}
// Mixins are unrolled in _defineClass.
if (!c.isAnonymousMixin) {
// If this class is annotated with `@JS`, then we only need to emit the
@@ -1051,24 +1030,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
});
var jsCtors = _defineConstructors(c, className);
var jsMethods = _emitClassMethods(c);
var jsStaticMethodTypeTags = <js_ast.Statement>[];
for (var member in c.procedures) {
// TODO(#57049): We tag all static members because we don't know if
// they've been changed after a hot reload. This won't be necessary if we
// can tag them during the delta diff phase.
if (member.isStatic && _reifyTearoff(member) && !member.isExternal) {
var result = _emitStaticTarget(member);
// We only need to tag static functions that are torn off at
// compile-time. We attach these late so tearoffs have access to
// their types.
var reifiedType = member.function
.computeThisFunctionType(member.enclosingLibrary.nonNullable);
jsStaticMethodTypeTags.add(
_emitFunctionTagged(result, reifiedType, asLazy: true)
.toStatement());
}
}
_emitSuperHelperSymbols(body);
// Deferred supertypes must be evaluated lazily while emitting classes to
@@ -1079,7 +1042,6 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
// Emit the class, e.g. `core.Object = class Object { ... }`
_defineClass(c, className, jsMethods, body, deferredSupertypes);
body.addAll(jsCtors);
body.addAll(jsStaticMethodTypeTags);
// Emit things that come after the ES6 `class ... { ... }`.
@@ -1255,7 +1217,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
var instanceMethods = methods.where((m) => !m.isStatic).toList();
body.add(_emitClassStatement(c, className, heritage, staticMethods));
var superclassId = _mixinSuperclassCache[c]!;
var superclassId = _emitScopedId(getLocalClassName(c.superclass!));
var classId = className is js_ast.Identifier
? className
: _emitScopedId(getLocalClassName(c));
@@ -1319,6 +1281,15 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
return _emitJSInterop(t.classNode) ?? _emitClassRef(t);
}
js_ast.Expression getBaseClass(int count) {
var base = emitDeferredClassRef(
c.getThisType(_coreTypes, c.enclosingLibrary.nonNullable));
while (--count >= 0) {
base = _emitJSObjectGetPrototypeOf(base, fullyQualifiedName: true);
}
return base;
}
// Find the real (user declared) superclass and the list of mixins.
// We'll use this to unroll the intermediate classes.
//
@@ -1401,7 +1372,9 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
_declareBeforeUse(mixinClass);
var mixinType =
_hierarchy.getClassAsInstanceOf(c, mixinClass)!.asInterfaceType;
var mixinId = _emitMixinId(m, m.isAnonymousMixin ? m : c);
var mixinName =
'${getLocalClassName(superclass)}_${getLocalClassName(mixinClass)}';
var mixinId = _emitScopedId('$mixinName\$');
// Collect all forwarding stub members from anonymous mixins classes.
// These can contain covariant parameter checks that need to be applied.
var savedClassProperties = _classProperties;
@@ -1439,25 +1412,27 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
_currentTypeEnvironment = savedTypeEnvironment;
_classProperties = savedClassProperties;
// TODO(markzipan): We originally bound mixin classes to a temporary as a
// workaround for a now-resolved Chrome issue. However, a side effect of
// this operation is that mixin IDs are renamed by the local visitor. We
// can remove this hoisting after we give mixins unique names.
// Bind the mixin class to a name to workaround a V8 bug with es6 classes
// and anonymous function names.
// TODO(leafp:) Eliminate this once the bug is fixed:
// https://bugs.chromium.org/p/v8/issues/detail?id=7069
body.add(js.statement('const # = #', [
mixinId,
js_ast.ClassExpression(_emitScopedId('${mixinId.name}\$'), baseClass,
forwardingMethodStubs)
js_ast.ClassExpression(
_emitScopedId(mixinName), baseClass, forwardingMethodStubs)
]));
emitMixinConstructors(mixinId, superclass, mixinClass, mixinType);
hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(mixinClass);
var mixinTargetLabel = js.string(fullyResolvedMixinClassLabel(m));
if (shouldDefer(mixinType)) {
deferredSupertypes.add(() => _runtimeStatement('applyMixin(#, #, #)',
[mixinId, emitDeferredClassRef(mixinType), mixinTargetLabel]));
deferredSupertypes.add(() => _runtimeStatement('applyMixin(#, #)', [
getBaseClass(mixinApplications.length - i),
emitDeferredClassRef(mixinType)
]));
} else {
body.add(_runtimeStatement('applyMixin(#, #, #)',
[mixinId, emitClassRef(mixinType), baseClass]));
body.add(_runtimeStatement(
'applyMixin(#, #)', [mixinId, emitClassRef(mixinType)]));
}
baseClass = mixinId;
@@ -1633,7 +1608,6 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
var staticMethods = <js_ast.Property>[];
var instanceMethods = <js_ast.Property>[];
var instanceMethodsDefaultTypeArgs = <js_ast.Property>[];
var methodsImmediateTarget = <js_ast.Property>[];
var staticGetters = <js_ast.Property>[];
var instanceGetters = <js_ast.Property>[];
var staticSetters = <js_ast.Property>[];
@@ -1687,15 +1661,9 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
var needsSignature = memberOverride == null ||
reifiedType != _memberRuntimeType(memberOverride, c);
var memberName = _declareMemberName(member);
if (!member.isAccessor) {
var immediateTarget = js.string(fullyResolvedTargetLabel(member));
methodsImmediateTarget
.add(js_ast.Property(memberName, immediateTarget));
}
if (needsSignature) {
js_ast.Expression type;
var memberName = _declareMemberName(member);
if (member.isAccessor) {
// These signatures are used for dynamic access and to inform the
// debugger. The `arrayRti` accessor is only used by the dart:_rti
@@ -1748,7 +1716,6 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
emitSignature('Method', instanceMethods);
emitSignature('MethodsDefaultTypeArg', instanceMethodsDefaultTypeArgs);
emitSignature('MethodsImmediateTarget', methodsImmediateTarget);
// TODO(40273) Skip for all statics when the debugger consumes signature
// information from symbol files.
emitSignature('StaticMethod', staticMethods);
@@ -2777,13 +2744,12 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
///
/// Unlike call sites, we always have an element available, so we can use it
/// directly rather than computing the relevant options for [_emitMemberName].
js_ast.Expression _declareMemberName(Member m, {bool useExtension = false}) {
js_ast.Expression _declareMemberName(Member m, {bool? useExtension}) {
var c = m.enclosingClass;
var actualUseExtension =
useExtension || (c != null && _extensionTypes.isNativeClass(c));
return _emitMemberName(m.name.text,
isStatic: m is Field ? m.isStatic : (m as Procedure).isStatic,
useExtension: actualUseExtension,
useExtension:
useExtension ?? c != null && _extensionTypes.isNativeClass(c),
member: m);
}
@@ -3111,23 +3077,10 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
.where((p) =>
!p.isExternal && !p.isAbstract && !_isStaticInteropTearOff(p))
.toList();
for (var p in procedures) {
if (!p.isAccessor) {
_moduleItems.add(_emitLibraryFunction(p));
}
// TODO(#57049): We tag all static members because we don't know if
// they've been changed after a hot reload. This won't be necessary if we
// can tag them during the delta diff phase.
if (p.isStatic && _reifyTearoff(p) && !p.isExternal) {
var nameExpr = _emitTopLevelName(p);
_moduleItems.add(_emitFunctionTagged(
nameExpr,
p.function
.computeThisFunctionType(p.enclosingLibrary.nonNullable),
asLazy: true)
.toStatement());
}
}
_moduleItems.addAll(procedures
.where((p) => !p.isAccessor)
.map(_emitLibraryFunction)
.toList());
_emitLibraryAccessors(procedures.where((p) => p.isAccessor).toList());
}
@@ -3265,14 +3218,13 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
}
js_ast.Expression _emitFunctionTagged(js_ast.Expression fn, FunctionType type,
{bool asLazy = false}) {
{bool topLevel = false}) {
var lazy = topLevel && !_canEmitTypeAtTopLevel(type);
var typeRep = _emitType(
// Avoid tagging a closure as Function? or Function*
type.withDeclaredNullability(Nullability.nonNullable));
if (type.typeParameters.isEmpty) {
return asLazy
? _runtimeCall('lazyFn(#, () => #)', [fn, typeRep])
: _runtimeCall('fn(#, #)', [fn, typeRep]);
return _runtimeCall(lazy ? 'lazyFn(#, #)' : 'fn(#, #)', [fn, typeRep]);
} else {
var typeParameterDefaults = [
for (var parameter in type.typeParameters)
@@ -3280,11 +3232,56 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
];
var defaultInstantiatedBounds =
_emitConstList(const DynamicType(), typeParameterDefaults);
return asLazy
? _runtimeCall('lazyGFn(#, () => #, () => #)',
[fn, typeRep, defaultInstantiatedBounds])
: _runtimeCall(
'gFn(#, #, #)', [fn, typeRep, defaultInstantiatedBounds]);
return _runtimeCall(
'gFn(#, #, #)', [fn, typeRep, defaultInstantiatedBounds]);
}
}
/// Whether the expression for [type] can be evaluated at this point in the JS
/// module.
///
/// Types cannot be evaluated if they depend on something that hasn't been
/// defined yet. For example:
///
/// C foo() => null;
/// class C {}
///
/// If we're emitting the type information for `foo`, we cannot refer to `C`
/// yet, so we must evaluate foo's type lazily.
bool _canEmitTypeAtTopLevel(DartType type) {
switch (type) {
case InterfaceType():
return !_pendingClasses!.contains(type.classNode) &&
type.typeArguments.every(_canEmitTypeAtTopLevel);
case FutureOrType():
return !_pendingClasses!.contains(_coreTypes.deprecatedFutureOrClass) &&
_canEmitTypeAtTopLevel(type.typeArgument);
case FunctionType():
// Generic functions are always safe to emit, because they're lazy until
// type arguments are applied.
if (type.typeParameters.isNotEmpty) return true;
return _canEmitTypeAtTopLevel(type.returnType) &&
type.positionalParameters.every(_canEmitTypeAtTopLevel) &&
type.namedParameters.every((n) => _canEmitTypeAtTopLevel(n.type));
case RecordType():
return type.positional.every(_canEmitTypeAtTopLevel) &&
type.named.every((n) => _canEmitTypeAtTopLevel(n.type));
case TypedefType():
return type.typeArguments.every(_canEmitTypeAtTopLevel);
case ExtensionType():
return _canEmitTypeAtTopLevel(type.extensionTypeErasure);
case DynamicType():
case VoidType():
case NeverType():
case NullType():
case IntersectionType():
case TypeParameterType():
case StructuralParameterType():
return true;
case AuxiliaryType():
throwUnsupportedAuxiliaryType(type);
case InvalidType():
throwUnsupportedInvalidType(type);
}
}
@@ -5018,7 +5015,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
}
var jsMemberName = _emitMemberName(memberName, member: member);
if (_reifyTearoff(member)) {
return _runtimeCall('tearoff(#, null, #)', [jsReceiver, jsMemberName]);
return _runtimeCall('bind(#, #)', [jsReceiver, jsMemberName]);
}
var jsPropertyAccess = js_ast.PropertyAccess(jsReceiver, jsMemberName);
return isJsMember(member)
@@ -5159,28 +5156,11 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
return _emitSuperPropertyGet(node.interfaceTarget);
}
/// Emits a reference to a distinct mixin application, represented by
/// a [mixedInClass] being mixed into [baseClass].
///
/// Anonymous mixins should pass themselves as [baseClass] since they are
/// already uniquely generated per distinct mixin application
js_ast.Identifier _emitMixinId(Class mixedInClass, Class baseClass) {
return _mixinCache.putIfAbsent(
(mixedInClass, baseClass), () => _emitScopedId(mixedInClass.name));
}
js_ast.Expression _emitSuperPropertyGet(Member target) {
if (_reifyTearoff(target)) {
if (_superAllowed) {
var jsTarget = _emitSuperTarget(target);
var jsName = _declareMemberName(target);
var enclosingClass = target.enclosingClass!;
var supertypeReference = _mixinSuperclassCache[_currentClass!] ??
(enclosingClass.isAnonymousMixin
? _emitMixinId(enclosingClass, enclosingClass)
: _emitTopLevelNameNoExternalInterop(enclosingClass));
return _runtimeCall(
'bind(this, #, #, #)', [supertypeReference, jsName, jsTarget]);
return _runtimeCall('bind(this, #, #)', [jsTarget.selector, jsTarget]);
} else {
return _emitSuperTearoff(target);
}
@@ -5224,15 +5204,15 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
_emitStaticGet(node.target);
js_ast.Expression _emitStaticGet(Member target) {
var propertyAccessor = _emitStaticTarget(target);
var context = propertyAccessor.receiver;
var property = propertyAccessor.selector;
var result = js.call('#.#', [context, property]);
var result = _emitStaticTarget(target);
if (_reifyTearoff(target)) {
var enclosingMemberTargetName =
js.string(fullyResolvedTargetLabel(target));
return _runtimeCall('staticTearoff(#, #, #)',
[context, enclosingMemberTargetName, property]);
// TODO(jmesserly): we could tag static/top-level function types once
// in the module initialization, rather than at the point where they
// escape.
return _emitFunctionTagged(
result,
target.function!
.computeThisFunctionType(target.enclosingLibrary.nonNullable));
}
return result;
}
@@ -5866,7 +5846,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
/// Emits the [js_ast.PropertyAccess] for accessors or method calls to
/// [jsTarget].[jsName], replacing `super` if it is not allowed in scope.
js_ast.PropertyAccess _emitSuperTarget(Member member, {bool setter = false}) {
var jsName = _declareMemberName(member);
var jsName = _emitMemberName(member.name.text, member: member);
// Optimize access to non-virtual fields, if allowed in the current context.
if (_optimizeNonVirtualFieldAccess &&
member is Field &&
!_virtualFields.isVirtual(member)) {
@@ -5922,25 +5903,6 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
return js_ast.PropertyAccess(js_ast.This(), jsMethod.name);
}
/// Generates a special string used for identifying a torn off member [m].
///
/// This tag is used for determining tearoff equality. We attach these tags
/// at tearoff time for static tearoffs and in the method signature for
/// dynamic tearoffs.
String fullyResolvedTargetLabel(Member m) {
return '${m.enclosingLibrary.importUri}:${m.enclosingClass?.name ?? ""}';
}
/// Generates a special string used for identifying class [c]'s applied mixed
/// in members.
///
/// This tag is used for determining tearoff equality. We attach these tags
/// at tearoff time for static tearoffs and in the method signature for
/// dynamic tearoffs.
String fullyResolvedMixinClassLabel(Class c) {
return '${c.enclosingLibrary.importUri}:${c.name}';
}
/// Generates a helper method that is inserted into the class that binds a
/// tearoff of [member] from `super` and returns a call to the helper.
///
@@ -5948,16 +5910,11 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
// TODO(nshahan) Replace with a kernel transform and synthetic method filters
// for devtools.
js_ast.Expression _emitSuperTearoff(Member member) {
var jsName = _declareMemberName(member);
var jsName = _emitMemberName(member.name.text, member: member);
var name = '_#super#tearOff#${member.name.text}';
var jsMethod = _superHelpers.putIfAbsent(name, () {
var superclass = member.enclosingClass?.superclass;
var supertypeReference = superclass == null
? js_ast.LiteralNull()
: _mixinSuperclassCache[member.enclosingClass!] ??
_emitTopLevelNameNoExternalInterop(superclass);
var jsReturnValue = _runtimeCall(
'bind(this, #, #, super[#])', [supertypeReference, jsName, jsName]);
var jsReturnValue =
_runtimeCall('bind(this, #, super[#])', [jsName, jsName]);
var fn = js.fun('function() { return #; }', [jsReturnValue]);
name = js_ast.friendlyNameForDartOperator[name] ?? name;
return js_ast.Method(_emitScopedId(name), fn);
@@ -6348,7 +6305,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
}
/// Emits the target of a [StaticInvocation], [StaticGet], or [StaticSet].
js_ast.PropertyAccess _emitStaticTarget(Member target) {
js_ast.Expression _emitStaticTarget(Member target) {
var c = target.enclosingClass;
if (c != null) {
// A static native element should just forward directly to the JS type's
@@ -6358,12 +6315,9 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
if (isExternal && (target as Procedure).isStatic) {
var nativeName = _extensionTypes.getNativePeers(c);
if (nativeName.isNotEmpty) {
var annotationName = _annotationName(target, isJSName);
var memberName = annotationName == null
? _emitStaticMemberName(target.name.text, target)
: js.string(annotationName);
return js_ast.PropertyAccess(
_runtimeCall('global.#', [nativeName[0]]), memberName);
var memberName = _annotationName(target, isJSName) ??
_emitStaticMemberName(target.name.text, target);
return _runtimeCall('global.#.#', [nativeName[0], memberName]);
}
}
return js_ast.PropertyAccess(_emitStaticClassName(c, isExternal),
@@ -232,20 +232,6 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
Map<Class, js_ast.Identifier> get classIdentifiers =>
_symbolData.classIdentifiers;
/// Maps every mixin application to a unique identifier.
///
/// A mixin application is represented as a (mixin, class) pair, where
/// 'mixin' is being mixed into 'class'. Anonymous mixins are already
/// unique per mixin application and so pass themselves in as both 'mixin'
/// and 'class'.
///
/// This mapping is used when generating super property getters in mixins.
final Map<(Class, Class), js_ast.Identifier> _mixinCache = {};
/// Records a reference to a mixin application's passed in superclass.
/// (see [_emitMixinStatement]).
final Map<Class, js_ast.Identifier> _mixinSuperclassCache = {};
/// Maps each class `Member` node compiled in the module to the name used for
/// the member in JavaScript.
///
@@ -1187,13 +1173,6 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
// a simpler representation within instance members of the class.
_currentTypeEnvironment = ClassTypeEnvironment(c.typeParameters);
// Store identifiers for a mixin application's passed in superclass.
// (see [_emitMixinStatement]).
if (c.isMixinDeclaration && !c.isMixinClass) {
_mixinSuperclassCache.putIfAbsent(
c, () => _emitScopedId(getLocalClassName(c.superclass!)));
}
// Mixins are unrolled in _defineClass.
if (!c.isAnonymousMixin) {
// If this class is annotated with `@JS`, then we only need to emit the
@@ -1484,7 +1463,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
var instanceProperties = properties.where((m) => !m.isStatic).toList();
body.addAll(_emitClassStatement(c, className, heritage, staticProperties));
var superclassId = _mixinSuperclassCache[c]!;
var superclassId = _emitScopedId(getLocalClassName(c.superclass!));
var classId = className is js_ast.Identifier
? className
: _emitScopedId(getLocalClassName(c));
@@ -1611,7 +1590,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
var mixinClass = m.isAnonymousMixin ? m.mixedInClass! : m;
var mixinType =
_hierarchy.getClassAsInstanceOf(c, mixinClass)!.asInterfaceType;
var mixinId = _emitMixinId(m, m.isAnonymousMixin ? m : c);
var mixinName =
'${getLocalClassName(superclass)}_${getLocalClassName(mixinClass)}';
var mixinId = _emitScopedId('$mixinName\$');
// Collect all forwarding stub members from anonymous mixins classes.
// These can contain covariant parameter checks that need to be applied.
var savedClassProperties = _classProperties;
@@ -1649,26 +1630,24 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
_currentTypeEnvironment = savedTypeEnvironment;
_classProperties = savedClassProperties;
// TODO(markzipan): We originally bound mixin classes to a temporary as a
// workaround for a now-resolved Chrome issue. However, a side effect of
// this operation is that mixin IDs are renamed by the local visitor. We
// can remove this hoisting after we give mixins unique names.
// Bind the mixin class to a name to workaround a V8 bug with es6 classes
// and anonymous function names.
// TODO(leafp:) Eliminate this once the bug is fixed:
// https://bugs.chromium.org/p/v8/issues/detail?id=7069
body.add(js.statement('const # = #', [
mixinId,
js_ast.ClassExpression(
_emitScopedId('${mixinId.name}\$'), null, forwardingMethodStubs)
_emitScopedId(mixinName), null, forwardingMethodStubs)
]));
_classExtendsLinks.add(_runtimeStatement(
'classExtends(#, #)', [mixinId, embedderResolvedBaseClass]));
emitMixinConstructors(mixinId, superclass, mixinClass, mixinType);
hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(mixinClass);
var mixinTargetLabel = js.string(fullyResolvedMixinClassLabel(m));
// The SDK is never hot reloaded, so we can avoid the overhead of
// resolving their classes through the embedder.
_mixinApplicationLinks.add(_runtimeStatement('applyMixin(#, #, #)', [
_mixinApplicationLinks.add(_runtimeStatement('applyMixin(#, #)', [
mixinId,
emitClassRef(mixinType, resolvedFromEmbedder: !_isBuildingSdk),
mixinTargetLabel
emitClassRef(mixinType, resolvedFromEmbedder: !_isBuildingSdk)
]));
baseClass = mixinId;
embedderResolvedBaseClass = mixinId;
@@ -1831,7 +1810,6 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
var staticMethods = <js_ast.Property>[];
var instanceMethods = <js_ast.Property>[];
var instanceMethodsDefaultTypeArgs = <js_ast.Property>[];
var methodsImmediateTarget = <js_ast.Property>[];
var staticGetters = <js_ast.Property>[];
var instanceGetters = <js_ast.Property>[];
var staticSetters = <js_ast.Property>[];
@@ -1885,16 +1863,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
var needsSignature = memberOverride == null ||
reifiedType != _memberRuntimeType(memberOverride, c);
var memberName = _declareMemberName(member,
useExtension: _isObjectMethodTearoff(member.name.text));
if (!member.isAccessor) {
var immediateTarget = js.string(fullyResolvedTargetLabel(member));
methodsImmediateTarget
.add(js_ast.Property(memberName, immediateTarget));
}
if (needsSignature) {
js_ast.Expression type;
var memberName = _declareMemberName(member);
if (member.isAccessor) {
// These signatures are used for dynamic access and to inform the
// debugger. The `arrayRti` accessor is only used by the dart:_rti
@@ -1947,7 +1918,6 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
emitSignature('Method', instanceMethods);
emitSignature('MethodsDefaultTypeArg', instanceMethodsDefaultTypeArgs);
emitSignature('MethodsImmediateTarget', methodsImmediateTarget);
// TODO(40273) Skip for all statics when the debugger consumes signature
// information from symbol files.
emitSignature('StaticMethod', staticMethods);
@@ -3169,14 +3139,12 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
///
/// Unlike call sites, we always have an element available, so we can use it
/// directly rather than computing the relevant options for [_emitMemberName].
js_ast.Expression _declareMemberName(Member m, {bool useExtension = false}) {
js_ast.Expression _declareMemberName(Member m, {bool? useExtension}) {
var c = m.enclosingClass;
var name = m.name.text;
var actualUseExtension =
useExtension || (c != null && _extensionTypes.isNativeClass(c));
return _emitMemberName(name,
return _emitMemberName(m.name.text,
isStatic: m is Field ? m.isStatic : (m as Procedure).isStatic,
useExtension: actualUseExtension,
useExtension:
useExtension ?? c != null && _extensionTypes.isNativeClass(c),
member: m);
}
@@ -5469,7 +5437,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
}
var jsMemberName = _emitMemberName(memberName, member: member);
if (_reifyTearoff(member)) {
return _runtimeCall('tearoff(#, null, #)', [jsReceiver, jsMemberName]);
return _runtimeCall('tearoff(#, #)', [jsReceiver, jsMemberName]);
}
var jsPropertyAccess = js_ast.PropertyAccess(jsReceiver, jsMemberName);
return isJsMember(member)
@@ -5610,28 +5578,11 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
return _emitSuperPropertyGet(node.interfaceTarget);
}
/// Emits a reference to a distinct mixin application, represented by
/// a [mixedInClass] being mixed into [baseClass].
///
/// Anonymous mixins should pass themselves as [baseClass] since they are
/// already uniquely generated per distinct mixin application
js_ast.Identifier _emitMixinId(Class mixedInClass, Class baseClass) {
return _mixinCache.putIfAbsent(
(mixedInClass, baseClass), () => _emitScopedId(mixedInClass.name));
}
js_ast.Expression _emitSuperPropertyGet(Member target) {
if (_reifyTearoff(target)) {
if (_superAllowed) {
var jsTarget = _emitSuperTarget(target);
var jsName = _declareMemberName(target);
var enclosingClass = target.enclosingClass!;
var supertypeReference = _mixinSuperclassCache[_currentClass!] ??
(enclosingClass.isAnonymousMixin
? _emitMixinId(enclosingClass, enclosingClass)
: _emitTopLevelNameNoExternalInterop(enclosingClass));
return _runtimeCall(
'bind(this, #, #, #)', [supertypeReference, jsName, jsTarget]);
return _runtimeCall('bind(this, #, #)', [jsTarget.selector, jsTarget]);
} else {
return _emitSuperTearoff(target);
}
@@ -5680,9 +5631,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
var property = propertyAccessor.selector;
var result = js.call('#.#', [context, property]);
if (_reifyTearoff(target)) {
var targetLabel = js.string(fullyResolvedTargetLabel(target));
return _runtimeCall(
'staticTearoff(#, #, #)', [context, targetLabel, property]);
return _runtimeCall('staticTearoff(#, #)', [context, property]);
}
return result;
}
@@ -6316,7 +6265,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
/// Emits the [js_ast.PropertyAccess] for accessors or method calls to
/// [jsTarget].[jsName], replacing `super` if it is not allowed in scope.
js_ast.PropertyAccess _emitSuperTarget(Member member, {bool setter = false}) {
var jsName = _declareMemberName(member);
var jsName = _emitMemberName(member.name.text, member: member);
// Optimize access to non-virtual fields, if allowed in the current context.
if (_optimizeNonVirtualFieldAccess &&
member is Field &&
!_virtualFields.isVirtual(member)) {
@@ -6372,25 +6322,6 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
return js_ast.PropertyAccess(js_ast.This(), jsMethod.name);
}
/// Generates a special string used for identifying a torn off member [m].
///
/// This tag is used for determining tearoff equality. We attach these tags
/// at tearoff time for static tearoffs and in the method signature for
/// dynamic tearoffs.
String fullyResolvedTargetLabel(Member m) {
return '${m.enclosingLibrary.importUri}:${m.enclosingClass?.name ?? ""}';
}
/// Generates a special string used for identifying class [c]'s applied mixed
/// in members.
///
/// This tag is used for determining tearoff equality. We attach these tags
/// at tearoff time for static tearoffs and in the method signature for
/// dynamic tearoffs.
String fullyResolvedMixinClassLabel(Class c) {
return '${c.enclosingLibrary.importUri}:${c.name}';
}
/// Generates a helper method that is inserted into the class that binds a
/// tearoff of [member] from `super` and returns a call to the helper.
///
@@ -6398,16 +6329,11 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
// TODO(nshahan) Replace with a kernel transform and synthetic method filters
// for devtools.
js_ast.Expression _emitSuperTearoff(Member member) {
var jsName = _declareMemberName(member);
var jsName = _emitMemberName(member.name.text, member: member);
var name = '_#super#tearOff#${member.name.text}';
var jsMethod = _superHelpers.putIfAbsent(name, () {
var superclass = member.enclosingClass?.superclass;
var supertypeReference = superclass == null
? js_ast.LiteralNull()
: _mixinSuperclassCache[member.enclosingClass!] ??
_emitTopLevelNameNoExternalInterop(superclass);
var jsReturnValue = _runtimeCall(
'bind(this, #, #, super[#])', [supertypeReference, jsName, jsName]);
var jsReturnValue =
_runtimeCall('bind(this, #, super[#])', [jsName, jsName]);
var fn = js.fun('function() { return #; }', [jsReturnValue]);
name = js_ast.friendlyNameForDartOperator[name] ?? name;
return js_ast.Method(_emitScopedId(name), fn);
@@ -133,8 +133,7 @@ void runExpressionCompilationTests(ExpressionCompilerWorkerTestDriver driver) {
'errors': isEmpty,
'warnings': isEmpty,
'infos': isEmpty,
'compiledProcedure':
stringContainsInOrder(['developer', 'postEvent']),
'compiledProcedure': contains('developer.postEvent'),
})
]));
});
@@ -168,8 +167,7 @@ void runExpressionCompilationTests(ExpressionCompilerWorkerTestDriver driver) {
'errors': isEmpty,
'warnings': isEmpty,
'infos': isEmpty,
'compiledProcedure':
stringContainsInOrder(['developer', 'postEvent']),
'compiledProcedure': contains('developer.postEvent'),
})
]));
});
@@ -752,7 +752,8 @@ void runSharedTests(
test('getFunctionName (static method)', () async {
var getFunctionName =
setup.emitLibraryBundle ? 'getFunctionName' : 'getFunctionMetadata';
var expectedName = 'BaseClass.staticMethod';
var expectedName =
setup.emitLibraryBundle ? 'BaseClass.staticMethod' : 'staticMethod';
await driver.checkRuntimeInFrame(
breakpointId: 'BP',
@@ -13,18 +13,13 @@
part of dart._runtime;
/// Returns a new type that mixes members from base and the mixin.
void applyMixin(
@notNull Object to,
@notNull Object from,
@notNull String mixinMethodTargetLabel,
) {
void applyMixin(@notNull Object to, @notNull Object from) {
JS('', '#[#] = #', to, _mixin, from);
var toProto = JS<Object>('!', '#.prototype', to);
var fromProto = JS<Object>('!', '#.prototype', from);
_copyMembers(toProto, fromProto);
_mixinSignature(to, from, _methodSig);
_mixinSignature(to, from, _methodsDefaultTypeArgSig);
_copyMixinMethodsImmediateTargetSignature(to, from, mixinMethodTargetLabel);
_mixinSignature(to, from, _fieldSig);
_mixinSignature(to, from, _getterSig);
_mixinSignature(to, from, _setterSig);
@@ -119,31 +114,6 @@ void _mixinSignature(@notNull Object to, @notNull Object from, kind) {
});
}
/// Mixins must update their methods' enclosing target labels in [from] to that
/// of the [to] class when applied.
void _copyMixinMethodsImmediateTargetSignature(
@notNull Object to,
@notNull Object from,
@notNull String mixinMethodTargetLabel,
) {
// We set the descriptor's label in the [from] object to its new label during
// the mixin copy.
var labelTransform = (Object desc) {
JS('', '#.value = #', desc, mixinMethodTargetLabel);
return desc;
};
JS('', '#[#] = #', to, _methodsImmediateTargetSig, () {
var baseMembers = getMethodsImmediateTargets(jsObjectGetPrototypeOf(to));
// Coerce undefined to null.
baseMembers = baseMembers == null ? null : baseMembers;
var fromMembers = getMethodsImmediateTargets(from);
if (fromMembers == null) return baseMembers;
var toSignature = JS('', 'Object.create(#)', baseMembers);
copyProperties(toSignature, fromMembers, transform: labelTransform);
return toSignature;
});
}
final _mixin = JS('', 'Symbol("mixin")');
getMixin(clazz) => JS(
@@ -166,7 +136,6 @@ Object instantiateClass(Object genericClass, List<Object> typeArgs) {
final _constructorSig = JS('', 'Symbol("sigCtor")');
final _methodSig = JS('', 'Symbol("sigMethod")');
final _methodsDefaultTypeArgSig = JS('', 'Symbol("sigMethodDefaultTypeArgs")');
final _methodsImmediateTargetSig = JS('', 'Symbol("sigMethodImmediateTarget")');
final _fieldSig = JS('', 'Symbol("sigField")');
final _getterSig = JS('', 'Symbol("sigGetter")');
final _setterSig = JS('', 'Symbol("sigSetter")');
@@ -181,8 +150,6 @@ getConstructors(value) => _getMembers(value, _constructorSig);
getMethods(value) => _getMembers(value, _methodSig);
getMethodsDefaultTypeArgs(value) =>
_getMembers(value, _methodsDefaultTypeArgSig);
getMethodsImmediateTargets(value) =>
_getMembers(value, _methodsImmediateTargetSig);
getFields(value) => _getMembers(value, _fieldSig);
getGetters(value) => _getMembers(value, _getterSig);
getSetters(value) => _getMembers(value, _setterSig);
@@ -272,14 +239,6 @@ getMethodType(obj, name) {
return rtiFromSignature(obj, JS<Object?>('', '#[#]', m, name));
}
/// Returns the immediate target string for the method [name].
String? getMethodImmediateTarget(obj, holder, name) {
var typeSigHolder = holder ?? getTypeSignatureContainer(obj);
var methodsImmediateTargets = getMethodsImmediateTargets(typeSigHolder);
if (methodsImmediateTargets == null) return null;
return JS<String>('', '#[#]', methodsImmediateTargets, name);
}
/// Returns the default type argument values for the instance method [name].
JSArray<Object> getMethodDefaultTypeArgs(obj, name) {
var typeSigHolder = getTypeSignatureContainer(obj);
@@ -347,8 +306,6 @@ classGetConstructorType(cls, name) {
void setMethodSignature(f, sigF) => JS('', '#[#] = #', f, _methodSig, sigF);
void setMethodsDefaultTypeArgSignature(f, sigF) =>
JS('', '#[#] = #', f, _methodsDefaultTypeArgSig, sigF);
void setMethodsImmediateTargetSignature(f, sigF) =>
JS('', '#[#] = #', f, _methodsImmediateTargetSig, sigF);
void setFieldSignature(f, sigF) => JS('', '#[#] = #', f, _fieldSig, sigF);
void setGetterSignature(f, sigF) => JS('', '#[#] = #', f, _getterSig, sigF);
void setSetterSignature(f, sigF) => JS('', '#[#] = #', f, _setterSig, sigF);
@@ -475,14 +432,6 @@ void _applyExtension(jsType, dartExtType) {
dartExtType,
_methodsDefaultTypeArgSig,
);
JS(
'',
'#[#] = #[#]',
jsType,
_methodsImmediateTargetSig,
dartExtType,
_methodsImmediateTargetSig,
);
JS('', '#[#] = #[#]', jsType, _fieldSig, dartExtType, _fieldSig);
JS('', '#[#] = #[#]', jsType, _getterSig, dartExtType, _getterSig);
JS('', '#[#] = #[#]', jsType, _setterSig, dartExtType, _setterSig);
@@ -571,8 +520,7 @@ void defineExtensionAccessors(type, Iterable memberNames) {
var member;
Object? p = proto;
for (; p != null; p = jsObjectGetPrototypeOf(p)) {
var property = _canonicalMember(p, name);
member = getOwnPropertyDescriptor(p, property);
member = getOwnPropertyDescriptor(p, name);
if (member != null) break;
}
defineProperty(proto, JS('', 'dartx[#]', name), member);
@@ -584,13 +584,7 @@ String? _getDartSymbolName(@notNull dynamic symbol) =>
: _getDartName(_symbolDescription(symbol));
String? _getDartName(String? name) {
if (name == null) return null;
if (name.startsWith('dartx.')) {
// 'toString' and 'noSuchMethod' are accessed through their extension
// property but should be visible.
if (name == 'dartx.toString' || name == 'dartx.noSuchMethod') {
return name.substring(6, name.length);
}
if (name == null || name.startsWith('dartx.')) {
return null;
}
// Show late fields: '_#C#lateField'.
@@ -51,12 +51,7 @@ class InvocationImpl extends Invocation {
/// Encodes [property] as a valid JS member name.
String stringNameForProperty(property) {
if (JS<bool>('', 'typeof # === "symbol"', property)) {
var name = _toSymbolName(property);
// Remove extension method prefixes if necessary.
if (JS<bool>('', '#.startsWith("dartx.")', name)) {
return JS<String>('', '#.substring(6, #.length)', name, name);
}
return name;
return _toSymbolName(property);
}
if (JS<bool>('', 'typeof # === "string"', property)) {
return '$property';
@@ -64,49 +59,30 @@ String stringNameForProperty(property) {
throw Exception('Unable to construct a valid JS string name for $property.');
}
/// Used for canonicalizing tearoffs via a two-way lookup of enclosing method
/// target label and member name.
///
/// TODO(markzipan): We can't use a JS WeakMap to key by method context because
/// we sometimes wrap library objects in lazily-loaded proxy objects. We can
/// avoid memory leaks if we handle proxy libraries natively.
final tearoffCache = JS<Object>('!', 'new Map()');
/// Used for canonicalizing tearoffs via a two-way lookup of enclosing object
/// and member name.
final tearoffCache = JS<Object>('!', 'new WeakMap()');
/// Constructs a static tearoff, on `context[property]`.
///
/// [immediateMethodTargetLabel] uniquely identifies the class from which this
/// method is torn off. Static tearoffs provide this at tearoff time.
///
/// Static tearoffs are canonicalized at runtime via the `tearoffCache`. We
/// avoid canonicalizing based on [context] to avoid comparing proxy-wrapped
/// top level library objects.
staticTearoff(context, String immediateMethodTargetLabel, property) {
/// Static tearoffs are canonicalized at runtime via `tearoffCache`.
staticTearoff(context, property) {
if (context == null) context = jsNull;
var propertyMap = _lookupNonTerminal(
tearoffCache,
immediateMethodTargetLabel,
);
var propertyMap = _lookupNonTerminal(tearoffCache, context);
var canonicalizedTearoff = JS<Object?>('', '#.get(#)', propertyMap, property);
if (canonicalizedTearoff != null) return canonicalizedTearoff;
var tear = tearoff(context, immediateMethodTargetLabel, property);
var tear = tearoff(context, property);
JS('', '#.set(#, #)', propertyMap, property, tear);
JS('', '#._isStaticTearoff = true', tear);
return tear;
}
/// Constructs a new tearoff, on `context[property]`. Tearoffs are represented
/// as a closure that resolves its underlying member late.
///
/// [immediateMethodTargetLabel] uniquely identifies the class from which this
/// method is torn off. Static tearoffs provide this at tearoff time. This is
/// directly provided for static tearoffs. If null (such as in dynamic/instance
/// tearoffs), we resolve this via this tearoff's method signature.
///
/// Note: We do not canonicalize instance tearoffs to be consistent with
/// Dart2JS, but we should update this if the spec changes. See #3612.
tearoff(context, String? immediateMethodTargetLabel, property) {
tearoff(context, property) {
if (context == null) context = jsNull;
property = _canonicalMember(context, property);
var tear = JS('', '(...args) => #[#](...args)', context, property);
var rtiName = JS_GET_NAME(JsGetName.SIGNATURE_NAME);
// Type-resolving members on tearoffs must be resolved late. Static tearoffs
@@ -150,13 +126,6 @@ tearoff(context, String? immediateMethodTargetLabel, property) {
);
JS('', '#._boundObject = #', tear, context);
JS('', '#._boundName = #', tear, stringNameForProperty(property));
JS(
'',
'#._boundMethodTarget = #',
tear,
immediateMethodTargetLabel ??
getMethodImmediateTarget(context, null, property),
);
return tear;
}
@@ -164,39 +133,26 @@ tearoff(context, String? immediateMethodTargetLabel, property) {
/// Sets the runtime type of the torn off method appropriately,
/// and also binds the object.
///
/// [immediateMethodTarget] is the class at the exact point in the [obj]'s
/// hierarchy where [name] is torn off. This field is only used when the
/// immediate target cannot be resolved on [obj] (such as in super tearoffs).
///
/// If the optional `f` argument is passed in, it will be used as the method.
/// This supports cases like `super.foo` where we need to tear off the method
/// from the superclass, not from the `obj` directly.
// TODO(60297): This function currently binds super tearoffs too early. This
// should be updated to receive obj's supertype at runtime like we do for
// mixin classes.
bind(obj, immediateMethodTarget, name, method) {
// TODO(leafp): Consider caching the tearoff on the object?
bind(obj, name, method) {
if (obj == null) obj = jsNull;
var property = _canonicalMember(obj, name);
if (method == null) method = JS('', '#[#]', obj, property);
if (method == null) method = JS('', '#[#]', obj, name);
var f = JS('', '#.bind(#)', method, obj);
// TODO(jmesserly): canonicalize tearoffs.
JS('', '#._boundObject = #', f, obj);
JS('', '#._boundName = #', f, stringNameForProperty(name));
JS('', '#._boundMethod = #', f, method);
JS(
'',
'#._boundMethodTarget = #',
f,
getMethodImmediateTarget(obj, immediateMethodTarget, property),
);
var methodType = getMethodType(obj, property);
var methodType = getMethodType(obj, name);
// Native JavaScript methods do not have Dart signatures attached that need
// to be copied.
if (methodType != null) {
if (rti.isGenericFunctionType(methodType)) {
// Attach the default type argument values to the new function in case
// they are needed for a dynamic call.
var defaultTypeArgs = getMethodDefaultTypeArgs(obj, property);
var defaultTypeArgs = getMethodDefaultTypeArgs(obj, name);
JS('', '#._defaultTypeArgs = #', f, defaultTypeArgs);
}
JS('', '#[#] = #', f, JS_GET_NAME(JsGetName.SIGNATURE_NAME), methodType);
@@ -222,13 +178,6 @@ bindCall(obj, name) {
// TODO(jmesserly): canonicalize tearoffs.
JS('', '#._boundObject = #', f, obj);
JS('', '#._boundMethod = #', f, method);
JS('', '#._boundName = #', f, stringNameForProperty(name));
JS(
'',
'#._boundMethodTarget = #',
f,
getMethodImmediateTarget(obj, obj, name),
);
JS('', '#[#] = #', f, JS_GET_NAME(JsGetName.SIGNATURE_NAME), ftype);
if (rti.isGenericFunctionType(ftype)) {
// Attach the default type argument values to the new function in case
@@ -284,7 +233,7 @@ dload(obj, field) {
if (hasField(typeSigHolder, f) || hasGetter(typeSigHolder, f))
return JS('', '#[#]', obj, f);
if (hasMethod(typeSigHolder, f)) return tearoff(obj, null, f);
if (hasMethod(typeSigHolder, f)) return tearoff(obj, f);
// Handle record types by trying to access [f] via convenience getters.
if (_jsInstanceOf(obj, RecordImpl) && f is String) {
@@ -1030,11 +979,11 @@ String _toString(obj) {
/// interop value).
@notNull
String Function() toStringTearoff(obj) {
if (obj == null) obj = jsNull;
if (JS<bool>('!', '#[#] !== void 0', obj, extensionSymbol('toString'))) {
if (obj == null ||
JS<bool>('!', '#[#] !== void 0', obj, extensionSymbol('toString'))) {
// The bind helper can handle finding the toString method for null or Dart
// Objects.
return bind(obj, null, extensionSymbol('toString'), null);
return tearoff(obj, extensionSymbol('toString'));
}
// Otherwise bind the native JavaScript toString method.
// This differs from dart2js to provide a more useful toString at development
@@ -1042,7 +991,7 @@ String Function() toStringTearoff(obj) {
// If obj does not have a native toString method this will throw but that
// matches the behavior of dart2js and it would be misleading to make this
// work at development time but allow it to fail in production.
return bind(obj, null, 'toString', null);
return tearoff(obj, 'toString');
}
/// Converts to a non-null [String], equivalent to
@@ -1104,18 +1053,17 @@ noSuchMethod(obj, Invocation invocation) {
/// JavaScript interop value).
@notNull
dynamic Function(Invocation) noSuchMethodTearoff(obj) {
if (obj == null) obj = jsNull;
if (JS<bool>('!', '#[#] !== void 0', obj, extensionSymbol('noSuchMethod'))) {
if (obj == null ||
JS<bool>('!', '#[#] !== void 0', obj, extensionSymbol('noSuchMethod'))) {
// The bind helper can handle finding the toString method for null or Dart
// Objects.
return bind(obj, null, extensionSymbol('noSuchMethod'), null);
return tearoff(obj, extensionSymbol('noSuchMethod'));
}
// Otherwise, manually pass the Dart Core Object noSuchMethod to the bind
// helper.
return bind(
obj,
null,
'noSuchMethod',
extensionSymbol('noSuchMethod'),
JS(
'!',
'#.prototype[#]',
@@ -1169,16 +1117,8 @@ _canonicalMember(obj, name) {
// Private names are symbols and are already canonical.
if (JS('!', 'typeof # === "symbol"', name)) return name;
// 'toString' and 'noSuchMethod' use their extension symbol when available.
if (obj != null &&
JS('!', '# === "toString" || # === "noSuchMethod"', name, name)) {
if (JS<bool>('!', '#[#] !== void 0', obj, extensionSymbol(name))) {
return extensionSymbol(name);
}
}
if (obj != null && JS<bool>('!', '#[#] != null', obj, _extensionType)) {
return extensionSymbol(name);
return JS('', 'dartx.#', name);
}
// Check for certain names that we can't use in JS
@@ -267,7 +267,6 @@ void hotRestart() {
JS('', '#.clear()', constantLists);
JS('', '#.clear()', constantSets);
JS('', '#.clear()', constantMaps);
JS('', '#.clear()', tearoffCache);
JS('', '#.forEach((value) => value.fill(void 0))', moduleConstCaches);
@@ -78,19 +78,18 @@ copyTheseProperties(
from,
namesAndSymbols, {
bool Function(Object)? copyWhen,
Object Function(Object)? transform,
}) {
for (int i = 0, n = JS('!', '#.length', namesAndSymbols); i < n; ++i) {
var nameOrSymbol = JS<Object>('!', '#[#]', namesAndSymbols, i);
if ('constructor' == nameOrSymbol) continue;
if ('prototype' == nameOrSymbol) continue;
if (copyWhen != null && !copyWhen(nameOrSymbol)) continue;
copyProperty(to, from, nameOrSymbol, transform: transform);
copyProperty(to, from, nameOrSymbol);
}
return to;
}
copyProperty(to, from, name, {Object Function(Object)? transform}) {
copyProperty(to, from, name) {
var desc = getOwnPropertyDescriptor(from, name);
if (JS('!', '# == Symbol.iterator', name)) {
// On native types, Symbol.iterator may already be present.
@@ -105,9 +104,6 @@ copyProperty(to, from, name, {Object Function(Object)? transform}) {
return;
}
}
if (transform != null) {
desc = JS<Object>('', '#(#)', transform, desc);
}
defineProperty(to, name, desc);
}
@@ -118,18 +114,11 @@ exportProperty(to, from, name) => copyProperty(to, from, name);
/// This operation is commonly called `mixin` in JS.
///
/// [copyWhen] allows you to specify when a JS property will be copied.
/// [transform] allows you to specify a value based on a property descriptor.
copyProperties(
to,
from, {
bool Function(Object)? copyWhen,
Object Function(Object)? transform,
}) {
copyProperties(to, from, {bool Function(Object)? copyWhen}) {
return copyTheseProperties(
to,
from,
getOwnNamesAndSymbols(from),
copyWhen: copyWhen,
transform: transform,
);
}
@@ -214,33 +214,15 @@ class JSFunction extends Interceptor {
return JS<bool>('!', '# === #', originalFn, otherFn);
}
}
// This is a static tearoff. Static tearoffs always provide a bound
// enclosing method target string that should be compared instead of the
// original object (in case the original object is a proxied library).
if (JS<bool>('!', '#._isStaticTearoff', this)) {
return JS<bool>(
'!',
'#._boundMethodTarget === #._boundMethodTarget'
'&& #._boundName === #._boundName ',
originalFn,
otherFn,
originalFn,
otherFn,
);
}
// This is an instance tearoff, test if the bound instances and methods
// are equal.
return JS<bool>(
'!',
'# === #._boundObject '
'&& #._boundName === #._boundName '
'&& #._boundMethodTarget === #._boundMethodTarget',
'# === #._boundObject && #._boundMethod === #._boundMethod',
boundObj,
otherFn,
originalFn,
otherFn,
originalFn,
otherFn,
);
}
@@ -248,14 +230,9 @@ class JSFunction extends Interceptor {
var boundObj = JS<Object?>('', '#._boundObject', this);
if (boundObj == null) return identityHashCode(this);
var boundName = JS<Object>('!', '#._boundName', this);
var boundMethodTarget = JS<Object>('!', '#._boundMethodTarget', this);
int hash = (17 * 31 + identityHashCode(boundName)) & 0x1fffffff;
hash = (hash * 31 + identityHashCode(boundMethodTarget)) & 0x1fffffff;
if (!JS<bool>('!', '#._isStaticTearoff', this)) {
hash = (hash * 31 + boundObj.hashCode) & 0x1fffffff;
}
return hash;
var boundMethod = JS<Object>('!', '#._boundMethod', this);
int hash = (17 * 31 + boundObj.hashCode) & 0x1fffffff;
return (hash * 31 + identityHashCode(boundMethod)) & 0x1fffffff;
}
Type get runtimeType =>
@@ -5912,23 +5912,6 @@ Value:
]
]
],
[
"li",
{
"style": "padding-left: 13px;"
},
[
"span",
{},
[
"span",
{
"style": ""
},
"<DART_SDK>"
]
]
],
[
"li",
{
+1 -3
View File
@@ -15,9 +15,7 @@ void main() {
// The print method is only used here because we know it is a member of the
// dart:core library.
var printMethod = JS('', '#.print', core);
// Tearoffs are wrapped, so we must extract their bound method.
var printTearoff = print;
Expect.equals(JS('', '#._boundMethod', printTearoff), printMethod);
Expect.equals(print, printMethod);
// Test getLibraries()
// Note that we call `getLibraries` after an access to `Expect` as DDC may
@@ -1,26 +0,0 @@
// Copyright (c) 2025, 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
// Tests reload succeeds when super getter are updated.
class Bar {
method() {
return 42;
}
}
class Foo extends Bar {
get tearoff => super.method;
}
Future<void> main() async {
var tearoff = Foo().tearoff;
Expect.equals(42, tearoff());
await hotReload();
Expect.equals(100, tearoff());
}
@@ -1,38 +0,0 @@
// Copyright (c) 2025, 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 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
// Tests reload succeeds when super getter are updated.
class Bar {
method() {
return 100;
}
}
class Foo extends Bar {
get tearoff => super.method;
}
Future<void> main() async {
var tearoff = Foo().tearoff;
Expect.equals(42, tearoff());
await hotReload();
Expect.equals(100, tearoff());
}
/** DIFF **/
/*
class Bar {
method() {
- return 42;
+ return 100;
}
}
*/