get method
Accesses a property or method of this value.
Implementation
@override
Object? get(String name, {InterpreterVisitor? visitor}) {
Logger.debug(
"[Instance.get] Looking for '$name' on instance $hashCode of '${klass.name}'. Fields: ${_fields.keys}");
// Cluster J #29: returns true when `klass` itself or any of its
// ancestors via `superclass` registers a `bridgedSuperclass`. Used by
// the GEN-128 bridged-mixin skip below to decide whether falling
// through to the bridged-super walk can resolve the call.
bool hasBridgedSuperInChain(InterpretedClass? k) {
InterpretedClass? walk = k;
while (walk != null) {
if (walk.bridgedSuperclass != null) return true;
walk = walk.superclass;
}
return false;
}
// Handle Object properties that all objects have
switch (name) {
case 'runtimeType':
return klass;
case 'hashCode':
return hashCode;
}
// Check fields in the current instance first
if (_fields.containsKey(name)) {
final fieldValue = _fields[name];
if (fieldValue is LateVariable) {
// Return the value of the late variable (will initialize if needed)
Logger.debug(
"[Instance.get] Found late field '$name', accessing value...");
return fieldValue.value;
}
Logger.debug(
"[Instance.get] Found field '$name' with value: $fieldValue");
return fieldValue;
}
Logger.debug(
"[Instance.get] Field '$name' not found in instance fields. Checking getters/methods...");
// Check instance members (getter/method) in the current class and superclasses
InterpretedClass? currentClass = klass;
while (currentClass != null) {
final getter = currentClass.findInstanceGetter(name);
if (getter != null) {
if (visitor != null) {
return getter.bind(this).call(visitor, [], {});
} else {
return getter.bind(this); // Bind to the *original* instance ('this')
}
}
try {
final staticField = currentClass.getStaticField(name);
if (staticField != null) {
return staticField;
}
} catch (_) {}
final staticGetter = currentClass.findStaticGetter(name);
if (staticGetter != null) {
if (visitor != null) {
return staticGetter.bind(this).call(visitor, [], {});
} else {
return staticGetter
.bind(this); // Bind to the *original* instance ('this')
}
}
final staticMethod = currentClass.findStaticMethod(name);
if (staticMethod != null) {
return staticMethod.bind(this);
}
final method = currentClass.findInstanceMethod(name);
if (method != null) {
// Perf S2: cached tear-off — bind once, reuse on subsequent accesses.
return bindMethodCached(method); // Bound to the *original* 'this'
}
// Bug-45 (narrowed): widget access on State<T> subclass instances
// returns the parent StatefulWidget InterpretedInstance directly,
// bypassing bridged-State adapter dispatch. Only fires on the
// outermost iteration (when currentClass == klass) so an explicit
// `super.widget` walk doesn't pick this up unintentionally.
if (name == 'widget' &&
currentClass == klass &&
interpretedStatefulWidget != null) {
return interpretedStatefulWidget;
}
// Check bridged superclass at this level before moving up.
// RC-6: Use nativeProxy as fallback when bridgedSuperObject is null.
// This supports abstract class adapters (like _InterpretedState for State).
if (currentClass.bridgedSuperclass != null) {
// C14: getters may additionally fall back to `nativeStateProxy` so
// read-only State members (`context`, `mounted`) on plain interpreted
// State subclasses (no nativeProxy by Bug-45 design) still resolve
// through the proxy's real `_element`. Methods deliberately exclude
// it to preserve Bug-45 — `setState` etc. on plain States must keep
// routing to the RC-9 no-op fallback below, not back through Flutter.
final nativeTarget = bridgedSuperObject ?? nativeProxy;
final getterTarget = nativeTarget ?? nativeStateProxy;
if (getterTarget != null || nativeTarget != null) {
final bridgedSuper = currentClass.bridgedSuperclass!;
// RC-6b: For 'widget' access on State subclasses with nativeProxy,
// check if the proxy has an 'interpretedWidget' getter that returns
// the original InterpretedInstance of the widget class.
if (name == 'widget' && bridgedSuperObject == null && nativeProxy != null) {
try {
final dynamic proxy = nativeProxy;
// Duck-type check for InterpretedStateProxy.interpretedWidget
final interpretedWidget = proxy.interpretedWidget;
if (interpretedWidget is InterpretedInstance) {
Logger.debug(
"[Instance.get] Using interpretedWidget from nativeProxy for '$name' access.");
return interpretedWidget;
}
} catch (_) {
// nativeProxy doesn't have interpretedWidget, fall through to normal handling
}
}
// Try getter first — getters may use `nativeStateProxy` as
// fallback target (C14), methods may not.
final getterAdapter = bridgedSuper.findInstanceGetterAdapter(name);
if (getterAdapter != null && getterTarget != null) {
Logger.debug(
"[Instance.get] Found getter '$name' in bridged superclass '${bridgedSuper.name}' at level '${currentClass.name}'. Calling adapter.");
try {
final result = getterAdapter(visitor, getterTarget);
// Check if result is a native enum that has been bridged
if (result != null && visitor != null) {
final bridgedEnumValue =
visitor.environment.getBridgedEnumValue(result);
if (bridgedEnumValue != null) {
return bridgedEnumValue;
}
}
return result;
} catch (e, s) {
Logger.error(
"Native exception during bridged superclass getter '$name': $e\n$s");
throw RuntimeD4rtException(
"Native error in bridged superclass getter '$name': $e", originalException: e);
}
}
// Methods require the strict `nativeTarget` (no nativeStateProxy
// fallback) — see Bug-45.
if (nativeTarget != null) {
// Try method next
final methodAdapter = bridgedSuper.findInstanceMethodAdapter(name);
if (methodAdapter != null) {
Logger.debug(
"[Instance.get] Found method '$name' in bridged superclass '${bridgedSuper.name}' at level '${currentClass.name}'. Returning bound callable.");
return BridgedSuperMethodCallable(
nativeTarget, methodAdapter, name, bridgedSuper.name);
}
// RC-5: Check supplementary method adapters for unbridged methods.
// Methods like ChangeNotifier.notifyListeners() are @protected and
// not included in the generated bridge. Supplementary adapters
// registered via D4.registerSupplementaryMethod() fill this gap.
final supplementaryAdapter = D4.findSupplementaryMethod(
bridgedSuper.name, name);
if (supplementaryAdapter != null) {
Logger.debug(
"[Instance.get] Found supplementary method '$name' for bridged superclass '${bridgedSuper.name}'.");
return BridgedSuperMethodCallable(
nativeTarget, supplementaryAdapter, name, bridgedSuper.name);
}
}
} // end if (getterTarget != null || nativeTarget != null)
} // end if (bridgedSuperclass != null)
// Move up to the superclass
currentClass = currentClass.superclass;
}
// Check mixins (both interpreted and bridged) - reverse order for correct precedence
// (Last applied mixin wins in case of conflicts)
// Check interpreted mixins (in reverse order)
for (int i = klass.mixins.length - 1; i >= 0; i--) {
final mixin = klass.mixins[i];
final getter = mixin.findInstanceGetter(name);
if (getter != null) {
if (visitor != null) {
return getter.bind(this).call(visitor, [], {});
} else {
return getter.bind(this);
}
}
final method = mixin.findInstanceMethod(name);
if (method != null) {
// Perf S2: cached tear-off — see [_boundMethodCache].
return bindMethodCached(method);
}
}
// Check bridged mixins (in reverse order)
for (int i = klass.bridgedMixins.length - 1; i >= 0; i--) {
final bridgedMixin = klass.bridgedMixins[i];
// C10: For bridged mixins backed by native Flutter mixins (e.g.
// RestorationMixin), the adapter expects a real instance of the
// mixin type. When a native State proxy mixes the bridged mixin in
// (see d4rt_runtime_registrations.dart), the proxy is registered on
// `nativeProxy` and is the correct target. Fall back to
// `bridgedSuperObject`, then to `this` for purely-interpreted use.
final mixinTarget = nativeProxy ?? bridgedSuperObject ?? this;
// Try getter first
final getterAdapter = bridgedMixin.findInstanceGetterAdapter(name);
if (getterAdapter != null) {
Logger.debug(
"[Instance.get] Found getter '$name' in bridged mixin '${bridgedMixin.name}'. Calling adapter directly.");
try {
// For bridged mixins, call the getter directly and return the value
return getterAdapter(visitor, mixinTarget);
} on ArgumentD4rtException catch (e) {
// Cluster B: When a script class is `extends BridgedClass with
// BridgedMixin`, the native proxy registered on `nativeProxy`
// (e.g. _InterpretedRenderBox) is built ahead of time by the
// proxy factory in d4rt_runtime_registrations.dart and only
// mixes-in a fixed whitelist (ContainerRenderObjectMixin,
// SlottedContainerRenderObjectMixin, …). Bridged mixins outside
// that whitelist (e.g. RelayoutWhenSystemFontsChangeMixin,
// RenderBoxContainerDefaultsMixin via mixin chains) are NOT
// applied to the proxy. The bridge generator emits inherited
// member adapters (constraints, parentData, …) on every mixin's
// bridge with a strict `validateTarget<MixinType>` cast, which
// fails for our proxy.
//
// The mixin's `constraints` adapter is just an inherited delegate
// for `RenderObject.constraints`; an adapter on the bridged
// SUPERCLASS (e.g. RenderBox) reads the same field and accepts
// the proxy. Fall through to the bridged-super chain below to
// resolve via the superclass's adapter. If no super adapter
// matches either, we drop to the noSuchMethod / RC-9 fallbacks.
Logger.debug(
"[Instance.get] Bridged mixin '${bridgedMixin.name}' getter '$name' rejected target "
"${mixinTarget.runtimeType} (${e.message}). Falling through to bridged-super walk.");
// Continue to next mixin; if all mixins reject, the post-loop
// bridged-super fallback below handles the lookup.
continue;
} catch (e, s) {
Logger.error(
"Native exception during bridged mixin getter '$name': $e\n$s");
throw RuntimeD4rtException(
"Native error in bridged mixin getter '$name': $e", originalException: e);
}
}
// Try method next
final methodAdapter = bridgedMixin.findInstanceMethodAdapter(name);
if (methodAdapter != null) {
// GEN-128 (generic mixin-inherited-method fix): when the bridged
// mixin's adapter would receive the InterpretedInstance itself
// (because no native proxy / bridged-super object is available)
// AND no interface proxy can be materialised for this bridged
// mixin, the adapter cannot validate the target — it would
// throw `ArgumentD4rtException` at call time. Skip the bridged
// mixin so the post-loop bridged-super walk + RC-9 fallback
// handles the lookup instead. Typical case: an interpreted
// `State<T>` subclass mixing in `AutomaticKeepAliveClientMixin`
// calls `setState(...)` — `setState` is inherited from `State`,
// not defined by the mixin, so the call must route through the
// State's `nativeStateProxy` (RC-9 → BridgedSuperMethodCallable)
// rather than the mixin adapter. Mirrors tom_d4rt.
//
// Cluster J #29: the skip used to fire unconditionally when no
// proxy was available. For purely script-side bridged mixins
// (e.g. `class Calculator with TestMixin` where TestMixin is
// registered as a `BridgedClass` with `canBeUsedAsMixin: true`)
// there IS no bridged-super walk to fall through to — the mixin
// adapter is the only resolution. Only skip when there's
// actually a downstream bridged-super path; otherwise call the
// adapter and let it succeed (TestMixin-style adapters that
// don't validate the target) or surface a `Native error`.
if (identical(mixinTarget, this) &&
(visitor == null ||
D4.tryCreateInterfaceProxyByName(
bridgedMixin.name, this, visitor) ==
null) &&
hasBridgedSuperInChain(klass)) {
Logger.debug(
"[Instance.get] Skipping bridged mixin '${bridgedMixin.name}' "
"method '$name' — no native target and no interface proxy "
"available; falling through to bridged-super walk.");
continue;
}
Logger.debug(
"[Instance.get] Found method '$name' in bridged mixin '${bridgedMixin.name}'. Creating bound callable.");
// Return a callable that binds the method to the appropriate target
return BridgedMixinMethodCallable(
this, methodAdapter, name, bridgedMixin.name,
target: mixinTarget);
}
}
// Check for noSuchMethod before throwing an error
final noSuchMethod = klass.findInstanceMethod('noSuchMethod');
if (noSuchMethod != null && visitor != null) {
Logger.debug(
"[Instance.get] Property '$name' not found but noSuchMethod exists. Invoking noSuchMethod...");
// Create an Invocation.getter for this property access
final invocation = Invocation.getter(Symbol(name));
final boundNoSuchMethod = noSuchMethod.bind(this);
return boundNoSuchMethod.call(visitor, [invocation], {});
}
// RC-9: last-chance fallback for bridged-super members when there is
// no native target (typical for plain widgets / plain `_InterpretedState`).
// If a bridged superclass in the chain exposes a method/getter adapter
// for this name, return a no-target stand-in instead of throwing:
// - method → a NativeFunction that invokes any Callable argument (so
// `setState(() { _x = 1; })` still runs the script's
// callback and updates script state, even though the
// Flutter rebuild won't actually be scheduled) and then
// returns null.
// - getter → null (the script may guard on null anyway; this matches
// the "no native value available" semantics).
// This mirrors the cluster-3 `super.<method>()` no-op treatment landed
// in [5c0c5939] but for direct (unprefixed) access.
{
InterpretedClass? walkClass = klass;
while (walkClass != null) {
final bridgedSuper = walkClass.bridgedSuperclass;
if (bridgedSuper != null) {
final methodAdapter = bridgedSuper.findInstanceMethodAdapter(name);
if (methodAdapter != null) {
// GEN-112 — when an interpreted `State<T>` subclass owns a
// native `_InterpretedState` proxy (stored on
// `nativeStateProxy`), dispatch the bridged-super method
// through that proxy so `setState`, `initState`, etc. fire
// on the real Flutter element. The original Bug-45
// narrowing left this slot a no-op to dodge cascading
// rebuild loops; the scheduler-phase guard in
// `StateUserBridge.overrideMethodSetState` (defers mid-
// frame setStates via `addPostFrameCallback`) plus the
// proxy's own `_lifecycleInProgress` re-entrancy guard
// already handle that hazard. Mirror of `tom_d4rt`.
if (nativeStateProxy != null) {
Logger.debug(
"[Instance.get] GEN-112: routing '${bridgedSuper.name}.$name' through nativeStateProxy ${nativeStateProxy.runtimeType}.");
return BridgedSuperMethodCallable(
nativeStateProxy!, methodAdapter, name, bridgedSuper.name);
}
Logger.debug(
"[Instance.get] No native target for '${bridgedSuper.name}.$name' — returning callback-invoking no-op fallback.");
return NativeFunction(
(v, positionalArgs, namedArgs, typeArgs) {
for (final arg in positionalArgs) {
if (arg is Callable) {
arg.call(v, const [], const {});
}
}
return null;
},
arity: 0,
name: name);
}
final getterAdapter = bridgedSuper.findInstanceGetterAdapter(name);
if (getterAdapter != null) {
// Cluster B: When a bridged-mixin's getter adapter rejected the
// native proxy via `validateTarget` (because the proxy doesn't
// mix-in that mixin), the mixin loop above falls through to
// here with `nativeProxy` still set. The same-named getter on
// the bridged superclass typically accepts the proxy (its
// `validateTarget<BridgedSuper>` succeeds because the proxy
// extends the bridged super by construction), so call it
// directly. Only return null when there really is no native
// target available (the original RC-9 semantics for plain
// interpreted widgets / States).
final nativeTarget = bridgedSuperObject ?? nativeProxy;
if (nativeTarget != null) {
try {
Logger.debug(
"[Instance.get] RC-9b: calling bridged-super '${bridgedSuper.name}.$name' getter with nativeTarget ${nativeTarget.runtimeType}.");
return getterAdapter(visitor, nativeTarget);
} on ArgumentD4rtException catch (e) {
Logger.debug(
"[Instance.get] RC-9b: bridged-super '${bridgedSuper.name}.$name' adapter rejected nativeTarget: ${e.message}. Walking up.");
// Continue walking up the bridged-super chain.
} catch (e, s) {
Logger.error(
"Native exception during bridged super getter '$name' (RC-9b): $e\n$s");
throw RuntimeD4rtException(
"Native error in bridged super getter '$name': $e", originalException: e);
}
} else {
Logger.debug(
"[Instance.get] No native target for '${bridgedSuper.name}.$name' getter — returning null.");
return null;
}
}
}
walkClass = walkClass.superclass;
}
}
// If not found anywhere in the hierarchy or bridge
throw RuntimeD4rtException("Undefined property '$name' on ${klass.name}.");
}