visitExtensionDeclaration method

  1. @override
Object? visitExtensionDeclaration(
  1. SExtensionDeclaration node
)
override

Implementation

@override
Object? visitExtensionDeclaration(SExtensionDeclaration node) {
  final extensionName = node.name?.name;
  Logger.debug(
    "[visitExtensionDeclaration] Declaring extension: ${extensionName ?? '<unnamed>'}",
  );

  // 1. Resolve the 'on' type
  final onTypeNode = node.extendedType;
  if (onTypeNode == null) {
    // This might happen for extension types, which are different.
    // For now, assume classic extensions always have an 'on' clause.
    Logger.warn(
      "[visitExtensionDeclaration] Extension '${extensionName ?? '<unnamed>'}' has no 'on' clause, skipping.",
    );
    return null;
  }

  // Resolve the type name from the AST node
  String onTypeName;
  bool isOnNullableType = false; // G-DOV-10/11: Track nullable on-type
  if (onTypeNode is SNamedType) {
    onTypeName = onTypeNode.name!.name;
    isOnNullableType = onTypeNode.isNullable; // Check for 'T?' syntax
  } else {
    Logger.warn(
      "[visitExtensionDeclaration] Unsupported 'on' type node for resolution: ${onTypeNode.runtimeType}. Skipping extension.",
    );
    return null;
  }

  // Look up the RuntimeType in the environment
  RuntimeType onRuntimeType;
  try {
    final typeValue = environment.get(onTypeName);

    // Handle Resolution of Native/Bridged Types
    if (typeValue is RuntimeType) {
      // Standard case: Found an InterpretedClass/Mixin or existing BridgedClass
      onRuntimeType = typeValue;
      Logger.debug(
        "[visitExtensionDeclaration] Resolved 'on' type '$onTypeName' to RuntimeType: ${onRuntimeType.name}",
      );
    } else if (typeValue is NativeFunction) {
      // Heuristic: If environment.get returns a NativeFunction for a common type name,
      // assume it represents the native type and find/create a corresponding BridgedClass.
      // This relies on the global environment being populated correctly with type bridges.
      BridgedClass? bridgedType = _getBridgedClassForNativeType(onTypeName);
      if (bridgedType != null) {
        onRuntimeType = bridgedType;
        Logger.debug(
          "[visitExtensionDeclaration] Resolved native 'on' type '$onTypeName' to BridgedClass: ${onRuntimeType.name}",
        );
      } else {
        // We found something, but couldn't map it to a known bridged type
        throw RuntimeD4rtException(
          "Symbol '$onTypeName' resolved to NativeFunction but could not map to a known BridgedClass.",
        );
      }
    } else {
      // Resolved to something unexpected (e.g., an instance, null, etc.)
      throw RuntimeD4rtException(
        "Symbol '$onTypeName' resolved to non-type: ${typeValue?.runtimeType}",
      );
    }
  } on RuntimeD4rtException catch (e) {
    // Check if the error is specifically "Undefined variable"which means the type wasn't found at all.
    if (e.message.contains("Undefined variable: $onTypeName")) {
      // Special handling for core types that might not be explicitly defined if stdlib wasn't fully loaded?
      // Or maybe they are always NativeFunctions?
      BridgedClass? coreBridgedType = _getBridgedClassForNativeType(
        onTypeName,
      );
      if (coreBridgedType != null) {
        onRuntimeType = coreBridgedType;
        Logger.debug(
          "[visitExtensionDeclaration] Resolved unfound core 'on' type '$onTypeName' to BridgedClass: ${onRuntimeType.name}",
        );
      } else {
        // Type genuinely not found or not a recognized core type
        throw RuntimeD4rtException(
          "Could not resolve 'on' type '$onTypeName' for extension '${extensionName ?? '<unnamed>'}': Type not found or not a recognized core type.",
        );
      }
    } else {
      // Propagate other RuntimeErrors (like the non-type error from above)
      throw RuntimeD4rtException(
        "Could not resolve 'on' type '$onTypeName' for extension '${extensionName ?? '<unnamed>'}': ${e.message}",
      );
    }
  }

  // 2. Process members (methods, getters, setters, operators) - both instance and static
  final members = <String, Callable>{};
  final staticMethods = <String, Callable>{};
  final staticGetters = <String, Callable>{};
  final staticSetters = <String, Callable>{};
  final staticFields = <String, Object?>{};

  for (final member in node.members) {
    if (member is SMethodDeclaration) {
      final methodName =
          member.name!.name; // Operator names like '+', '[]' are also lexemes

      if (member.isStatic) {
        // Handle static methods, getters, and setters
        final function = InterpretedFunction.method(
          member,
          environment,
          null,
        );

        if (member.isGetter) {
          staticGetters[methodName] = function;
          Logger.debug(
            "[visitExtensionDeclaration]   Added static getter: $methodName",
          );
        } else if (member.isSetter) {
          staticSetters[methodName] = function;
          Logger.debug(
            "[visitExtensionDeclaration]   Added static setter: $methodName",
          );
        } else {
          staticMethods[methodName] = function;
          Logger.debug(
            "[visitExtensionDeclaration]   Added static method: $methodName",
          );
        }
      } else {
        // Create InterpretedExtensionMethod for instance method-like declarations
        final function = InterpretedExtensionMethod(
          member,
          environment,
          onRuntimeType,
        );
        members[methodName] = function;
        String memberType = "method";
        if (member.isGetter) memberType = "getter";
        if (member.isSetter) memberType = "setter";
        if (member.isOperator) memberType = "operator";
        Logger.debug(
          "[visitExtensionDeclaration]   Added extension $memberType: $methodName",
        );
      }
    } else if (member is SFieldDeclaration) {
      // Only static fields are allowed in extensions.
      if (!member.isStatic) {
        Logger.warn(
          "[visitExtensionDeclaration] Instance fields are not allowed in extensions. Skipping field '$member'.",
        );
        continue; // Skip instance fields
      }
      // Handle static fields - store in the InterpretedExtension object
      for (final variable in member.fields!.variables) {
        final fieldName = variable.name!.name;
        Object? value;
        if (variable.initializer != null) {
          // Evaluate static initializer immediately in the current environment
          try {
            value = variable.initializer!.accept<Object?>(this);
          } catch (e) {
            throw RuntimeD4rtException(
              "Error evaluating static initializer for extension field '$fieldName': $e",
            );
          }
        }
        // Store in staticFields map instead of environment
        staticFields[fieldName] = value;
        Logger.debug(
          "[visitExtensionDeclaration]   Stored static field: $fieldName",
        );
      }
    } else {
      Logger.warn(
        "[visitExtensionDeclaration] Unsupported extension member type: ${member.runtimeType}. Skipping.",
      );
    }
  }

  // 3. Create and store the InterpretedExtension
  final interpretedExtension = InterpretedExtension(
    name: extensionName,
    onType: onRuntimeType,
    isOnNullableType: isOnNullableType, // G-DOV-10/11: Pass nullable flag
    members: members,
    staticMethods: staticMethods,
    staticGetters: staticGetters,
    staticSetters: staticSetters,
    staticFields: staticFields,
  );

  // How to store it? In the environment associated with its name?
  // Or in a separate list in the environment?
  // Let's try defining it by name if it has one, otherwise maybe a special list.
  if (extensionName != null) {
    environment.define(extensionName, interpretedExtension);
    Logger.debug(
      "[visitExtensionDeclaration] Defined named extension '$extensionName' in environment.",
    );
  } else {
    // Store unnamed extensions in a special list in the environment
    environment.addUnnamedExtension(interpretedExtension);
    Logger.debug(
      "[visitExtensionDeclaration] Added unnamed extension to environment list.",
    );
  }

  return null; // Declarations typically return null
}