visitPropertyAccess method

  1. @override
Object? visitPropertyAccess(
  1. 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}.");
    }
  }
}