visitSimpleIdentifier method
Visit a SSimpleIdentifier.
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})",
);
}
}