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,
              originalStackTrace: s,
            );
          }
        }

        // 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,
          originalStackTrace: s,
        );
      }
    }

    // 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,
                originalStackTrace: s,
              );
            }
          } 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 UndefinedMemberD4rtException(
    "Undefined property '$name' on ${klass.name}.",
    memberName: name,
  );
}