visitPrefixedIdentifier method
Visit a SPrefixedIdentifier.
Implementation
@override
Object? visitPrefixedIdentifier(SPrefixedIdentifier node) {
final prefixValue = node.prefix!.accept<Object?>(this);
if (prefixValue is AsyncSuspensionRequest) {
// Propagate the suspension so that the state machine resumes this node after resolution
return prefixValue;
}
final memberName = node.identifier!.name;
// Handle the case where the prefix is an environment (prefixed import)
if (prefixValue is Environment) {
Logger.debug(
"[SPrefixedIdentifier] The prefix '${node.prefix.toString()}' resolved to an Environment. Searching for '$memberName' in this environment.",
);
try {
// The call to get() on the prefixed environment could return a function (which must be called if it's a getter)
// or a direct value.
final member = prefixValue.get(memberName);
// If it's an InterpretedFunction and a getter, it must be called.
if (member is InterpretedFunction && member.isGetter) {
Logger.debug(
"[SPrefixedIdentifier] Member '$memberName' is a getter. Calling...",
);
return member.call(
this,
[],
{},
); // Call without 'this' because it's a prefixed import
}
Logger.debug(
"[SPrefixedIdentifier] Member '$memberName' found directly: $member",
);
return member; // Return the value/function directly
} on RuntimeD4rtException catch (e) {
throw RuntimeD4rtException(
"Erreur lors de la récupération du membre '$memberName' de l'import préfixé '${node.prefix.toString()}': ${e.message}",
);
}
}
if (prefixValue is InterpretedClass) {
// Static access
try {
return prefixValue.getStaticField(memberName);
} on RuntimeD4rtException catch (_) {
InterpretedFunction? staticMember = prefixValue.findStaticGetter(
memberName,
);
staticMember ??= prefixValue.findStaticMethod(memberName);
if (staticMember != null) {
if (staticMember.isGetter) {
return staticMember.call(this, [], {});
} else {
return staticMember;
}
} else {
// G-DOV-5 FIX: Handle constructor tear-offs (Class.new)
// Return the class itself as it implements Callable and its call()
// method properly creates an instance and calls the constructor.
if (memberName == 'new') {
final constructor = prefixValue.findConstructor('');
if (constructor != null) return prefixValue;
}
// G-DOV2-2 FIX: Handle named constructor tear-offs (Class.fromMap, etc.)
// Named constructors can be used as tear-offs when passed to higher-order functions
final namedConstructor = prefixValue.findConstructor(memberName);
if (namedConstructor != null) {
// Return a callable that will invoke this named constructor
return _NamedConstructorTearOff(
prefixValue,
namedConstructor,
memberName,
);
}
// Cluster C33: class-as-value (Type literal) semantics. A
// PrefixedIdentifier like `slotType.hashCode` where `slotType`
// is bound to an InterpretedClass should dispatch
// `Object.hashCode`/`Object.runtimeType` on the Type instance,
// not look up a static member. Fall back before throwing.
if (memberName == 'hashCode') {
return prefixValue.hashCode;
}
if (memberName == 'runtimeType') {
return prefixValue.runtimeType;
}
throw RuntimeD4rtException(
"Undefined static member '$memberName' on class '${prefixValue.name}'.",
);
}
}
} else if (prefixValue is InterpretedEnum) {
if (memberName == 'values') {
Logger.debug(
"[SPrefixedIdentifier] Accessing static getter 'values' on enum '${prefixValue.name}'.",
);
return prefixValue.valuesList;
}
// Check enum values first
final value = prefixValue.values[memberName];
if (value != null) {
Logger.debug(
"[SPrefixedIdentifier] Accessing enum value '$memberName' on enum '${prefixValue.name}'.",
);
return value;
}
// Check static fields
if (prefixValue.staticFields.containsKey(memberName)) {
Logger.debug(
"[SPrefixedIdentifier] Accessing static field '$memberName' on enum '${prefixValue.name}'.",
);
return prefixValue.staticFields[memberName];
}
// Check static getters
final staticGetter = prefixValue.staticGetters[memberName];
if (staticGetter != null) {
Logger.debug(
"[SPrefixedIdentifier] Calling static getter '$memberName' on enum '${prefixValue.name}'.",
);
return staticGetter.call(this, [], {});
}
// Check static methods
final staticMethod = prefixValue.staticMethods[memberName];
if (staticMethod != null) {
Logger.debug(
"[SPrefixedIdentifier] Accessing static method '$memberName' on enum '${prefixValue.name}'.",
);
return staticMethod;
}
// Check mixins for static members (reverse order)
for (final mixin in prefixValue.mixins.reversed) {
// Check static fields
try {
final mixinStaticField = mixin.getStaticField(memberName);
Logger.debug(
"[SPrefixedIdentifier] Found static field '$memberName' from mixin '${mixin.name}' for enum '${prefixValue.name}'",
);
return mixinStaticField;
} on RuntimeD4rtException {
// Continue to next check
}
// Check static getters
final mixinStaticGetter = mixin.findStaticGetter(memberName);
if (mixinStaticGetter != null) {
Logger.debug(
"[SPrefixedIdentifier] Found static getter '$memberName' from mixin '${mixin.name}' for enum '${prefixValue.name}'",
);
return mixinStaticGetter.call(this, [], {});
}
// Check static methods
final mixinStaticMethod = mixin.findStaticMethod(memberName);
if (mixinStaticMethod != null) {
Logger.debug(
"[SPrefixedIdentifier] Found static method '$memberName' from mixin '${mixin.name}' for enum '${prefixValue.name}'",
);
return mixinStaticMethod;
}
}
// Not found
throw RuntimeD4rtException(
"Undefined static member '$memberName' on enum '${prefixValue.name}'. Available enum values: ${prefixValue.valueNames.join(', ')}",
);
} else if (prefixValue is BridgedClass) {
final bridgedClass = prefixValue;
Logger.debug(
"[SPrefixedIdentifier] Static access on BridgedClass: ${bridgedClass.name}.$memberName",
);
final staticGetter = bridgedClass.findStaticGetterAdapter(memberName);
if (staticGetter != null) {
return wrapNativeReturnValue(staticGetter(this));
}
final staticMethod = bridgedClass.findStaticMethodAdapter(memberName);
if (staticMethod != null) {
// Return the static method as a callable value
Logger.debug(
"[SPrefixedIdentifier] Returning bridged static method '$memberName' as value from '${bridgedClass.name}'.",
);
return BridgedStaticMethodCallable(
bridgedClass,
staticMethod,
memberName,
);
}
// Constructor tear-off support for bridged classes:
// `EagerGestureRecognizer.new` → unnamed-ctor tear-off
// `Foo.fromMap` → named-ctor tear-off
// Mirrors the InterpretedClass branch above. Without this, scripts
// using Dart 2.15+ tear-off syntax against a bridged class fail with
// "Undefined static member 'new' on bridged class '...'".
if (memberName == 'new') {
final ctor = bridgedClass.findConstructorAdapter('');
if (ctor != null) {
Logger.debug(
"[SPrefixedIdentifier] Returning bridged unnamed-constructor tear-off '${bridgedClass.name}.new'.",
);
return _BridgedConstructorTearOff(bridgedClass, ctor, '');
}
}
final namedCtor = bridgedClass.findConstructorAdapter(memberName);
if (namedCtor != null) {
Logger.debug(
"[SPrefixedIdentifier] Returning bridged named-constructor tear-off '${bridgedClass.name}.$memberName'.",
);
return _BridgedConstructorTearOff(bridgedClass, namedCtor, memberName);
}
// Cluster C32: class-as-value (Type literal) semantics. A
// PrefixedIdentifier like `slotType.hashCode` where `slotType` is
// bound to a BridgedClass should dispatch
// `Object.hashCode`/`Object.runtimeType` on the Type instance, not
// look up a static member. Fall back before throwing.
if (memberName == 'hashCode') {
return bridgedClass.hashCode;
}
if (memberName == 'runtimeType') {
return bridgedClass.runtimeType;
}
throw RuntimeD4rtException(
"Undefined static member '$memberName' on bridged class '${bridgedClass.name}'.",
);
} else if (prefixValue is InterpretedExtension) {
// Handle static member access on extensions
final extension = prefixValue;
Logger.debug(
"[SPrefixedIdentifier] Static access on Extension: ${extension.name ?? '<unnamed>'}.$memberName",
);
// Check static field
if (extension.staticFields.containsKey(memberName)) {
return extension.staticFields[memberName];
}
// Check static getter
final staticGetter = extension.findStaticGetter(memberName);
if (staticGetter != null) {
return staticGetter.call(this, [], {});
}
// Check static method
final staticMethod = extension.findStaticMethod(memberName);
if (staticMethod != null) {
return staticMethod;
}
throw RuntimeD4rtException(
"Undefined static member '$memberName' on extension '${extension.name ?? '<unnamed>'}'.",
);
} else if (prefixValue is InterpretedInstance) {
try {
final member = prefixValue.get(memberName, visitor: this);
if (member is InterpretedFunction && member.isGetter) {
return member.bind(prefixValue).call(this, [], {});
} else {
return member;
}
} on RuntimeD4rtException catch (e) {
if (e.message.contains("Undefined property '$memberName'")) {
final extensionMember = environment.findExtensionMember(
prefixValue,
memberName,
);
if (extensionMember is ExtensionMemberCallable) {
if (extensionMember.isGetter) {
return extensionMember.call(this, [prefixValue], {});
} else if (!extensionMember.isOperator &&
!extensionMember.isSetter) {
return extensionMember;
}
}
}
throw RuntimeD4rtException(
"${e.message} (accessing property via SPrefixedIdentifier '$memberName')",
);
}
} else if (prefixValue is InterpretedEnumValue) {
if (memberName == 'index') {
Logger.debug(
"[SPrefixedIdentifier] Accessing getter 'index' on enum value '$prefixValue'.",
);
return prefixValue.index;
} else if (memberName == 'toString') {
Logger.debug(
"[SPrefixedIdentifier] Accessing method 'toString' on enum value '$prefixValue'. Returning callable.",
);
// Return directly the string for simplicity in prefixed access?
// No, return a callable function to be consistent with methods.
return NativeFunction(
(_, args, _, _) {
if (args.isNotEmpty) {
throw RuntimeD4rtException("toString() takes no arguments.");
}
return prefixValue.toString();
},
arity: 0,
name: 'toString',
);
} else if (memberName == 'name') {
Logger.debug(
"[SPrefixedIdentifier] Explicitly accessing 'name' on enum value '$prefixValue'. Returning value.",
);
return prefixValue.name; // Access directly the 'name' property
} else {
Logger.debug(
"[SPrefixedIdentifier] Accessing member '$memberName' on enum value '$prefixValue'. Calling get()...",
);
try {
// Pass 'this' (the visitor) to allow getter execution if needed.
return prefixValue.get(memberName, this);
} on ReturnException catch (e) {
// If get() executes a getter that throws ReturnException
return e.value;
} on RuntimeD4rtException catch (e) {
// G-DOV2-7 FIX: Try extension lookup if direct access fails
if (e.message.contains("Undefined property '$memberName'")) {
Logger.debug(
"[SPrefixedIdentifier] Direct access failed for '$memberName' on enum $prefixValue. Trying extension lookup...",
);
final extensionMember = environment.findExtensionMember(
prefixValue,
memberName,
);
if (extensionMember is ExtensionMemberCallable) {
if (extensionMember.isGetter) {
Logger.debug(
"[SPrefixedIdentifier] Found extension getter '$memberName' for enum. Calling...",
);
return extensionMember.call(this, [prefixValue], {});
} else if (!extensionMember.isOperator &&
!extensionMember.isSetter) {
Logger.debug(
"[SPrefixedIdentifier] Found extension method '$memberName' for enum. Returning tear-off.",
);
return extensionMember;
}
}
}
// Propagate error if extension lookup failed
throw RuntimeD4rtException(
"Error getting member '$memberName' from enum value '$prefixValue': ${e.message}",
);
} catch (e) {
// Propagate other errors from get()
throw RuntimeD4rtException(
"Error getting member '$memberName' from enum value '$prefixValue': $e",
);
}
}
} else if (toBridgedInstance(prefixValue).$2) {
final bridgedInstance = toBridgedInstance(prefixValue).$1!;
// GEN-075: Check universal Object properties first
switch (memberName) {
case 'hashCode':
return bridgedInstance.nativeObject.hashCode;
case 'runtimeType':
return bridgedInstance.nativeObject.runtimeType;
default:
}
final getterAdapter = bridgedInstance.bridgedClass
.findInstanceGetterAdapter(memberName);
if (getterAdapter != null) {
return getterAdapter(this, bridgedInstance.nativeObject);
}
final methodAdapter = bridgedInstance.bridgedClass
.findInstanceMethodAdapter(memberName);
if (methodAdapter != null) {
// C20a fix: prefixed identifier `prefix.method` is a method tear-off
// when not followed by an invocation. Return the callable rather than
// invoking it with empty arguments. The previous `.call(this, [], {})`
// crashed bridges that index `positionalArgs[0]` (e.g. Set.contains
// used as `_kAllStates.where(states.contains)`), surfacing as
// "Set.contains: Invalid value" RangeError.
return BridgedMethodCallable(
bridgedInstance,
methodAdapter,
memberName,
);
}
// 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, memberName);
if (supertypeMatch.$2) {
Logger.debug(
"[SPrefixedIdentifier] Resolved '$memberName' via supertype walk on '${bridgedInstance.bridgedClass.name}'.",
);
return supertypeMatch.$1;
}
// No adapter found, try extension methods/getters
try {
final extensionMember = environment.findExtensionMember(
bridgedInstance,
memberName,
);
if (extensionMember is ExtensionMemberCallable) {
if (extensionMember.isGetter) {
Logger.debug(
"[SPrefixedIdentifier] Found extension getter '$memberName' for ${bridgedInstance.bridgedClass.name}. Calling...",
);
final extensionArgs = <Object?>[bridgedInstance];
return extensionMember.call(this, extensionArgs, {});
} else if (!extensionMember.isOperator && !extensionMember.isSetter) {
Logger.debug(
"[SPrefixedIdentifier] Found extension method '$memberName' for ${bridgedInstance.bridgedClass.name}. Returning callable.",
);
return BoundExtensionCallable(bridgedInstance, extensionMember);
}
}
} on RuntimeD4rtException catch (findError) {
Logger.debug(
"[SPrefixedIdentifier] Error finding extension '$memberName' for ${bridgedInstance.bridgedClass.name}: ${findError.message}",
);
}
// RC-5: Enum property fallback — same as visitSPropertyAccess Fix I
if (bridgedInstance.nativeObject is Enum) {
final enumObj = bridgedInstance.nativeObject as Enum;
switch (memberName) {
case 'name':
return enumObj.name;
case 'index':
return enumObj.index;
case 'hashCode':
return enumObj.hashCode;
case 'runtimeType':
return enumObj.runtimeType;
case 'toString':
return NativeFunction(
(visitor, args, namedArgs, typeArgs) => enumObj.toString(),
arity: 0,
name: 'toString',
);
}
// Cluster-26 (Key.label dispatch): If a custom getter was registered
// on the BridgedEnumDefinition (e.g. KeyEventType.label), dispatch
// through the BridgedEnumValue. Only entered for unknown properties
// to keep built-in enum access on the fast path.
// Walks the current scope chain (which extends from the per-module
// env where bridges register the BridgedEnum). Falls back to the
// global env for runners that pre-populate enums there.
final bridgedEnumValue =
environment.getBridgedEnumValue(enumObj) ??
globalEnvironment.getBridgedEnumValue(enumObj);
if (bridgedEnumValue != null) {
try {
return bridgedEnumValue.get(memberName);
} on RuntimeD4rtException {
// Fall through to the "Undefined property" error below.
}
}
}
// D2: D4InterpretedProxy unwrap fallback (see visitSPropertyAccess).
final nativeForUnwrap = bridgedInstance.nativeObject;
if (nativeForUnwrap is D4InterpretedProxy) {
final inner = nativeForUnwrap.d4rtInstance;
if (inner is InterpretedInstance) {
try {
return inner.get(memberName, visitor: this);
} catch (_) {
// Fall through to the "Undefined property" error below.
}
}
}
throw RuntimeD4rtException(
"Undefined property or method '$memberName' on bridged instance of '${bridgedInstance.bridgedClass.name}'.",
);
} else if (prefixValue is InterpretedRecord) {
// Accessing field of a record
final record = prefixValue;
Logger.debug(
"[SPrefixedIdentifier] Access on InterpretedRecord: .$memberName",
);
// Check for Object methods (hashCode, runtimeType, toString)
switch (memberName) {
case 'hashCode':
return record.hashCode;
case 'runtimeType':
return record.runtimeType;
}
// Check if it's a positional field access ($1, $2, ...)
if (memberName.startsWith('\$') && memberName.length > 1) {
try {
final index = int.parse(memberName.substring(1)) - 1;
if (index >= 0 && index < record.positionalFields.length) {
return record.positionalFields[index];
} else {
throw RuntimeD4rtException(
"Record positional field index \$$index out of bounds (0..${record.positionalFields.length - 1}).",
);
}
} catch (e) {
// Handle parse errors or other issues
throw RuntimeD4rtException(
"Invalid positional record field accessor '$memberName'.",
);
}
} else {
// Check if it's a named field access
if (record.namedFields.containsKey(memberName)) {
return record.namedFields[memberName];
} else {
throw RuntimeD4rtException(
"Record has no field named '$memberName'. Available fields: ${record.namedFields.keys.join(', ')}",
);
}
}
} else if (prefixValue is Record) {
// E6: native Dart Record (e.g., produced by `Iterable.indexed` from
// dart:core). Records do not implement reflection without
// `dart:mirrors`, so we route positional access via `dynamic`
// dispatch ($1..$9) and Object members directly.
return _accessNativeRecordField(prefixValue, memberName);
} else if (prefixValue is InterpretedExtensionTypeInstance) {
// Lim-1 FIX: Handle property access on extension type instances
final extensionInstance = prefixValue;
Logger.debug(
"[SPrefixedIdentifier] Access on InterpretedExtensionTypeInstance: .$memberName",
);
// Check for Object methods first (hashCode, runtimeType, toString)
switch (memberName) {
case 'hashCode':
return extensionInstance.representationValue.hashCode;
case 'runtimeType':
return extensionInstance.extensionType;
}
// Delegate to the extension type instance's get() method
try {
return extensionInstance.get(memberName, this);
} on ReturnException catch (e) {
// If get() executes a getter that throws ReturnException
return e.value;
}
} else if (prefixValue is BridgedEnum) {
Logger.debug(
"[SPrefixedIdentifier] Accessing value on BridgedEnum: ${prefixValue.name}.$memberName",
);
final enumValue = prefixValue.getValue(memberName);
if (enumValue != null) {
return enumValue; // Return the BridgedEnumValue
} else {
throw RuntimeD4rtException(
"Undefined enum value '$memberName' on bridged enum '${prefixValue.name}'.",
);
}
} else if (prefixValue is BridgedEnumValue) {
final bridgedEnumValue = prefixValue;
Logger.debug(
"[SPrefixedIdentifier] Accessing property '$memberName' on BridgedEnumValue (within InterpretedEnumValue block): $bridgedEnumValue",
);
try {
// Use the get() method of BridgedEnumValue
return bridgedEnumValue.get(memberName);
} on ReturnException catch (e) {
return e.value;
} on RuntimeD4rtException {
// Relaunch the RuntimeErrors directly
rethrow;
} catch (e, s) {
// Catch other potential errors (ex: from the adapter)
Logger.error(
"[SPrefixedIdentifier] Native exception during bridged enum property get '$bridgedEnumValue.$memberName': $e\n$s",
);
throw RuntimeD4rtException(
"Native error during bridged enum property get '$memberName' on $bridgedEnumValue: $e",
originalException: e,
);
}
} else if (prefixValue is Callable) {
// Handle property access on function types (InterpretedFunction, NativeFunction, etc.)
Logger.debug("[SPrefixedIdentifier] Access on Callable: .$memberName");
switch (memberName) {
case 'hashCode':
return prefixValue.hashCode;
case 'runtimeType':
// Return Function as the runtimeType for all callable types
return Function;
case 'toString':
return NativeFunction(
(_, args, _, _) {
if (args.isNotEmpty) {
throw RuntimeD4rtException("toString() takes no arguments.");
}
return prefixValue.toString();
},
arity: 0,
name: 'toString',
);
}
throw RuntimeD4rtException(
"Cannot access property '$memberName' on function. Functions only support 'hashCode', 'runtimeType', and 'toString'.",
);
} else {
try {
final extensionMember = environment.findExtensionMember(
prefixValue,
memberName,
);
if (extensionMember is ExtensionMemberCallable) {
// Handle extension getter call immediately
if (extensionMember.isGetter) {
Logger.debug(
"[SPrefixedIdentifier] Found extension getter '$memberName' (fallback). Calling...",
);
final extensionPositionalArgs = [
prefixValue,
]; // Getter takes receiver
return extensionMember.call(this, extensionPositionalArgs, {});
} else if (!extensionMember.isOperator && !extensionMember.isSetter) {
// Return extension method itself if it's not a getter/setter/operator
Logger.debug(
"[SPrefixedIdentifier] Found extension method '$memberName' (fallback). Returning callable.",
);
return extensionMember;
}
// If it's an operator or setter, we probably shouldn't reach here via SPrefixedIdentifier
// Fall through to Stdlib if it wasn't a getter or regular method.
}
// If no suitable extension found, proceed to Stdlib call
Logger.debug(
"[SPrefixedIdentifier] No suitable extension found for '$memberName' (fallback). Trying Stdlib...",
);
} on RuntimeD4rtException catch (findError) {
// If findExtensionMember itself threw (e.g., type not found), proceed to Stdlib
Logger.debug(
"[SPrefixedIdentifier] Error finding extension '$memberName' (fallback): ${findError.message}. Trying Stdlib...",
);
}
// Fix I: Generic Enum property getter — handles raw native enums
// not wrapped in BridgedEnumValue (e.g., returned by bridge getters).
// All Dart enums implement the Enum interface with .name and .index.
if (prefixValue is Enum) {
switch (memberName) {
case 'name':
return (prefixValue).name;
case 'index':
return (prefixValue).index;
case 'hashCode':
return prefixValue.hashCode;
case 'runtimeType':
return prefixValue.runtimeType;
case 'toString':
return NativeFunction(
(_, args, _, _) => prefixValue.toString(),
arity: 0,
name: 'toString',
);
}
}
// GEN-C3c: Universal Object-member fallback for arbitrary native
// targets reached via PrefixedIdentifier. Every Dart Object exposes
// `toString`, `hashCode`, and `runtimeType`; these must always
// resolve. Mirrors the BridgedInstance branch (GEN-075) above and
// the same fallback in visitPropertyAccess.
if (prefixValue != null) {
switch (memberName) {
case 'hashCode':
return prefixValue.hashCode;
case 'runtimeType':
return prefixValue.runtimeType;
case 'toString':
return NativeFunction(
(_, args, _, _) => prefixValue.toString(),
arity: 0,
name: 'toString',
);
}
}
throw RuntimeD4rtException(
"Cannot access property '$memberName' on target of type ${prefixValue?.runtimeType}.",
);
}
}