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:
committed by
Commit Queue
parent
3d30b2f17e
commit
58ba6006a6
@@ -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",
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
Reference in New Issue
Block a user