summary_resolve-check-2026-10-15.md 5.0 KB

Session summary — 2026-10-15: the resolve/check pass

Branch resolve-check (off generics-inc1 / 6639c19). Suite 193/193; fixpoint OK (3,491,608 bytes); tree clean.

Tags: v3-resolve-check-step1 … step4d, v3-two-phase-parse-plan.

What the thread set out to do

Split semantic analysis out of the grammar's parse-time actions into a post-parse pass (Check), so that identifier binding happens after a unit's declarations are known — which is what the forward-reference machinery (FwdVar*, IsFwdVar, the Design forward branch) exists to work around.

What landed (all green at each step)

Step Commit What
1 50df067 Check.def/.mod scaffold (Resolve), called by the driver between Parse and Lower, gated by -noresolve. No-op; byte-identical.
2 f34ca98 Forward resolution moved into Check (NoteForward/FlushForward); made scope-independent (SymTab.fwdVarScope + FindScope) so it can run post-parse. FwdVarType deleted.
3 7ca434b The forward operand is now InvalidType, so the generic "skip checks on an unknown type" rule replaces the purpose-built leniency. Deleted SymTab.IsFwdVar, the fwdVarT table and all five guards; FwdVarRef no longer allocates a placeholder.
4a 5703b27 Expression nodes now store their type (SetTy at the Expr/SimExpr/Term/Fact levels).
4b 192ad55 Check resolves a forward designator's full type (base symbol + selectors via SelType) and stores it on the node.
4c 9dc2ed7 Resolved types propagate up the expression tree (PropNode/EvalExpr); Lower reads the stored type for string/set literals (removes a duplicate construction).
4d 8efae2e A forward-tainted assignment is now type-checked — n := "abc" with a later VAR n : INTEGER was silently accepted.

New tests: t_fwdsel.mod, t_fwdprop.mod, t_bad_fwd.mod (expect_fail).

Findings worth keeping

  • The AST is not a typed IR. NkArray/NkRecord/NkPointer/ NkEnum/NkTypeIdent/NkProcType are never built; constructed types live only in SymTab. So the original plan's "walk the type AST" had nothing to walk — type-constructor checks stay parse-time.
  • Lower constructs types while emitting and NewDesc does not dedup, so a type must be constructed once and cached on the node (the parser already does, and Lower now reads it for literals).
  • The forward tolerance is a tested feature (t_fwdvar, t_fwdsel, t_fwdprop, Showcase22), including through an array-index selector.
  • FwdVar* cannot be deleted by moving checks. Even with every check in the walk, Design must still bind a not-yet-declared name — impossible in one pass. That needs a genuinely two-phase parse (declarations before bodies). See docs/plan-two-phase-parse.md.
  • The scanner's position model is quirky: after Get, sym is the token just scanned but pos/len describe the previous one (LexString vs LookAheadName), and Reset does not reset pos/len/nextPos/nextLen/line/col, so stale values leak across files. Any mid-file restart must reproduce that state exactly.

Where the machinery lives now

  • SymTab: FwdVarRef/FwdVarName/FwdVarResolve — record (name, kind, scope) and resolve by name; no placeholder, no leniency.
  • Check: NoteForward/FlushForward (resolve + store the designator type), SelType, EvalExpr/PropNode (propagate), CheckAssign (forward-tainted assignments).
  • Grammar: Design's forward branch (FwdVarRef + Check.NoteForward, Op=1 marker on the node).
  • Lower: unchanged type computation except the literal cases.

Next steps (choices)

  1. Implement the two-phase parse (docs/plan-two-phase-parse.md), then delete FwdVar*/NoteForward/FlushForward and the Design forward branch. Recommended start: do the restart at the driver level using the exported Reset + repeated Get (not new scanner internals), add a small grammar body-entry production, and calibrate the stop point empirically (encode observed pos/len into the error channel). Largest piece in the thread; spans sessions.
  2. Drop the non-standard tolerance (a forward reference is a plain 201) and update the four tests that rely on it. Deletes the whole mechanism for near-zero work, but is a language-semantics change.
  3. Extend the touched-subtree checks (control-flow conditions, RETURN, call arity) — the same safe pattern as step 4d; more forward-reference diagnostics, no parser change.
  4. Stop — the forward mechanism is clean, forward references are typed and checked, and FwdVar* is a small, self-contained path.

Resume

cd .../m2compiler-V3
git checkout resolve-check
./bootstrap/fixpoint.sh                       # FIXPOINT OK
cd compiler && ./build.sh && ./run_tests.sh   # 193/193

Key docs: docs/plan-resolve-check.md (running record), docs/plan-two-phase-parse.md (design for step 1 above).