visitMixinDeclaration method
Visit a SMixinDeclaration.
Implementation
@override
Object? visitMixinDeclaration(SMixinDeclaration node) {
final mixinName = node.name!.name;
Logger.debug(
"[Visitor.visitMixinDeclaration] START for '$mixinName' in env: ${environment.hashCode}",
);
// Retrieve the placeholder mixin object created in Pass 1
Object? placeholder = environment.get(mixinName);
if (placeholder == null ||
placeholder is! InterpretedClass ||
!placeholder.isMixin) {
throw StateD4rtException(
"Placeholder for mixin '$mixinName' not found or invalid during Pass 2.",
);
}
final mixinClass = placeholder;
Logger.debug(
"[Visitor.visitMixinDeclaration] Retrieved placeholder for mixin '$mixinName' (hash: ${mixinClass.hashCode})",
);
// Resolve 'on' clause constraints ON THE EXISTING mixinClass object
if (node.onClause != null) {
mixinClass.onClauseTypes.clear(); // Clear existing before populating
for (final typeNode in node.onClause!.superclassConstraints) {
final typeName = typeNode.name!.name;
try {
final potentialType = environment.get(typeName);
if (potentialType is InterpretedClass ||
potentialType is BridgedClass) {
// RC-7b: Accept both InterpretedClass and BridgedClass as valid
// on-clause constraints. Bridged types like State, ChangeNotifier
// are BridgedClass instances resolved from the environment.
mixinClass.onClauseTypes.add(potentialType as RuntimeType);
Logger.debug(
"[Visitor.visitMixinDeclaration] Added 'on' constraint '$typeName' for '$mixinName'",
);
} else {
throw RuntimeD4rtException(
"Type '$typeName' in 'on' clause of mixin '$mixinName' is not a class (${potentialType?.runtimeType}).",
);
}
} on RuntimeD4rtException {
throw RuntimeD4rtException(
"Type '$typeName' in 'on' clause of mixin '$mixinName' not found. Ensure it's defined.",
);
}
}
}
// Cluster-18: Resolve 'implements' clause for mixins. Without this, an
// interpreted mixin like
// mixin _TickerProviderShim on State<T> implements TickerProvider { ... }
// never records TickerProvider as a bridged interface, so callers using
// `vsync: this` (where `this` is a State that mixes in this shim) cannot
// satisfy a TickerProvider parameter through interface-proxy resolution.
if (node.implementsClause != null) {
Logger.debug(
"[Visitor.visitMixinDeclaration] Processing 'implements' clause for '$mixinName' in env: ${environment.hashCode}",
);
for (final interfaceType in node.implementsClause!.interfaces) {
final interfaceName = interfaceType.name!.name;
try {
final potentialInterface = environment.get(interfaceName);
if (potentialInterface is InterpretedClass) {
if (potentialInterface.isBase) {
throw RuntimeD4rtException(
"Mixin '$mixinName' cannot implement base class '$interfaceName' outside of its library.",
);
}
mixinClass.interfaces.add(potentialInterface);
Logger.debug(
"[Visitor.visitMixinDeclaration] Added interface '$interfaceName' for '$mixinName'",
);
} else if (potentialInterface is BridgedClass) {
mixinClass.bridgedInterfaces.add(potentialInterface);
Logger.debug(
"[Visitor.visitMixinDeclaration] Added bridged interface '$interfaceName' for '$mixinName'",
);
} else {
throw RuntimeD4rtException(
"Mixin '$mixinName' cannot implement non-class '$interfaceName' (${potentialInterface?.runtimeType}).",
);
}
} on RuntimeD4rtException {
throw RuntimeD4rtException(
"Interface '$interfaceName' not found for mixin '$mixinName'. Ensure it's defined.",
);
}
}
}
// Populate members ON THE EXISTING mixinClass object
final declarationEnv =
environment; // Members use the mixin's declaration env
final originalVisitorEnv = environment;
Logger.debug(
"[Visitor.visitMixinDeclaration] Processing members for mixin '$mixinName' (hash: ${mixinClass.hashCode})",
);
try {
environment = declarationEnv;
for (final member in node.members) {
if (member is SMethodDeclaration) {
final methodName = member.name!.name;
// Methods capture the GLOBAL environment via the mixinClass
final function = InterpretedFunction.method(
member,
globalEnvironment,
mixinClass,
);
if (member.isStatic) {
// Static members belong to the mixin definition itself
if (member.isGetter) {
mixinClass.staticGetters[methodName] = function;
} else if (member.isSetter) {
mixinClass.staticSetters[methodName] = function;
} else {
mixinClass.staticMethods[methodName] = function;
}
} else {
if (member.isGetter) {
mixinClass.getters[methodName] = function;
} else if (member.isSetter) {
mixinClass.setters[methodName] = function;
} else if (member.isOperator) {
// Add operator methods to the operators map for mixins
mixinClass.operators[methodName] = function;
} else {
mixinClass.methods[methodName] = function;
}
}
} else if (member is SFieldDeclaration) {
if (member.isStatic) {
for (final variable in member.fields!.variables) {
mixinClass.staticFields[variable.name!.name] = variable
.initializer
?.accept<Object?>(this);
}
} else {
mixinClass.fieldDeclarations.add(member);
}
} else if (member is SConstructorDeclaration) {
throw RuntimeD4rtException(
"Mixins cannot declare constructors ('$mixinName').",
);
}
}
} finally {
environment = originalVisitorEnv;
}
// Perf T6: compact the mixin's function tables now that wiring is done.
mixinClass.freezeMemberTables();
Logger.debug("[Visitor.visitMixinDeclaration] END for '$mixinName'");
return null; // Mixin declaration doesn't return a value
}