visitExtensionDeclaration method
Visit a SExtensionDeclaration.
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
}