You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
E0381 binds names on its own scope stack, which never declares a for-header variable: a loop variable that shadows a scope member reads as the member (false E0381, exit 1) #2004
E0381's analyzer (InitializationAnalyzer) keeps its own scope stack and looks each name up on it by spelling. enterFunctionDeclaration declares the parameters on the stack, and enterVariableDeclaration declares the locals. Nothing declares a for-header variable (forVarDecl). So in for (u32 count <- 0; …), every read and write of count binds whatever outer count the stack holds.
Reproduction (main @ 0cd96994b; the same on PR #1993's head bd4f034d5)
scope S {
u32 count;
public u32 f() {
u32 sum <- 0;
for (u32 count <- 0; count < 3; count +<- 1) {
sum +<- count;
}
return sum;
}
}
6:29 error[E0381]: use of uninitialized variable 'count'
7:20 error[E0381]: use of uninitialized variable 'count'
The transpiler exits 1, but the for variable is initialized by its header. The reads bind the scope member, which is never assigned.
Control: rename the loop variable to i, and the transpiler exits 0.
Write arm: on PR #1993 the result depends on declaration order
PR #1993 changes the scope-member pre-scan (#1019) to bind a bare write through 1.4 (IProgram.lexicalDeclarationAt), which does see the for variable. The listener still binds through its own stack, which does not.
u32 flag <- 0;
scope S {
u32 count;
public void set() {
for (u32 count <- 0; flag < 3; flag +<- 1) {
count <- 5;
}
}
public u32 get() {
return count;
}
}
The member is never written, so E0381 is the correct result in both orders. On the PR, when get() comes after set(), the listener has recorded count <- 5 against the member, and that record persists into get(). On main, the pre-scan matched by spelling as well, so both orders are wrong.
Cause
There are two binding decisions in one analyzer. CLAUDE.md says: "Do not record declarations in an analyzer's own enterVariableDeclaration / enterParameter / enterForVarDecl: that is a second binding decision." 1.4's lexical frames already hold every local, parameter and for variable with its span. IProgram.bindValue(sourceFile, root, name, at) is "the one place a spelling becomes a declaration", and it already takes global. / this. as its root.
Definition of done
InitializationAnalyzer decides what a name binds through IProgram.bindValue, with the target's own global. / this. / bare root, and not through its own stack or a spelling test. It no longer declares parameters or locals itself, and initialization state is keyed by the declaration that 1.4 returns.
Fixtures: the for-variable read compiles, and the write arm reports E0381 in both declaration orders. Each fixture fails if the stack binding is put back (mutation-checked).
Box 1:InitializationAnalyzer.bindingKey calls IProgram.bindValue(sourceFile, root, name, at) for every read, write, argument and pre-scan target. Writes pass ChainRoot.ofTarget(target) as root.
State is keyed by the ILocalDeclaration that 1.4 returns, or by the C name of a file-scope variable or scope member.
ScopeStack is deleted, and nothing declares parameters any more. A parameter or for variable has no state, so it reads as initialized.
A local's state is created under the declaration 1.4 framed for it, and an invariant asserts that 1.4 framed it.
Box 2: the fixtures are in tests/bugs/issue-2004-initialization-binds-through-1-4/: the for-variable read executes, and the write arm reports E0381 in both orders.
With main's stack-based analyzer put back, all three fail.
With bd4f034d's version put back, the read and reader-after fail. Reader-before passes there, because that pre-scan already bound through 1.4 (see the table above). It fails when the pre-scan binds the bare target as the member.
Activity
fix(#2003,#2004): initialization binds through 1.4; one statement wal…
devin-ai-integration commentedon Oct 11, 2026
Both boxes are met on PR #1993 at
40c60db96:InitializationAnalyzer.bindingKeycallsIProgram.bindValue(sourceFile, root, name, at)for every read, write, argument and pre-scan target. Writes passChainRoot.ofTarget(target)asroot.ILocalDeclarationthat 1.4 returns, or by the C name of a file-scope variable or scope member.ScopeStackis deleted, and nothing declares parameters any more. A parameter orforvariable has no state, so it reads as initialized.tests/bugs/issue-2004-initialization-binds-through-1-4/: thefor-variable read executes, and the write arm reports E0381 in both orders.main's stack-based analyzer put back, all three fail.bd4f034d's version put back, the read and reader-after fail. Reader-before passes there, because that pre-scan already bound through 1.4 (see the table above). It fails when the pre-scan binds the bare target as the member.refactor(#2004): find a local's framed declaration by its span, not a…