visitPropertyAccess method
Object?
visitPropertyAccess(
- PropertyAccess node
)
override
Implementation
@override
Object? visitPropertyAccess(PropertyAccess node) {
final target = node.target?.accept<Object?>(this);
if (target is AsyncSuspensionRequest) {
// Propagate suspension so the state machine resumes this node after resolution
return target;
}
// Determine if this is a conditional access by inspecting the source
final isNullAware = node.toSource().contains('?.');
final propertyName = node.propertyName.name;
// Null safety support: if the target is null and the access is null-aware, return null
if (target == null) {
if (isNullAware) {
return null;
}
// G-DOV-10/11 FIX: Try extension lookup on nullable types before throwing
final extensionMember =
environment.findExtensionMember(target, propertyName, visitor: this);
if (extensionMember != null) {
if (extensionMember is InterpretedFunction &&
extensionMember.isGetter) {
// Execute extension getter with 'this' bound to null
final extensionEnv = Environment(enclosing: environment);
extensionEnv.define('this', null);
final prevEnv = environment;
environment = extensionEnv;
try {
return extensionMember.call(this, [], {});
} finally {
environment = prevEnv;
}
}
return extensionMember;
}
throw RuntimeD4rtException(
"Cannot access property '$propertyName' on null. Use '?.' for null-aware access.");
}
Logger.debug(
"[PropertyAccess: ${node.toSource()}] Target type: ${target.runtimeType}, Target value: ${target.toString()}");
if (target is InterpretedInstance) {
// Standard Instance Access: Try direct first, then extension
try {
final member = target.get(propertyName,
visitor: this); // .get() handles inheritance
if (member is InterpretedFunction && member.isGetter) {
return member
.call(this, [], {}); // .get already returned bound getter
} else {
return member; // field value or bound method
}
} on RuntimeD4rtException catch (e) {
// Try Extension Lookup Before Error
if (e.message.contains("Undefined property '$propertyName'")) {
Logger.debug(
"[PropertyAccess] Direct access failed for '$propertyName'. Trying extension lookup on ${target.runtimeType}.");
try {
final extensionMember =
environment.findExtensionMember(target, propertyName);
if (extensionMember is ExtensionMemberCallable) {
if (extensionMember.isGetter) {
Logger.debug(
"[PropertyAccess] Found extension getter '$propertyName'. Calling...");
// Getters are called with the instance as the first (and only) positional argument
final extensionPositionalArgs = [target];
return extensionMember.call(this, extensionPositionalArgs, {});
} else if (!extensionMember.isOperator &&
!extensionMember.isSetter) {
// Return the extension method itself (it's not bound yet)
Logger.debug(
"[PropertyAccess] Found extension method '$propertyName'. Returning callable.");
return extensionMember;
}
}
// No suitable extension found, fall through to rethrow original error
Logger.debug(
"[PropertyAccess] No suitable extension member found for '$propertyName'.");
} on RuntimeD4rtException catch (findError) {
// Error during extension lookup itself
Logger.debug(
"[PropertyAccess] Error during extension lookup for '$propertyName': ${findError.message}");
// Fall through to rethrow original error
}
}
// Rethrow original error if it wasn't "Undefined property"or if extension lookup failed
throw RuntimeD4rtException(
"${e.message} (accessing property via PropertyAccess '$propertyName')");
}
} else if (target is InterpretedEnumValue) {
try {
// Get should execute the getter or return the field/bound method
// Pass the visitor to potentially execute getters
final member = target.get(propertyName, this);
// Check if the result from get was already the final value (field or executed getter)
// or if it's a bound method that shouldn't be called here.
if (member is Callable) {
// Property access should generally not return a raw callable method.
// If get() returned a bound method, it means the propertyName matched a method name,
// which is not what property access typically expects.
// However, Dart allows accessing methods like properties to get a tear-off.
// So, we return the bound method (Callable) here.
Logger.debug(
"[PropertyAccess] Accessed enum method '$propertyName' on $target as tear-off. Returning bound method.");
return member;
} else {
// Must be a field value or the result of an executed getter.
Logger.debug(
"[PropertyAccess] Accessed enum field/getter '$propertyName' on $target. Value: $member");
return member;
}
} on RuntimeD4rtException catch (e) {
// Try Extension Getter if Direct Fails (similar to InterpretedInstance)
if (e.message.contains("Undefined property '$propertyName'")) {
Logger.debug(
"[PropertyAccess] Direct access failed for '$propertyName' on enum $target. Trying extension lookup...");
try {
final extensionMember =
environment.findExtensionMember(target, propertyName);
if (extensionMember is ExtensionMemberCallable) {
if (extensionMember.isGetter) {
Logger.debug(
"[PropertyAccess] Found extension getter '$propertyName' for enum. Calling...");
final extensionPositionalArgs = [target];
return extensionMember.call(this, extensionPositionalArgs, {});
} else if (!extensionMember.isOperator &&
!extensionMember.isSetter) {
Logger.debug(
"[PropertyAccess] Found extension method '$propertyName' for enum. Returning tear-off.");
return extensionMember;
}
}
Logger.debug(
"[PropertyAccess] No suitable extension member found for '$propertyName' on enum.");
} on RuntimeD4rtException catch (findError) {
Logger.debug(
"[PropertyAccess] Error during extension lookup for '$propertyName' on enum: ${findError.message}");
}
}
// Rethrow original error or error from extension lookup
throw RuntimeD4rtException(
"${e.message} (accessing property via PropertyAccess '$propertyName' on enum value '$target')");
}
} else if (target is InterpretedEnum) {
// Accessing static member on the enum itself
InterpretedFunction? staticGetter = target.staticGetters[propertyName];
if (staticGetter != null) {
// Call the static getter
return staticGetter.call(this, [], {});
}
Object? staticField = target.staticFields[propertyName];
if (target.staticFields.containsKey(propertyName)) {
// Return static field value (could be null)
return staticField;
}
InterpretedFunction? staticMethod = target.staticMethods[propertyName];
if (staticMethod != null) {
// Return the static method itself (tear-off)
return staticMethod;
}
// Check mixins for static members (reverse order)
for (final mixin in target.mixins.reversed) {
final mixinStaticGetter = mixin.findStaticGetter(propertyName);
if (mixinStaticGetter != null) {
Logger.debug(
"[PropertyAccess] Found static getter '$propertyName' from mixin '${mixin.name}' for enum '${target.name}'");
return mixinStaticGetter.call(this, [], {});
}
final mixinStaticMethod = mixin.findStaticMethod(propertyName);
if (mixinStaticMethod != null) {
Logger.debug(
"[PropertyAccess] Found static method '$propertyName' from mixin '${mixin.name}' for enum '${target.name}'");
return mixinStaticMethod;
}
// Check static fields - use try/catch since getStaticField throws if not found
try {
final mixinStaticField = mixin.getStaticField(propertyName);
Logger.debug(
"[PropertyAccess] Found static field '$propertyName' from mixin '${mixin.name}' for enum '${target.name}'");
return mixinStaticField;
} on RuntimeD4rtException {
// Continue to next mixin
}
}
// Check for built-in 'values'
if (propertyName == 'values') {
return target.valuesList;
}
// Not found
throw RuntimeD4rtException(
"Undefined static property '$propertyName' on enum '${target.name}'.");
} else if (target is InterpretedClass) {
// Static Access (no change)
try {
// Check static fields first (no inheritance for static fields in Dart)
return target.getStaticField(propertyName);
} on RuntimeD4rtException catch (_) {
// If not a field, check static methods/getters
InterpretedFunction? staticMember =
target.findStaticGetter(propertyName);
staticMember ??= target.findStaticMethod(propertyName);
if (staticMember != null) {
if (staticMember.isGetter) {
return staticMember.call(this, [], {}); // Call static getter
} else {
// Return static method function itself (not bound)
return staticMember;
}
} else {
throw RuntimeD4rtException(
"Undefined static member '$propertyName' on class '${target.name}'.");
}
}
} else if (target is BoundSuper) {
// Super Property Access
final instance = target.instance;
final startClass = target.startLookupClass;
InterpretedClass? currentClass =
startClass; // Start search from superclass
while (currentClass != null) {
// Check instance field in the bound instance's fields
// Use the new public getter on the instance to access the field value
try {
final fieldValue = instance.getField(propertyName);
// Field found on the instance, return its value regardless of where we are in the super hierarchy lookup
return fieldValue;
} on RuntimeD4rtException {
// Field doesn't exist directly on the instance, continue searching for getters/methods
}
// Check instance getter in the current class in hierarchy
final getter = currentClass.findInstanceGetter(propertyName);
if (getter != null) {
// Bind getter to the original instance and call
return getter.bind(instance).call(this, [], {});
}
// Check instance method (less common for property access, but possible)
final method = currentClass.findInstanceMethod(propertyName);
if (method != null) {
// Bind method to the original instance and return the bound method
return method.bind(instance);
}
// Move up the hierarchy
currentClass = currentClass.superclass;
}
// Not found in superclass hierarchy
throw RuntimeD4rtException(
"Undefined property '$propertyName' accessed via 'super' on instance of '${instance.klass.name}'.");
} else if (target is BridgedClass) {
final bridgedClass = target;
Logger.debug(
"[PropertyAccess] Static access on BridgedClass: ${bridgedClass.name}.$propertyName");
final staticGetter = bridgedClass.findStaticGetterAdapter(propertyName);
if (staticGetter != null) {
Logger.debug("[PropertyAccess] Found static getter adapter.");
return wrapNativeReturnValue(staticGetter(this));
}
final staticMethod = bridgedClass.findStaticMethodAdapter(propertyName);
if (staticMethod != null) {
Logger.debug("[PropertyAccess] Found static method adapter.");
throw UnimplementedD4rtException(
"Returning bridged static methods as values from PropertyAccess is not yet supported.");
// return BridgedStaticMethodCallable(bridgedClass, staticMethod, propertyName);
} else {
throw RuntimeD4rtException(
"Undefined static member '$propertyName' on bridged class '${bridgedClass.name}'.");
}
} else if (toBridgedInstance(target).$2) {
final bridgedInstance = toBridgedInstance(target).$1!;
Logger.debug(
"[PropertyAccess] Access on BridgedInstance: ${bridgedInstance.bridgedClass.name}.$propertyName");
switch (propertyName) {
case 'runtimeType':
return target.runtimeType;
case 'hashCode':
return target.hashCode;
default:
}
final getterAdapter =
bridgedInstance.bridgedClass.findInstanceGetterAdapter(propertyName);
if (getterAdapter != null) {
Logger.debug("[PropertyAccess] Found instance getter adapter.");
return getterAdapter(
this, bridgedInstance.nativeObject); // Call instance getter adapter
}
final methodAdapter =
bridgedInstance.bridgedClass.findInstanceMethodAdapter(propertyName);
if (methodAdapter != null) {
Logger.debug(
"[PropertyAccess] Found instance method adapter. Binding...");
// Return a callable bound to the instance
return BridgedMethodCallable(
bridgedInstance, methodAdapter, propertyName);
}
// Try extension lookup before throwing error
Logger.debug(
"[PropertyAccess] Direct access failed for '$propertyName' on BridgedInstance. Trying extension lookup...");
final extensionMember =
environment.findExtensionMember(bridgedInstance, propertyName);
if (extensionMember is ExtensionMemberCallable) {
if (extensionMember.isGetter) {
Logger.debug(
"[PropertyAccess] Found extension getter '$propertyName' for BridgedInstance. Calling...");
// Getters are called with the native object as the first positional argument
final extensionPositionalArgs = [bridgedInstance.nativeObject];
return extensionMember.call(this, extensionPositionalArgs, {});
} else if (!extensionMember.isOperator && !extensionMember.isSetter) {
Logger.debug(
"[PropertyAccess] Found extension method '$propertyName' for BridgedInstance. Returning tear-off.");
return extensionMember;
}
}
// Fix I: Check if the nativeObject is actually an Enum
if (bridgedInstance.nativeObject is Enum) {
final enumObj = bridgedInstance.nativeObject as Enum;
switch (propertyName) {
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',
);
}
}
throw RuntimeD4rtException(
"Undefined property or method '$propertyName' on bridged instance of '${bridgedInstance.bridgedClass.name}'.");
} else if (target is InterpretedRecord) {
// Accessing field of a record
final record = target;
Logger.debug(
"[PropertyAccess] Access on InterpretedRecord: .$propertyName");
// Check if it's a positional field access (\$1, \$2, ...)
if (propertyName.startsWith('\$') && propertyName.length > 1) {
try {
final index = int.parse(propertyName.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 '$propertyName'.");
}
} else {
// Check if it's a named field access
if (record.namedFields.containsKey(propertyName)) {
return record.namedFields[propertyName];
} else {
throw RuntimeD4rtException(
"Record has no field named '$propertyName'. Available fields: ${record.namedFields.keys.join(', ')}");
}
}
} else if (target is BridgedEnumValue) {
return target.get(propertyName);
} else if (target is BridgedEnum) {
Logger.debug(
"[PropertyAccess] Accessing value on BridgedEnum: ${target.name}.$propertyName");
final enumValue = target.getValue(propertyName);
if (enumValue != null) {
return enumValue; // Return the BridgedEnumValue
} else {
throw RuntimeD4rtException(
"Undefined enum value '$propertyName' on bridged enum '${target.name}'.");
}
} else if (target is BoundBridgedSuper) {
final instance = target.instance; // The interpreted 'this' instance
final bridgedSuper = target.startLookupClass;
final nativeSuperObject =
instance.bridgedSuperObject; // Retrieve the native object
if (nativeSuperObject == null) {
throw RuntimeD4rtException(
"Internal error: Cannot access super property '$propertyName' on bridged superclass '${bridgedSuper.name}' because the native super object is missing.");
}
// Try the bridged getter
final getterAdapter =
bridgedSuper.findInstanceGetterAdapter(propertyName);
if (getterAdapter != null) {
try {
return getterAdapter(this, nativeSuperObject);
} catch (e, s) {
Logger.error(
"Native exception during super access to bridged getter '${bridgedSuper.name}.$propertyName': $e\n$s");
throw RuntimeD4rtException(
"Native error during super access to bridged getter '$propertyName': $e");
}
}
// Try the bridged method (for tear-off)
final methodAdapter =
bridgedSuper.findInstanceMethodAdapter(propertyName);
if (methodAdapter != null) {
// Return a callable bound to the native object
return BridgedSuperMethodCallable(
nativeSuperObject, methodAdapter, propertyName, bridgedSuper.name);
}
// Not found
throw RuntimeD4rtException(
"Undefined property or method '$propertyName' accessed via 'super' on bridged superclass '${bridgedSuper.name}'.");
} else if (target is Callable) {
// ENG-006: Handle property access on function objects (InterpretedFunction, NativeFunction, etc.)
// Functions support runtimeType, hashCode, toString, and call tear-off.
Logger.debug(
"[PropertyAccess] Access on Callable: ${target.runtimeType}.$propertyName");
switch (propertyName) {
case 'runtimeType':
return target.runtimeType;
case 'hashCode':
return target.hashCode;
case 'toString':
// Return a bound toString method
return NativeFunction(
(visitor, args, namedArgs, typeArgs) => target.toString(),
arity: 0,
name: 'toString',
);
case 'call':
// Tear-off: return the callable itself
return target;
default:
throw RuntimeD4rtException(
"Undefined property '$propertyName' on function object (${target.runtimeType}).");
}
} else {
// Check if target is a native enum that has been bridged
final bridgedEnumValue = environment.getBridgedEnumValue(target);
if (bridgedEnumValue != null) {
Logger.debug(
"[PropertyAccess] Found bridged enum value for native enum ${target.runtimeType}");
try {
return bridgedEnumValue.get(propertyName);
} catch (e) {
throw RuntimeD4rtException(
"Undefined property '$propertyName' on bridged enum value '${bridgedEnumValue.name}'.");
}
}
// Fix I: Generic Enum property access for raw Enum targets
if (target is Enum) {
switch (propertyName) {
case 'name':
return (target as Enum).name;
case 'index':
return (target as Enum).index;
case 'hashCode':
return target.hashCode;
case 'runtimeType':
return target.runtimeType;
case 'toString':
return NativeFunction(
(visitor, args, namedArgs, typeArgs) => target.toString(),
arity: 0,
name: 'toString',
);
}
}
Logger.debug(
"[PropertyAccess] Looking for extension getter '$propertyName' for target type ${target.runtimeType}.");
final extensionCallable =
environment.findExtensionMember(target, propertyName);
if (extensionCallable is ExtensionMemberCallable &&
extensionCallable.isGetter) {
Logger.debug(
"[PropertyAccess] Found extension getter '$propertyName'. Calling...");
// Prepend the target instance to the positional arguments for the extension call
final extensionPositionalArgs = [
target
]; // Getters take no explicit args
try {
// Call the extension getter
return extensionCallable.call(this, extensionPositionalArgs, {});
} on ReturnException catch (e) {
return e.value;
} catch (e) {
throw RuntimeD4rtException(
"Error executing extension getter '$propertyName': $e");
}
} else {
// No extension getter found either, rethrow the original stdlib error
Logger.debug(
"[PropertyAccess] Extension getter '$propertyName' not found. Rethrowing original error.");
throw RuntimeD4rtException(
"Undefined property or method '$propertyName' on ${target.runtimeType}.");
}
}
}