diff --git a/base/src/main/java/org/aya/resolve/visitor/ExprResolver.java b/base/src/main/java/org/aya/resolve/visitor/ExprResolver.java index 20d40b74a6..1d86afee66 100644 --- a/base/src/main/java/org/aya/resolve/visitor/ExprResolver.java +++ b/base/src/main/java/org/aya/resolve/visitor/ExprResolver.java @@ -260,11 +260,13 @@ private void addReference(@NotNull DefVar defVar) { private void introduceDependencies(@NotNull GeneralizedVar var) { if (allowedGeneralizes.containsKey(var)) return; + // Introduce dependencies first var dependencies = collector.getDependencies(var); for (var dep : dependencies) { introduceDependencies(dep); } + // Now introduce the variable itself var owner = var.owner; assert owner != null : "GeneralizedVar owner should not be null"; var param = owner.toExpr(false, var.toLocal()); @@ -275,11 +277,11 @@ private void introduceDependencies(@NotNull GeneralizedVar var) { public @NotNull AnyVar resolve(@NotNull QualifiedID name) { var result = ctx.get(name); if (result instanceof GeneralizedVar gvar) { + // Ensure all dependencies are introduced introduceDependencies(gvar); var gened = allowedGeneralizes.getOrNull(gvar); if (gened != null) return gened.ref(); } - return result; } diff --git a/base/src/main/java/org/aya/resolve/visitor/StmtResolver.java b/base/src/main/java/org/aya/resolve/visitor/StmtResolver.java index 7ca1d76408..909664aa0e 100644 --- a/base/src/main/java/org/aya/resolve/visitor/StmtResolver.java +++ b/base/src/main/java/org/aya/resolve/visitor/StmtResolver.java @@ -4,6 +4,7 @@ import kala.collection.SeqView; import kala.collection.immutable.ImmutableSeq; +import kala.collection.mutable.MutableList; import kala.value.MutableValue; import org.aya.generic.stmt.TyckOrder; import org.aya.generic.stmt.TyckUnit; @@ -39,8 +40,54 @@ static void resolveStmt(@NotNull ResolvingStmt stmt, @NotNull ResolveInfo info) case ResolvingStmt.GenStmt(var variables) -> { var resolver = new ExprResolver(info.thisModule(), false); resolver.enter(Where.Head); + + // First pass: register all variables to detect cycles + for (var variable : variables.variables) { + resolver.collector().registerVariable(variable); + } + + // Second pass: handle dependencies and references + var ownerRefs = MutableList.create(); + for (var variable : variables.variables) { + var owner = variable.owner; + assert owner != null : "GeneralizedVar owner should not be null"; + + // Add to allowedGeneralizes + var param = owner.toExpr(false, variable.toLocal()); + resolver.allowedGeneralizes().put(variable, param); + + // Collect owner reference if it's a TyckUnit + if (owner instanceof TyckUnit unit) { + var ref = new TyckOrder.Head(unit); + if (!ownerRefs.contains(ref)) ownerRefs.append(ref); + } + + // Handle dependencies + var deps = resolver.collector().getDependencies(variable); + for (var dep : deps) { + if (!resolver.allowedGeneralizes().containsKey(dep)) { + var depOwner = dep.owner; + assert depOwner != null : "GeneralizedVar owner should not be null"; + var depParam = depOwner.toExpr(false, dep.toLocal()); + resolver.allowedGeneralizes().put(dep, depParam); + } + + // Add dependency owner reference if it's a TyckUnit + if (dep.owner instanceof TyckUnit depUnit) { + var ref = new TyckOrder.Head(depUnit); + if (!ownerRefs.contains(ref)) ownerRefs.append(ref); + } + } + } + + // Add collected references to resolver + ownerRefs.forEach(resolver.reference()::append); + + // Process the statement itself variables.descentInPlace(resolver, (_, p) -> p); - addReferences(info, new TyckOrder.Head(variables), resolver); + + // Do not add the GenStmt itself to TyckOrder as it's not a TyckUnit + // So we skip calling addReferences for the GenStmt } } } diff --git a/base/src/main/java/org/aya/resolve/visitor/VariableDependencyCollector.java b/base/src/main/java/org/aya/resolve/visitor/VariableDependencyCollector.java index 8641ce4512..d9f22d0af0 100644 --- a/base/src/main/java/org/aya/resolve/visitor/VariableDependencyCollector.java +++ b/base/src/main/java/org/aya/resolve/visitor/VariableDependencyCollector.java @@ -17,19 +17,36 @@ import java.util.HashMap; import java.util.Map; +/** + * Collects dependency information for generalized variables using DFS on their types. + * + * 1. A variable's type may reference other generalized variables; we record those as dependencies. + * 2. If we revisit a variable already on the DFS stack ("visiting" set), that indicates + * a cyclic dependency, and we report an error. + * 3. Once a variable is fully processed, it goes into the "visited" set; future registrations + * of the same variable skip repeated traversal. + * + * Pitfalls & Notes: + * - A single variable (e.g. “A”) should be registered once, to avoid duplication. + * - Attempting to re-scan or re-introduce “A” in another variable’s context can cause + * confusion or potential cycles. So we do all dependency scans here, at declaration time. + * - Any reference to a variable out of scope is handled as an error in the resolver + * if it’s not in the allowedGeneralizes map. + */ public final class VariableDependencyCollector { private final Map> dependencies = new HashMap<>(); private final Reporter reporter; private final MutableSet visiting = MutableSet.create(); + private final MutableSet visited = MutableSet.create(); public VariableDependencyCollector(Reporter reporter) { this.reporter = reporter; } public void registerVariable(GeneralizedVar var) { - if (dependencies.containsKey(var)) return; + if (visited.contains(var)) return; - // If var is already being visited, we found a cycle. + // If var is already being visited in current DFS path, we found a cycle if (!visiting.add(var)) { reporter.report(new CyclicDependencyError(var.sourcePos(), var)); throw new Context.ResolvingInterruptedException(); @@ -40,7 +57,11 @@ public void registerVariable(GeneralizedVar var) { visiting.remove(var); // Recursively register dependencies - for (var dep : deps) registerVariable(dep); + for (var dep : deps) { + registerVariable(dep); + } + + visited.add(var); } public ImmutableSeq getDependencies(GeneralizedVar var) {