visitSimpleIdentifier method

  1. @override
Object? visitSimpleIdentifier(
  1. SSimpleIdentifier node
)
override

Implementation

@override
Object? visitSimpleIdentifier(SSimpleIdentifier node) {
  final name = node.name;

  if (Logger.isDebug) {
    Logger.debug(
      "[visitSimpleIdentifier] Looking for '$name'. Visitor env: ${environment.hashCode}",
    );
  }

  // Lexical search & Bridges.
  //
  // S3c (plan_3 §9.3 / §4.6): an eligible innermost-block local carries a
  // resolved slot directly on the node ([SSimpleIdentifier.resolvedSlot]);
  // read it straight from the current frame's slot array — one field read +
  // one list index, no name hashing and no chain walk (note-6 §10.4).
  // Eligibility (the resolver) guarantees the matching `defineSlot` ran in
  // this same frame before any read, and that the local is never assigned or
  // captured, so the slot is the sole live binding. Every other use (bridge
  // / prefixed / enum / non-local) carries no slot and takes the full
  // name-based [Environment.lookup] walk. The slot field also survives
  // serialization, so analyzer-free bundles take this fast path too.
  //
  // The lexical walk uses the NON-THROWING [Environment.lookup]: a miss here
  // is the COMMON case for an instance-member reference (a bare field/getter
  // that means `this.<name>`), so the old throwing `get()` turned every such
  // read into a thrown+caught `RuntimeD4rtException` with a captured stack
  // trace — thousands per second under an animation loop, dominating CPU and
  // allocation. A sentinel keeps the miss on the normal return path; we fall
  // through to the implicit-`this` lookup below without ever throwing.
  Object? value;
  final bool foundLexically;
  final slot = node.resolvedSlot;
  if (slot != null) {
    value = environment.getSlot(slot);
    foundLexically = true;
  } else {
    final looked = environment.lookup(name);
    foundLexically = !identical(looked, Environment.kNotFound);
    value = foundLexically ? looked : null;
  }

  if (foundLexically) {
    if (Logger.isDebug) {
      Logger.debug(
        "[visitSimpleIdentifier] Found '$name' via environment.lookup() -> ${value?.runtimeType}",
      );
    }

    // Handle late variables. `.value` triggers lazy initialization and may
    // throw LateInitializationError; per Plan H that must surface unwrapped
    // (native Dart semantics), so we let it propagate rather than fall through
    // to the implicit-'this' lookup that would rewrap it as "Undefined
    // variable".
    if (value is LateVariable) {
      if (Logger.isDebug) {
        Logger.debug(
          "[visitSimpleIdentifier] Accessing late variable '$name'",
        );
      }
      return value.value;
    }

    // FIX-20260613-1038-C: instance members shadow bridged top-level /
    // library declarations. The lexical walk above also reaches the global
    // scope, so a bridged top-level name (e.g. vector_math's
    // `radians(double)`) can mask an instance field/getter/method of the
    // same name — the wrong way round for Dart, whose order is
    // locals → instance members → library. Correct it here, gated tightly so
    // the common path pays almost nothing:
    //   * the slot fast-path (`slot != null`) is a resolved local — never a
    //     global, so it is skipped entirely;
    //   * we only look past the lexical hit when `this` actually declares a
    //     member of this name (the genuine collision), and only THEN confirm
    //     the lexical value came from the global scope (a true local below
    //     global keeps shadowing, matching Dart).
    if (slot == null) {
      final maybeThis = environment.lookup('this');
      if (maybeThis is InterpretedInstance &&
          maybeThis.hasInstanceMember(name)) {
        final globalHit = globalEnvironment.lookup(name);
        if (identical(globalHit, value)) {
          return maybeThis.get(name, visitor: this);
        }
      }
    }

    // note-6 (§10.4): keep the resolved-read return path free of any String
    // `==`/hash — the `name` comparison only runs when debug logging is on, so
    // a production read is just the slot index + LateVariable check above.
    if (Logger.isDebug && name == 'initialValue') {
      Logger.debug(
        "[visitSimpleIdentifier] Returning '$name' = $value (from lexical/bridge)",
      );
    }
    return value;
  }

  if (Logger.isDebug) {
    Logger.debug(
      "[visitSimpleIdentifier] '$name' not found lexically or as bridge. Trying implicit 'this'.",
    );
  }

  // Implicit attempt via 'this'
  // (Only if not found lexically/as bridge)
  Object? thisInstance;
  try {
    thisInstance = environment.get('this');
  } on RuntimeD4rtException {
    // 'this' does not exist in the current environment.
    // Before giving up, try searching static methods in the current class if we're in a static context
    if (currentFunction != null &&
        currentFunction!.ownerType is InterpretedClass) {
      final ownerClass = currentFunction!.ownerType as InterpretedClass;
      Logger.debug(
        "[visitSimpleIdentifier] 'this' not found, but we're in class '${ownerClass.name}'. Checking static members for '$name'.",
      );

      // Check static methods
      final staticMethod = ownerClass.findStaticMethod(name);
      if (staticMethod != null) {
        Logger.debug(
          "[visitSimpleIdentifier] Found static method '$name' in current class '${ownerClass.name}'.",
        );
        return staticMethod;
      }

      // Check static getters
      final staticGetter = ownerClass.findStaticGetter(name);
      if (staticGetter != null) {
        Logger.debug(
          "[visitSimpleIdentifier] Found static getter '$name' in current class '${ownerClass.name}'.",
        );
        return staticGetter;
      }

      // Check static fields
      try {
        final staticField = ownerClass.getStaticField(name);
        Logger.debug(
          "[visitSimpleIdentifier] Found static field '$name' in current class '${ownerClass.name}'.",
        );
        return staticField;
      } on RuntimeD4rtException {
        // Static field not found, continue to final error
      }
    }

    // This is the end of the search, the identifier is undefined.
    throw RuntimeD4rtException("Undefined variable: $name");
  }

  // 'this' was found, now we try to access the member.
  try {
    if (thisInstance is InterpretedInstance) {
      // Access the property on 'this'
      final member = thisInstance.get(name, visitor: this);
      Logger.debug(
        "[visitSimpleIdentifier] Found '$name' via implicit InterpretedInstance 'this'. Member: ${member?.runtimeType}",
      );
      return member; // Return the field value or bound function (getter/method)
    } else if (toBridgedInstance(thisInstance).$2) {
      final bridgedInstance = toBridgedInstance(thisInstance).$1!;

      Logger.debug(
        "[visitSimpleIdentifier] Trying implicit 'this' access on BridgedInstance (${bridgedInstance.bridgedClass.name}) for member '$name'.",
      );
      // Check instance getter FIRST
      final getterAdapter = bridgedInstance.bridgedClass
          .findInstanceGetterAdapter(name);
      if (getterAdapter != null) {
        Logger.debug(
          "[visitSimpleIdentifier] Found BRIDGED GETTER '$name' via implicit 'this'. Calling adapter...",
        );
        final getterResult = getterAdapter(
          this,
          bridgedInstance.nativeObject,
        );
        if (name == 'initialValue') {
          Logger.debug(
            "[visitSimpleIdentifier] Returning '$name' = $getterResult (from BridgedInstance getter this)",
          );
        }
        return getterResult;
      }
      Logger.debug(
        "[visitSimpleIdentifier]   Bridged getter '$name' not found. Checking for method...",
      );
      // Then check instance method
      final methodAdapter = bridgedInstance.bridgedClass
          .findInstanceMethodAdapter(name);
      if (methodAdapter != null) {
        Logger.debug(
          "[visitSimpleIdentifier] Found BRIDGED METHOD '$name' via implicit 'this'. Binding...",
        );
        // Return a callable bound to the instance

        final boundCallable = BridgedMethodCallable(
          bridgedInstance,
          methodAdapter,
          name,
        );
        if (name == 'initialValue') {
          Logger.debug(
            "[visitSimpleIdentifier] Returning '$name' = $boundCallable (bound method from BridgedInstance this)",
          );
        }
        return boundCallable;
      }
      Logger.debug(
        "[visitSimpleIdentifier]   Bridged method '$name' not found either.",
      );
      // Cluster-12 (priority 3): Walk the registered supertype chain when
      // the leaf bridge has no matching getter/method. See
      // [InterpreterVisitorExtension.lookupOnBridgedSupertypes].
      final supertypeMatch =
          lookupOnBridgedSupertypes(bridgedInstance, name);
      if (supertypeMatch.$2) {
        Logger.debug(
          "[visitSimpleIdentifier]   Resolved '$name' via supertype walk on '${bridgedInstance.bridgedClass.name}'.",
        );
        return supertypeMatch.$1;
      }
      // GEN-075: Fallback for universal Object properties
      switch (name) {
        case 'hashCode':
          return bridgedInstance.nativeObject.hashCode;
        case 'runtimeType':
          return bridgedInstance.nativeObject.runtimeType;
        case 'toString':
          return BridgedMethodCallable(
            bridgedInstance,
            (visitor, nativeObj, positionalArgs, namedArgs, typeArgs) =>
                nativeObj.toString(),
            name,
          );
        default:
      }
      // If neither getter, method, nor Object property, error
      throw RuntimeD4rtException(
        "Undefined property or method '$name' on bridged instance of '${bridgedInstance.bridgedClass.name}' accessed via implicit 'this'.",
      );
    } // +++ NEW BLOCK +++
    else if (thisInstance is InterpretedEnumValue) {
      Logger.debug(
        "[visitSimpleIdentifier] Found '$name' via implicit InterpretedEnumValue 'this'.",
      );
      // Delegate to the get method of the enum value, passing visitor
      // This will execute getters or return bound methods/fields.
      final enumMember = thisInstance.get(name, this);
      if (name == 'initialValue') {
        Logger.debug(
          "[visitSimpleIdentifier] Returning '$name' = $enumMember (from EnumValue this)",
        );
      }
      return enumMember;
    }
    throw RuntimeD4rtException(
      "Undefined variable: $name (this exists as native type ${thisInstance?.runtimeType}",
    );
  } on RuntimeD4rtException catch (thisErr) {
    // Plan H: rethrow LateInitializationError directly (without wrapping
    // as "Undefined variable") so it surfaces to the framework error
    // reporter unwrapped — matches native Dart behaviour and gives
    // accurate diagnostics for "late variable accessed before assigned".
    if (thisErr is LateInitializationError) {
      rethrow;
    }
    // 'this' not found OR instance.get() failed
    // If get() failed with a specific error, propagate it if it is NOT "Undefined property".
    if (thisErr.message.contains("Undefined property '$name'") ||
        thisErr.message.contains("Undefined property or method '$name'")) {
      Logger.debug(
        "[SSimpleIdentifier] Direct access failed for '$name' via implicit 'this'. Trying extension lookup on ${thisInstance?.runtimeType}.",
      );
      if (thisInstance != null) {
        // Check that 'this' exists before searching
        try {
          final extensionMember = environment.findExtensionMember(
            thisInstance,
            name,
          );

          if (extensionMember is ExtensionMemberCallable) {
            if (extensionMember.isGetter) {
              Logger.debug(
                "[SSimpleIdentifier] Found extension getter '$name' via implicit 'this'. Calling...",
              );
              // Extension getters are called with the instance as the only positional argument
              final extensionPositionalArgs = [thisInstance];
              try {
                return extensionMember.call(
                  this,
                  extensionPositionalArgs,
                  {},
                );
              } on ReturnException catch (e) {
                return e.value;
              } catch (e) {
                throw RuntimeD4rtException(
                  "Error executing extension getter '$name' via implicit 'this': $e",
                );
              }
            } else if (!extensionMember.isOperator &&
                !extensionMember.isSetter) {
              // Return the extension method itself (not bound)
              Logger.debug(
                "[SSimpleIdentifier] Found extension method '$name' via implicit 'this'. Returning callable.",
              );
              // Return a bound instance instead of the raw method.
              return BoundExtensionCallable(thisInstance, extensionMember);
            }
            // Operators/setters are generally not accessible directly via a simple identifier
          }
          // No suitable extension member found, fall through to final error
          Logger.debug(
            "[SSimpleIdentifier] No suitable extension member found for '$name' via implicit 'this'.",
          );
        } on RuntimeD4rtException catch (findError) {
          // Error during extension lookup itself
          Logger.debug(
            "[SSimpleIdentifier] Error during extension lookup for '$name' via implicit 'this': ${findError.message}",
          );
          // Fall through to final error
        }
      } else {
        Logger.debug(
          "[SSimpleIdentifier] Cannot search extension for '$name' because implicit 'this' is null.",
        );
      }
    }
    // Relaunch the error if it was NOT "Undefined property" OR if extension lookup failed.
    else if (thisErr.message.contains(
      "non implémenté pour BridgedInstance",
    )) {
      // Relaunch the specific BridgeInstance errors
      rethrow;
    }

    // Before final error, try static method lookup in enclosing class
    final enclosingClass = _findEnclosingClass();
    if (enclosingClass != null) {
      Logger.debug(
        "[visitSimpleIdentifier] Final attempt: checking static members for '$name' in enclosing class '${enclosingClass.name}'.",
      );

      // Check static methods
      final staticMethod = enclosingClass.findStaticMethod(name);
      if (staticMethod != null) {
        Logger.debug(
          "[visitSimpleIdentifier] Found static method '$name' in enclosing class '${enclosingClass.name}' (final attempt).",
        );
        return staticMethod;
      }

      // Check static getters
      final staticGetter = enclosingClass.findStaticGetter(name);
      if (staticGetter != null) {
        Logger.debug(
          "[visitSimpleIdentifier] Found static getter '$name' in enclosing class '${enclosingClass.name}' (final attempt).",
        );
        return staticGetter;
      }

      // Check static fields
      try {
        final staticField = enclosingClass.getStaticField(name);
        Logger.debug(
          "[visitSimpleIdentifier] Found static field '$name' in enclosing class '${enclosingClass.name}' (final attempt).",
        );
        return staticField;
      } on RuntimeD4rtException {
        // Static field not found, continue to final error
      }
    }

    // If the initial error was 'Undefined property' AND that extension lookup failed,
    // or if the initial error was something else, raise the final "Undefined variable" error.
    throw RuntimeD4rtException(
      "Undefined variable: $name (Original error: ${thisErr.message})",
    );
  }
}