get method

  1. @override
Object? get(
  1. String name, {
  2. InterpreterVisitor? visitor,
})
override

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}.");
}