visitEnumDeclaration method
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
}