[ddc] Adding support for hot reloaded super getters.
This removes the 'bind' and 'bindCall' methods in our runtime in favor of extending 'tearoff' to support their behavior. Also introduces 'superTearoff' for invoking torn off super members late. Change-Id: I52ab797558a225c57cc2c6197c7b1bfa7f05a3c6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416763 Commit-Queue: Mark Zhou <markzipan@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com> Reviewed-by: Bob Nystrom <rnystrom@google.com>
This commit is contained in:
@@ -5180,7 +5180,6 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
js_ast.Expression _emitSuperPropertyGet(Member target) {
|
||||
if (_reifyTearoff(target)) {
|
||||
if (_superAllowed) {
|
||||
var jsTarget = _emitSuperTarget(target);
|
||||
var jsName = _declareMemberName(target);
|
||||
var enclosingClass = target.enclosingClass!;
|
||||
js_ast.Expression? supertypeReference =
|
||||
@@ -5197,9 +5196,9 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
}
|
||||
}
|
||||
return _runtimeCall(
|
||||
'bind(this, #, #, #)', [supertypeReference, jsName, jsTarget]);
|
||||
'superTearoff(this, #, #)', [supertypeReference, jsName]);
|
||||
} else {
|
||||
return _emitSuperTearoff(target);
|
||||
return _emitSuperTearoffFromDisallowedContext(target);
|
||||
}
|
||||
}
|
||||
return _emitSuperTarget(target);
|
||||
@@ -5896,7 +5895,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
//
|
||||
// NOTE: This is intended to help in the cases of calling a `super` getter,
|
||||
// setter, or method. For the case of tearing off a `super` method in
|
||||
// contexts where `super` isn't allowed, see [_emitSuperTearoff].
|
||||
// contexts where `super` isn't allowed, see
|
||||
// [_emitSuperTearoffFromDisallowedContext].
|
||||
var name = member.name.text;
|
||||
var getter = (member is Field && !setter) ||
|
||||
(member is Procedure && member.isGetter);
|
||||
@@ -5964,17 +5964,15 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
/// This method assumes `super` is not allowed in the current context.
|
||||
// TODO(nshahan) Replace with a kernel transform and synthetic method filters
|
||||
// for devtools.
|
||||
js_ast.Expression _emitSuperTearoff(Member member) {
|
||||
js_ast.Expression _emitSuperTearoffFromDisallowedContext(Member member) {
|
||||
var jsName = _declareMemberName(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 superclass = member.enclosingClass!;
|
||||
var supertypeReference = _mixinSuperclassCache[superclass] ??
|
||||
_emitTopLevelNameNoExternalInterop(superclass);
|
||||
var jsReturnValue = _runtimeCall(
|
||||
'bind(this, #, #, super[#])', [supertypeReference, jsName, jsName]);
|
||||
'superTearoff(this, #, #)', [supertypeReference, jsName]);
|
||||
var fn = js.fun('function() { return #; }', [jsReturnValue]);
|
||||
name = js_ast.friendlyNameForDartOperator[name] ?? name;
|
||||
return js_ast.Method(_emitScopedId(name), fn);
|
||||
|
||||
@@ -5630,7 +5630,6 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
js_ast.Expression _emitSuperPropertyGet(Member target) {
|
||||
if (_reifyTearoff(target)) {
|
||||
if (_superAllowed) {
|
||||
var jsTarget = _emitSuperTarget(target);
|
||||
var jsName = _declareMemberName(target);
|
||||
var enclosingClass = target.enclosingClass!;
|
||||
js_ast.Expression? supertypeReference =
|
||||
@@ -5647,9 +5646,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
}
|
||||
}
|
||||
return _runtimeCall(
|
||||
'bind(this, #, #, #)', [supertypeReference, jsName, jsTarget]);
|
||||
'superTearoff(this, #, #)', [supertypeReference, jsName]);
|
||||
} else {
|
||||
return _emitSuperTearoff(target);
|
||||
return _emitSuperTearoffFromDisallowedContext(target);
|
||||
}
|
||||
}
|
||||
return _emitSuperTarget(target);
|
||||
@@ -6345,7 +6344,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
//
|
||||
// NOTE: This is intended to help in the cases of calling a `super` getter,
|
||||
// setter, or method. For the case of tearing off a `super` method in
|
||||
// contexts where `super` isn't allowed, see [_emitSuperTearoff].
|
||||
// contexts where `super` isn't allowed, see
|
||||
// [_emitSuperTearoffFromDisallowedContext].
|
||||
var name = member.name.text;
|
||||
var getter = (member is Field && !setter) ||
|
||||
(member is Procedure && member.isGetter);
|
||||
@@ -6413,17 +6413,17 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
/// This method assumes `super` is not allowed in the current context.
|
||||
// TODO(nshahan) Replace with a kernel transform and synthetic method filters
|
||||
// for devtools.
|
||||
js_ast.Expression _emitSuperTearoff(Member member) {
|
||||
js_ast.Expression _emitSuperTearoffFromDisallowedContext(Member member) {
|
||||
var jsName = _declareMemberName(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 superclass = member.enclosingClass!;
|
||||
var supertypeReference = _mixinSuperclassCache[superclass] ??
|
||||
_emitTopLevelNameNoExternalInterop(superclass);
|
||||
var jsReturnValue = _runtimeCall('superTearoff(this, #, #)', [
|
||||
supertypeReference,
|
||||
jsName,
|
||||
]);
|
||||
var fn = js.fun('function() { return #; }', [jsReturnValue]);
|
||||
name = js_ast.friendlyNameForDartOperator[name] ?? name;
|
||||
return js_ast.Method(_emitScopedId(name), fn);
|
||||
|
||||
@@ -97,21 +97,25 @@ staticTearoff(context, String immediateMethodTargetLabel, property) {
|
||||
/// 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.
|
||||
/// [immediateMethodTargetOrLabel] is either 'null', a method label string, or
|
||||
/// an object that resolves to a method label (via [getMethodImmediateTarget]).
|
||||
/// A method label uniquely identifies the class from which this method is
|
||||
/// torn off. Static tearoffs pass in a label when the tearoff is created. If
|
||||
/// null (such as in dynamic/instance tearoffs), we resolve the label 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) {
|
||||
@notNull
|
||||
Object tearoff(
|
||||
Object? context,
|
||||
Object? immediateMethodTargetOrLabel,
|
||||
@notNull Object property,
|
||||
) {
|
||||
if (context == null) context = jsNull;
|
||||
property = _canonicalMember(context, property);
|
||||
var tear = JS('', '(...args) => #[#](...args)', context, property);
|
||||
var tear = JS<Object>('!', '(...args) => #[#](...args)', context, property);
|
||||
var rtiName = JS_GET_NAME(JsGetName.SIGNATURE_NAME);
|
||||
// Type-resolving members on tearoffs must be resolved late. Static tearoffs
|
||||
// are tagged with their RTIs ahead of time. Runtime/instance tearoffs must
|
||||
// access them through `getMethodType` and `getMethodDefaultTypeArgs`.
|
||||
defineAccessor(
|
||||
tear,
|
||||
rtiName,
|
||||
@@ -122,6 +126,88 @@ tearoff(context, String? immediateMethodTargetLabel, property) {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
);
|
||||
defineAccessor(
|
||||
tear,
|
||||
'_boundMethodTarget',
|
||||
get: () {
|
||||
if (JS<bool>('', '# == null', immediateMethodTargetOrLabel)) {
|
||||
return getMethodImmediateTarget(context, null, property);
|
||||
}
|
||||
if (JS<bool>('', 'typeof # == "string"', immediateMethodTargetOrLabel)) {
|
||||
return JS<String>('!', '#', immediateMethodTargetOrLabel);
|
||||
}
|
||||
return getMethodImmediateTarget(
|
||||
context,
|
||||
immediateMethodTargetOrLabel,
|
||||
property,
|
||||
);
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
);
|
||||
JS('', '#._boundObject = #', tear, context);
|
||||
return _finishTearoff(tear, context, property);
|
||||
}
|
||||
|
||||
/// Constructs a tearoff on `super.property` from [context].
|
||||
///
|
||||
/// [context] is the object whose super member is being torn off.
|
||||
/// [superclass] is the class definition at the point in [context]'s hierarchy
|
||||
/// where [property] should be torn off.
|
||||
/// [property] is the property (string name or symbol) used to access the
|
||||
/// member being torn off.
|
||||
@notNull
|
||||
Object superTearoff(
|
||||
@notNull Object context,
|
||||
@notNull Object superclass,
|
||||
@notNull Object property,
|
||||
) {
|
||||
var superContext = JS<Object>('!', '#.prototype', superclass);
|
||||
property = _canonicalMember(superContext, property);
|
||||
var tear = JS<Object>(
|
||||
'!',
|
||||
'(...args) => #[#].bind(#)(...args)',
|
||||
superContext,
|
||||
property,
|
||||
context,
|
||||
);
|
||||
var rtiName = JS_GET_NAME(JsGetName.SIGNATURE_NAME);
|
||||
defineAccessor(
|
||||
tear,
|
||||
rtiName,
|
||||
get: () {
|
||||
var existingRti = JS<Object?>('', '#[#][#]', context, property, rtiName);
|
||||
return existingRti ?? getMethodType(context, property);
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
);
|
||||
defineAccessor(
|
||||
tear,
|
||||
'_boundMethodTarget',
|
||||
get: () {
|
||||
return getMethodImmediateTarget(superContext, superclass, property);
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
);
|
||||
JS('', '#._boundObject = #', tear, context);
|
||||
return _finishTearoff(tear, superContext, property);
|
||||
}
|
||||
|
||||
/// Appends hidden members to a tearoff required for correctness.
|
||||
///
|
||||
/// Does not append '_boundMethodTarget' and '_boundObject', as these have
|
||||
/// special handling logic.
|
||||
@notNull
|
||||
Object _finishTearoff(
|
||||
@notNull Object tear,
|
||||
Object? context,
|
||||
@notNull Object property,
|
||||
) {
|
||||
// Type-resolving members on tearoffs must be resolved late. Static tearoffs
|
||||
// are tagged with their RTIs ahead of time. Runtime/instance tearoffs must
|
||||
// access them through `getMethodType` and `getMethodDefaultTypeArgs`.
|
||||
defineAccessor(
|
||||
tear,
|
||||
'_defaultTypeArgs',
|
||||
@@ -148,97 +234,10 @@ tearoff(context, String? immediateMethodTargetLabel, property) {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
);
|
||||
JS('', '#._boundObject = #', tear, context);
|
||||
JS('', '#._boundName = #', tear, stringNameForProperty(property));
|
||||
JS(
|
||||
'',
|
||||
'#._boundMethodTarget = #',
|
||||
tear,
|
||||
immediateMethodTargetLabel ??
|
||||
getMethodImmediateTarget(context, null, property),
|
||||
);
|
||||
return tear;
|
||||
}
|
||||
|
||||
/// Given an object and a method name, tear off the method.
|
||||
/// 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) {
|
||||
if (obj == null) obj = jsNull;
|
||||
var property = _canonicalMember(obj, name);
|
||||
if (method == null) method = JS('', '#[#]', obj, property);
|
||||
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);
|
||||
// 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);
|
||||
JS('', '#._defaultTypeArgs = #', f, defaultTypeArgs);
|
||||
}
|
||||
JS('', '#[#] = #', f, JS_GET_NAME(JsGetName.SIGNATURE_NAME), methodType);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
/// Binds the `call` method of an interface type, handling null.
|
||||
///
|
||||
/// Essentially this works like `obj?.call`. It also handles the needs of
|
||||
/// [dsend]/[dcall], returning `null` if no method was found with the given
|
||||
/// canonical member [name].
|
||||
///
|
||||
/// [name] is typically `"call"` but it could be the [extensionSymbol] for
|
||||
/// `call`, if we define it on a native type, and [obj] is known statically to
|
||||
/// be a native type/interface with `call`.
|
||||
bindCall(obj, name) {
|
||||
if (obj == null) return null;
|
||||
var ftype = getMethodType(obj, name);
|
||||
if (ftype == null) return null;
|
||||
var method = JS('', '#[#]', obj, name);
|
||||
var f = JS('', '#.bind(#)', method, obj);
|
||||
// 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
|
||||
// they are needed for a dynamic call.
|
||||
var defaultTypeArgs = getMethodDefaultTypeArgs(obj, name);
|
||||
JS('', '#._defaultTypeArgs = #', f, defaultTypeArgs);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
/// Instantiate a generic method.
|
||||
///
|
||||
/// We need to apply the type arguments both to the function, as well as its
|
||||
@@ -540,11 +539,14 @@ _checkAndCall(f, ftype, obj, typeArgs, args, named, displayName) {
|
||||
// (we're now trying `call()` on `f`, so we want to call its nSM rather
|
||||
// than the original target's nSM).
|
||||
originalTarget = f;
|
||||
f = bindCall(f, _canonicalMember(f, 'call'));
|
||||
// Use [getMethodType] to determine if 'call' is allowed to be dynamically
|
||||
// torn off on this object.
|
||||
f = getMethodType(f, 'call') == null ? null : tearoff(f, null, 'call');
|
||||
ftype = null;
|
||||
displayName = 'call';
|
||||
}
|
||||
if (f == null) {
|
||||
if (f == null ||
|
||||
JS<bool>('', '#._boundObject[#._boundName] == null', f, f)) {
|
||||
return callNSM("Dynamic call of object has no instance method 'call'.");
|
||||
}
|
||||
}
|
||||
@@ -1029,12 +1031,12 @@ String _toString(obj) {
|
||||
/// statically known to have one attached to its prototype (null or a JavaScript
|
||||
/// interop value).
|
||||
@notNull
|
||||
String Function() toStringTearoff(obj) {
|
||||
Object toStringTearoff(obj) {
|
||||
if (obj == null) obj = jsNull;
|
||||
if (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, null, extensionSymbol('toString'));
|
||||
}
|
||||
// Otherwise bind the native JavaScript toString method.
|
||||
// This differs from dart2js to provide a more useful toString at development
|
||||
@@ -1042,7 +1044,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, null, 'toString');
|
||||
}
|
||||
|
||||
/// Converts to a non-null [String], equivalent to
|
||||
@@ -1103,26 +1105,27 @@ noSuchMethod(obj, Invocation invocation) {
|
||||
/// isn't statically known to have one attached to its prototype (null or a
|
||||
/// JavaScript interop value).
|
||||
@notNull
|
||||
dynamic Function(Invocation) noSuchMethodTearoff(obj) {
|
||||
if (obj == null) obj = jsNull;
|
||||
if (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);
|
||||
Object noSuchMethodTearoff(context) {
|
||||
if (context == null) context = jsNull;
|
||||
if (JS<bool>(
|
||||
'!',
|
||||
'#[#] !== void 0',
|
||||
context,
|
||||
extensionSymbol('noSuchMethod'),
|
||||
)) {
|
||||
// The bind helper can handle finding the noSuchMethod method for null or
|
||||
// Dart Objects.
|
||||
return tearoff(context, null, extensionSymbol('noSuchMethod'));
|
||||
}
|
||||
// Otherwise, manually pass the Dart Core Object noSuchMethod to the bind
|
||||
// helper.
|
||||
return bind(
|
||||
obj,
|
||||
// Otherwise, tear off the Dart Core Object's noSuchMethod.
|
||||
var tear = tearoff(
|
||||
JS_CLASS_REF(Object),
|
||||
null,
|
||||
'noSuchMethod',
|
||||
JS(
|
||||
'!',
|
||||
'#.prototype[#]',
|
||||
JS_CLASS_REF(Object),
|
||||
extensionSymbol('noSuchMethod'),
|
||||
),
|
||||
extensionSymbol('noSuchMethod'),
|
||||
);
|
||||
// Update the bound object for equality correctness.
|
||||
JS('', '#._boundObject = #', tear, context);
|
||||
return tear;
|
||||
}
|
||||
|
||||
/// The default implementation of `noSuchMethod` to match `Object.noSuchMethod`.
|
||||
@@ -1167,20 +1170,26 @@ final JsIterator = JS('', '''
|
||||
|
||||
_canonicalMember(obj, name) {
|
||||
// Private names are symbols and are already canonical.
|
||||
if (JS('!', 'typeof # === "symbol"', name)) return name;
|
||||
if (JS<bool>('!', '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))) {
|
||||
if (obj != null) {
|
||||
// 'toString', 'call', and 'noSuchMethod' use their extension symbol when
|
||||
// available but default to their string names.
|
||||
if (JS<bool>(
|
||||
'!',
|
||||
'# === "toString" || # === "noSuchMethod" || # === "call"',
|
||||
name,
|
||||
name,
|
||||
name,
|
||||
) &&
|
||||
JS<bool>('!', '#[#] != null', obj, extensionSymbol(name))) {
|
||||
return extensionSymbol(name) ?? name;
|
||||
}
|
||||
if (JS<bool>('!', '#[#] != null', obj, _extensionType)) {
|
||||
return extensionSymbol(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj != null && JS<bool>('!', '#[#] != null', obj, _extensionType)) {
|
||||
return extensionSymbol(name);
|
||||
}
|
||||
|
||||
// Check for certain names that we can't use in JS
|
||||
if (JS('!', '# == "constructor" || # == "prototype"', name, name)) {
|
||||
JS('', '# = "+" + #', name, name);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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<T>() {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
|
||||
class Foo extends Bar {
|
||||
get tearoff => super.method<String>;
|
||||
get tearoff2 => super.method;
|
||||
}
|
||||
|
||||
Future<void> main() async {
|
||||
var tearoff = Foo().tearoff;
|
||||
var tearoff2 = Foo().tearoff2;
|
||||
Expect.equals(42, tearoff());
|
||||
Expect.equals(42, tearoff2<String>());
|
||||
await hotReload();
|
||||
|
||||
Expect.equals(100, tearoff());
|
||||
Expect.equals(100, tearoff2());
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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 **/
|
||||
/*
|
||||
// Tests reload succeeds when super getter are updated.
|
||||
|
||||
class Bar {
|
||||
- method<T>() {
|
||||
- return 42;
|
||||
+ method() {
|
||||
+ return 100;
|
||||
}
|
||||
}
|
||||
|
||||
class Foo extends Bar {
|
||||
- get tearoff => super.method<String>;
|
||||
- get tearoff2 => super.method;
|
||||
+ get tearoff => super.method;
|
||||
}
|
||||
|
||||
Future<void> main() async {
|
||||
var tearoff = Foo().tearoff;
|
||||
- var tearoff2 = Foo().tearoff2;
|
||||
Expect.equals(42, tearoff());
|
||||
- Expect.equals(42, tearoff2<String>());
|
||||
await hotReload();
|
||||
|
||||
Expect.equals(100, tearoff());
|
||||
- Expect.equals(100, tearoff2());
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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';
|
||||
|
||||
// Test identity and equality of 'noSuchMethod' tearoffs.
|
||||
|
||||
void checkIdentical(Object o1, Object o2) {
|
||||
Expect.isTrue(identical(o1, o2));
|
||||
Expect.isTrue(o1 == o2);
|
||||
Expect.isTrue(o2 == o1);
|
||||
}
|
||||
|
||||
void checkEqual(Object o1, Object o2) {
|
||||
Expect.isTrue(o1 == o2);
|
||||
Expect.isTrue(o2 == o1);
|
||||
// The behavior of `identical` is unspecified, optimizations could
|
||||
// make a difference and should be allowed: Do not expect anything.
|
||||
}
|
||||
|
||||
void checkUnequal(Object o1, Object o2) {
|
||||
Expect.isTrue(o1 != o2);
|
||||
Expect.isTrue(o2 != o1);
|
||||
// We expect that `identical` is never true when `==` yields false.
|
||||
Expect.isFalse(identical(o1, o2));
|
||||
}
|
||||
|
||||
class CheckIdentical {
|
||||
const CheckIdentical(Object o1, Object o2) : assert(identical(o1, o2));
|
||||
}
|
||||
|
||||
class CheckNotIdentical {
|
||||
const CheckNotIdentical(Object o1, Object o2) : assert(!identical(o1, o2));
|
||||
}
|
||||
|
||||
class A {
|
||||
// Enable a mixed-in method in `M` that has a superinvocation.
|
||||
noSuchMethod(Invocation i) => 'A';
|
||||
}
|
||||
|
||||
mixin M on A {
|
||||
noSuchMethod(Invocation i) => super.noSuchMethod(i) + ' M on A';
|
||||
}
|
||||
|
||||
class AM extends A with M {
|
||||
Function(Invocation i) get tearoffSuperMethod => super.noSuchMethod;
|
||||
}
|
||||
|
||||
class AMM extends AM with M {
|
||||
// Tear off the second copy of M.noSuchMethod
|
||||
// (`tearoffSuperMethod` still tears off the first copy).
|
||||
Function(Invocation i) get tearoffSuperMethodSecond => super.noSuchMethod;
|
||||
// In this case, `super.` should not make a difference.
|
||||
Function(Invocation i) get tearoffSuperMethodSecondNoSuper => noSuchMethod;
|
||||
}
|
||||
|
||||
void main() {
|
||||
var amm = AMM();
|
||||
Function(Invocation i) vMixedInSuperMethod1 = amm.tearoffSuperMethod;
|
||||
Function(Invocation i) vMixedInSuperMethod2 = amm.tearoffSuperMethod;
|
||||
Function(Invocation i) vMixedInSuperMethodSecond1 =
|
||||
amm.tearoffSuperMethodSecond;
|
||||
Function(Invocation i) vMixedInSuperMethodSecond2 =
|
||||
amm.tearoffSuperMethodSecond;
|
||||
Function(Invocation i) vMixedInSuperMethodSecondNoSuper1 =
|
||||
amm.tearoffSuperMethodSecondNoSuper;
|
||||
Function(Invocation i) vMixedInSuperMethodSecondNoSuper2 =
|
||||
amm.tearoffSuperMethodSecondNoSuper;
|
||||
|
||||
checkEqual(amm.tearoffSuperMethod, amm.tearoffSuperMethod);
|
||||
checkEqual(vMixedInSuperMethod1, vMixedInSuperMethod2);
|
||||
checkEqual(amm.tearoffSuperMethodSecond, amm.tearoffSuperMethodSecond);
|
||||
checkEqual(vMixedInSuperMethodSecond1, vMixedInSuperMethodSecond2);
|
||||
checkUnequal(amm.tearoffSuperMethod, amm.tearoffSuperMethodSecond);
|
||||
checkUnequal(vMixedInSuperMethod1, vMixedInSuperMethodSecond2);
|
||||
checkUnequal(amm.tearoffSuperMethodSecond, amm.tearoffSuperMethod);
|
||||
checkUnequal(vMixedInSuperMethodSecond1, vMixedInSuperMethod2);
|
||||
|
||||
checkEqual(
|
||||
amm.tearoffSuperMethodSecondNoSuper,
|
||||
amm.tearoffSuperMethodSecondNoSuper,
|
||||
);
|
||||
checkEqual(
|
||||
vMixedInSuperMethodSecondNoSuper1,
|
||||
vMixedInSuperMethodSecondNoSuper2,
|
||||
);
|
||||
checkUnequal(amm.tearoffSuperMethod, amm.tearoffSuperMethodSecondNoSuper);
|
||||
checkUnequal(vMixedInSuperMethod1, vMixedInSuperMethodSecondNoSuper2);
|
||||
checkUnequal(amm.tearoffSuperMethodSecondNoSuper, amm.tearoffSuperMethod);
|
||||
checkUnequal(vMixedInSuperMethodSecondNoSuper1, vMixedInSuperMethod2);
|
||||
|
||||
checkEqual(amm.tearoffSuperMethodSecond, amm.tearoffSuperMethodSecondNoSuper);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// 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';
|
||||
|
||||
// Test identity and equality of 'toString' tearoffs.
|
||||
|
||||
void checkIdentical(Object o1, Object o2) {
|
||||
Expect.isTrue(identical(o1, o2));
|
||||
Expect.isTrue(o1 == o2);
|
||||
Expect.isTrue(o2 == o1);
|
||||
}
|
||||
|
||||
void checkEqual(Object o1, Object o2) {
|
||||
Expect.isTrue(o1 == o2);
|
||||
Expect.isTrue(o2 == o1);
|
||||
// The behavior of `identical` is unspecified, optimizations could
|
||||
// make a difference and should be allowed: Do not expect anything.
|
||||
}
|
||||
|
||||
void checkUnequal(Object o1, Object o2) {
|
||||
Expect.isTrue(o1 != o2);
|
||||
Expect.isTrue(o2 != o1);
|
||||
// We expect that `identical` is never true when `==` yields false.
|
||||
Expect.isFalse(identical(o1, o2));
|
||||
}
|
||||
|
||||
class CheckIdentical {
|
||||
const CheckIdentical(Object o1, Object o2) : assert(identical(o1, o2));
|
||||
}
|
||||
|
||||
class CheckNotIdentical {
|
||||
const CheckNotIdentical(Object o1, Object o2) : assert(!identical(o1, o2));
|
||||
}
|
||||
|
||||
class A {
|
||||
// Enable a mixed-in method in `M` that has a superinvocation.
|
||||
String toString() => 'A';
|
||||
}
|
||||
|
||||
mixin M on A {
|
||||
String toString() => super.toString() + ' M on A';
|
||||
}
|
||||
|
||||
class AM extends A with M {
|
||||
String Function() get tearoffSuperMethod => super.toString;
|
||||
}
|
||||
|
||||
class AMM extends AM with M {
|
||||
// Tear off the second copy of M.toString
|
||||
// (`tearoffSuperMethod` still tears off the first copy).
|
||||
String Function() get tearoffSuperMethodSecond => super.toString;
|
||||
// In this case, `super.` should not make a difference.
|
||||
String Function() get tearoffSuperMethodSecondNoSuper => toString;
|
||||
}
|
||||
|
||||
void main() {
|
||||
var amm = AMM();
|
||||
String Function() vMixedInSuperMethod1 = amm.tearoffSuperMethod;
|
||||
String Function() vMixedInSuperMethod2 = amm.tearoffSuperMethod;
|
||||
String Function() vMixedInSuperMethodSecond1 = amm.tearoffSuperMethodSecond;
|
||||
String Function() vMixedInSuperMethodSecond2 = amm.tearoffSuperMethodSecond;
|
||||
String Function() vMixedInSuperMethodSecondNoSuper1 =
|
||||
amm.tearoffSuperMethodSecondNoSuper;
|
||||
String Function() vMixedInSuperMethodSecondNoSuper2 =
|
||||
amm.tearoffSuperMethodSecondNoSuper;
|
||||
|
||||
checkEqual(amm.tearoffSuperMethod, amm.tearoffSuperMethod);
|
||||
checkEqual(vMixedInSuperMethod1, vMixedInSuperMethod2);
|
||||
checkEqual(amm.tearoffSuperMethodSecond, amm.tearoffSuperMethodSecond);
|
||||
checkEqual(vMixedInSuperMethodSecond1, vMixedInSuperMethodSecond2);
|
||||
checkUnequal(amm.tearoffSuperMethod, amm.tearoffSuperMethodSecond);
|
||||
checkUnequal(vMixedInSuperMethod1, vMixedInSuperMethodSecond2);
|
||||
checkUnequal(amm.tearoffSuperMethodSecond, amm.tearoffSuperMethod);
|
||||
checkUnequal(vMixedInSuperMethodSecond1, vMixedInSuperMethod2);
|
||||
|
||||
checkEqual(
|
||||
amm.tearoffSuperMethodSecondNoSuper,
|
||||
amm.tearoffSuperMethodSecondNoSuper,
|
||||
);
|
||||
checkEqual(
|
||||
vMixedInSuperMethodSecondNoSuper1,
|
||||
vMixedInSuperMethodSecondNoSuper2,
|
||||
);
|
||||
checkUnequal(amm.tearoffSuperMethod, amm.tearoffSuperMethodSecondNoSuper);
|
||||
checkUnequal(vMixedInSuperMethod1, vMixedInSuperMethodSecondNoSuper2);
|
||||
checkUnequal(amm.tearoffSuperMethodSecondNoSuper, amm.tearoffSuperMethod);
|
||||
checkUnequal(vMixedInSuperMethodSecondNoSuper1, vMixedInSuperMethod2);
|
||||
|
||||
checkEqual(amm.tearoffSuperMethodSecond, amm.tearoffSuperMethodSecondNoSuper);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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.
|
||||
|
||||
// Checks correctness of super tearoff invocation order across complex mixin
|
||||
// hierarchies.
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
var superTearoffCallOrder = <String>[];
|
||||
|
||||
class C extends Super1 with M1, M2 {}
|
||||
|
||||
mixin M1 on Super2 {}
|
||||
|
||||
mixin M2 on Super1 {}
|
||||
|
||||
class Super1 extends Super2 {
|
||||
paint() {
|
||||
superTearoffCallOrder.add('Super1');
|
||||
return super.paint;
|
||||
}
|
||||
}
|
||||
|
||||
class Super2 {
|
||||
paint() {
|
||||
superTearoffCallOrder.add('Super2');
|
||||
return this.paint;
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
var tearoff = C().paint();
|
||||
tearoff();
|
||||
Expect.listEquals(['Super1', 'Super2'], superTearoffCallOrder);
|
||||
}
|
||||
Reference in New Issue
Block a user