visitEnumDeclaration method

  1. @override
Object? visitEnumDeclaration(
  1. SEnumDeclaration node
)
override

Visit a SEnumDeclaration.

Implementation

@override
Object? visitEnumDeclaration(SEnumDeclaration node) {
  final enumName = node.name!.name;
  Logger.debug(
    "[Visitor.visitEnumDeclaration] START (Pass 2) for '$enumName'",
  );

  // Retrieve the enum placeholder object created in Pass 1
  final enumObj = environment.get(enumName);
  if (enumObj == null || enumObj is! InterpretedEnum) {
    throw StateD4rtException(
      "Enum placeholder object for '$enumName' not found or invalid during Pass 2.",
    );
  }

  // Process Mixin Application (similar to class mixin handling)
  if (node.withClause != null) {
    Logger.debug(
      "[Visitor.visitEnumDeclaration] Processing 'with' clause for '$enumName'",
    );
    for (final mixinType in node.withClause!.mixinTypes) {
      final mixinName = mixinType.name!.name;
      Logger.debug(
        "[Visitor.visitEnumDeclaration]   Trying to get mixin '$mixinName'",
      );

      Object? mixin;
      try {
        mixin = environment.get(mixinName);
      } on RuntimeD4rtException {
        throw RuntimeD4rtException(
          "Mixin '$mixinName' not found during lookup for enum '$enumName'. Ensure it's defined (as a mixin or class mixin).",
        );
      }

      if (mixin is InterpretedClass) {
        if (!mixin.isMixin) {
          throw RuntimeD4rtException(
            "Class '$mixinName' cannot be used as a mixin because it's not declared with 'mixin' or 'class mixin'.",
          );
        }

        // Add to the mixins list of the enum object
        enumObj.mixins.add(mixin);
        Logger.debug(
          "[Visitor.visitEnumDeclaration] Applied interpreted mixin '$mixinName' to '$enumName'",
        );
      } else if (mixin is BridgedClass) {
        // Support for bridged classes as mixins
        if (!mixin.canBeUsedAsMixin) {
          throw RuntimeD4rtException(
            "Bridged class '$mixinName' cannot be used as a mixin. Set canBeUsedAsMixin=true when registering the bridge.",
          );
        }

        // Add to the bridged mixins list
        enumObj.bridgedMixins.add(mixin);
        Logger.debug(
          "[Visitor.visitEnumDeclaration] Applied bridged mixin '$mixinName' to '$enumName'",
        );
      } else {
        throw RuntimeD4rtException(
          "Identifier '$mixinName' resolved to ${mixin?.runtimeType}, which is not a class/mixin, for enum '$enumName'.",
        );
      }
    }
  }

  // Process Members (Static and Instance)
  // Members defined in the enum body (methods, getters, fields, constructors)
  final originalVisitorEnv = environment; // Save original environment
  try {
    // Members are defined in the enum's declaration scope
    environment = enumObj.declarationEnvironment;
    for (final member in node.members) {
      if (member is SMethodDeclaration) {
        final methodName = member.name!.name;
        // Methods capture the enum's declaration environment implicitly
        final function = InterpretedFunction.method(
          member,
          environment,
          enumObj,
        );

        if (member.isStatic) {
          if (member.isGetter) {
            enumObj.staticGetters[methodName] = function;
          } else if (member.isSetter) {
            enumObj.staticSetters[methodName] = function;
          } else {
            enumObj.staticMethods[methodName] = function;
          }
          Logger.debug(
            "[Visitor.visitEnumDeclaration]   Processed static method/getter/setter: $methodName",
          );
        } else {
          if (member.isAbstract) {
            throw RuntimeD4rtException(
              "Enums cannot have abstract members ('$enumName.$methodName').",
            );
          }
          if (member.isGetter) {
            enumObj.getters[methodName] = function;
          } else if (member.isSetter) {
            enumObj.setters[methodName] = function;
          } else {
            enumObj.methods[methodName] = function;
          }
          Logger.debug(
            "[Visitor.visitEnumDeclaration]   Processed instance method/getter/setter: $methodName",
          );
        }
      } else if (member is SConstructorDeclaration) {
        if (member.isFactory) {
          throw UnimplementedD4rtException(
            "Factory constructors in enums are not yet supported.",
          );
        }
        if (member.redirectedConstructor != null) {
          throw UnimplementedD4rtException(
            "Redirecting constructors in enums are not yet supported.",
          );
        }
        // Check if it's the default unnamed constructor or a named one
        final constructorName = member.name?.name ?? '';
        // Constructors also capture the enum's declaration environment
        final function = InterpretedFunction.constructor(
          member,
          environment,
          enumObj,
        );

        enumObj.constructors[constructorName] = function;
        Logger.debug(
          "[Visitor.visitEnumDeclaration]   Processed constructor: ${constructorName.isEmpty ? enumName : '$enumName.$constructorName'}",
        );
      } else if (member is SFieldDeclaration) {
        // Store field declarations for instance initialization
        // Only non-static fields are relevant for enum value instances
        if (!member.isStatic) {
          enumObj.fieldDeclarations.add(member);
          for (final variable in member.fields!.variables) {
            Logger.debug(
              "[Visitor.visitEnumDeclaration]   Stored instance field declaration: ${variable.name!.name}",
            );
          }
        } else {
          // Evaluate static fields immediately
          for (final variable in member.fields!.variables) {
            final fieldName = variable.name!.name;
            Object? value;
            if (variable.initializer != null) {
              value = variable.initializer!.accept<Object?>(this);
            }
            enumObj.staticFields[fieldName] = value;
            Logger.debug(
              "[Visitor.visitEnumDeclaration]   Evaluated static field: $fieldName = $value",
            );
          }
        }
      } else {
        Logger.warn(
          "[Visitor.visitEnumDeclaration]   Ignoring unknown member type: ${member.runtimeType}",
        );
      }
    }
  } finally {
    environment = originalVisitorEnv; // Restore environment
  }

  // Perf T6: function tables are populated; freeze before value
  // instantiation (which only reads constructors).
  enumObj.freezeMemberTables();

  // Instantiate Enum Values
  Logger.debug(
    "[Visitor.visitEnumDeclaration]   Instantiating enum values...",
  );
  for (int i = 0; i < node.constants.length; i++) {
    final constantDecl = node.constants[i];
    final valueName = constantDecl.name!.name;

    if (enumObj.values.containsKey(valueName)) {
      Logger.warn(
        "[Visitor.visitEnumDeclaration] Enum value '$enumName.$valueName' already exists (should not happen).",
      );
      continue;
    }

    // Create the runtime value instance (without initialized fields yet)
    final enumValueInstance = InterpretedEnumValue(enumObj, valueName, i);

    // Initialize Instance Fields using Constructor
    final constructorInvocation = constantDecl.arguments;
    // TODO: constructorSelector not available in serialized AST SArgumentList
    final constructorName = '';
    final constructorFunc = enumObj.constructors[constructorName];

    if (constructorFunc == null && constructorInvocation != null) {
      throw RuntimeD4rtException(
        "Enum '$enumName' does not have a constructor named '$constructorName' required by constant '$valueName'.",
      );
    }
    if (constructorFunc == null &&
        enumObj.constructors.isNotEmpty &&
        enumObj.constructors.containsKey('')) {
      throw RuntimeD4rtException(
        "Enum '$enumName' has a default constructor but constant '$valueName' doesn't call it implicitly (requires explicit `()` if args are needed or constructor exists).",
      );
    }
    // If there are NO constructors defined at all, and no args are passed, it's okay.
    if (constructorFunc != null && constructorInvocation != null) {
      Logger.debug(
        "[Visitor.visitEnumDeclaration]     Calling constructor '${constructorName.isEmpty ? enumName : '$enumName.$constructorName'}' for value '$valueName'",
      );
      // Evaluate arguments for the constructor call
      final (positionalArgs, namedArgs) = _evaluateArguments(
        constructorInvocation,
      );

      // Call the constructor function, binding `this` to the enumValueInstance.
      // The constructor's call method needs to handle field initialization.
      try {
        // Use the _prepareExecutionEnvironment helper? Or call directly?
        // Need to ensure constructor initializers (: this.field = arg) run.
        // Let's assume constructorFunc.call handles this when isInitializer is true.
        final boundConstructor = constructorFunc.bind(enumValueInstance);
        boundConstructor.call(this, positionalArgs, namedArgs);
        Logger.debug(
          "[Visitor.visitEnumDeclaration]     Constructor call finished for '$valueName'. Fields: $enumValueInstance",
        ); // Log instance directly for now
      } on RuntimeD4rtException catch (e) {
        throw RuntimeD4rtException(
          "Error executing constructor for enum value '$enumName.$valueName': ${e.message}",
        );
      } catch (e) {
        throw RuntimeD4rtException(
          "Unexpected error executing constructor for enum value '$enumName.$valueName': $e",
        );
      }
    } else if (constructorFunc == null &&
        constructorInvocation == null &&
        enumObj.constructors.isNotEmpty) {
      // Has constructors, but none called and no default exists implicitly.
      throw RuntimeD4rtException(
        "Enum constant '$enumName.$valueName' must call a constructor if the enum defines any.",
      );
    } else {
      Logger.debug(
        "[Visitor.visitEnumDeclaration]     No constructor called for '$valueName' (enum has no explicit constructors or constant has no args).",
      );
      // Initialize fields from declarations if no constructor called?
      // This might mirror class field initialization before constructor body.
      final fieldInitEnv = Environment(
        enclosing: enumObj.declarationEnvironment,
      );
      fieldInitEnv.define('this', enumValueInstance);
      final originalVisitorEnvForFields = environment;
      try {
        environment = fieldInitEnv;
        for (final fieldDecl in enumObj.fieldDeclarations) {
          for (final variable in fieldDecl.fields!.variables) {
            if (variable.initializer != null) {
              final fieldName = variable.name!.name;
              final value = variable.initializer!.accept<Object?>(this);
              enumValueInstance.setField(fieldName, value);
              Logger.debug(
                "[Visitor.visitEnumDeclaration]     Initialized instance field '$fieldName'=$value for '$valueName' (default init).",
              );
            }
          }
        }
      } finally {
        environment = originalVisitorEnvForFields;
      }
    }

    // Store the fully initialized instance in the enum object's map
    enumObj.values[valueName] = enumValueInstance;
    Logger.debug(
      "[Visitor.visitEnumDeclaration]   Created and initialized instance for '$enumName.$valueName' with index $i",
    );
  }

  // Pre-cache the values list
  try {
    enumObj.valuesList; // Access the getter to trigger cache creation
    Logger.debug(
      "[Visitor.visitEnumDeclaration]   Cached 'values' list for '$enumName'.",
    );
  } catch (e) {
    // Log error if caching fails (shouldn't happen ideally)
    Logger.error(
      "[Visitor.visitEnumDeclaration] Failed to cache 'values' for '$enumName': $e",
    );
  }

  Logger.debug("[Visitor.visitEnumDeclaration] END (Pass 2) for '$enumName'");
  return null; // Declaration doesn't return a value
}