# reform-prep-1: parser spans + import/function diagnostics#38
Merged
Conversation
Foundation for the span-based automigrator (and wider diag-1 carets): every AST
node's loc can now be a true span instead of a single point. Previously each
token carried only its begin (line,col); the parser stamped {line0,col0,line0,
col0} -- a point -- so a caret was always a single '^'.
- Lexer.make_lexer now returns ((token_t, lloc_t) list, lloc_t, lloc_t): a batch
of tokens plus the batch's begin and end point locs. nexttokens' dispatch body
is bound to `val tokens = ...`; after it runs, `pos` sits at the batch end, so
`getloc(pos)` is the end loc. Internal recursive nexttokens() calls (string
interpolation, numeric-suffix, brace grouping) take .0 (the token list). No
per-arm changes; internal helpers keep the point-based lloc_t untouched.
- Parser applies the batch span to every token in the batch, so a node built
from one token already spans that token's width; nodes folded from several
tokens (loclist2loc, used at many parser sites) now union true spans.
Batches are single-token except a few synthetic expansions (one span over the
whole literal there is the right answer anyway). Carets now render '^~~~' over
the token width; 36 negative goldens migrated to the wider carets (expected --
each is a legitimate span extension, reviewed). test_all 147, fxtest all (incl.
T4 IR snapshots), -pr-resolve census 356 unchanged, bootstrap fixpoint holds
(only Lexer.c + Parser.c regenerate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ExpCast (x :> T) and ExpTyped (x : T) took only the operand's loc, so their span was `x`, not `x :> T` -- the migrator (and the reparse oracle) needs the whole construct. Since types carry no source loc, add consumed_loc(before, after) -- the loc of the last token a sub-parser consumed -- and fold the node loc as loclist2loc(operand .. end-of-type). Behavior-neutral (no negative golden's primary error sits on a cast/typed node); test_all 147, negative clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… on parse errors
Import errors (misspelled or off-path module) are common; they now point at the
whole module name, with a caret:
import Foo.Bar.Baz
^~~~~~~~~~~ error: module Foo.Bar.Baz is not found
- add_to_imported_modules is handed the full module-name span (first token ..
last dotted-name token, via consumed_loc) instead of just the first token
(`import`/`from` cases). The `from M import ...` case previously pointed at
the `from` keyword -- fixed (the loclist2loc seed must be the name's first
token, not `from`, or the span is dragged back to column 1).
- Parse errors are frontend diagnostics with accurate locations, so the
ParseError printer now appends the same caret excerpt used for CompileError
(Ast.loc_excerpt); EOF errors (no source line) degrade to no caret. 15 parse
negative goldens migrate to carets; new 237_import_not_found locks the import
case.
test_all 147, fxtest all, negative 85, -pr-resolve census 356 unchanged,
bootstrap fixpoint holds (Parser.c + Compiler.c).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A misspelled stdlib import is a common error; now it gets a suggestion:
import Strig
^~~~~ error: module Strig is not found; did you mean 'String'?
The candidate hypotheses are the *.fx files visible on the include path (each
file name is a module name), globbed via Filename.glob on the not-found path;
edit_distance picks the closest within a length-scaled threshold, with an
alphabetical tie-break so the suggestion is deterministic regardless of glob
order. edit_distance moved from Ast_typecheck to Ast so both the type checker
(unknown identifiers) and the parser (unknown modules) share one copy; both do
`from Ast import *`, so no call site changed.
Dotted names degrade to no suggestion (candidates are top-level module names) --
acceptable; the not-found caret already lands on the full dotted name (WP-1c).
test_all 147, fxtest all, negative (238 locks the case), -pr-resolve census 356
unchanged, bootstrap fixpoint holds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…'fun' keyword
A function's own diagnostics (implicit-return-type warning, redeclaration, the
overload candidate listing, ...) took the DefFun loc, which was the 'fun'
keyword (`^~~`). They now point at the function NAME:
fun compute(a: int, b: int) = a + b
^~~~~~~ warning: implicit return type of 'compute'
- parse_defun folds df_loc to the name span: the (possibly dotted, Class.method)
name from its first token to its last (consumed_loc); an operator definition
spans 'operator X' (keyword + symbol).
- 10 negative goldens shift accordingly -- candidate listings and
redeclaration/rettype diagnostics now cite the name column, an improvement.
test_all 147, fxtest all, negative 87, -pr-resolve census 356 unchanged, rettype
gate clean, bootstrap fixpoint holds (Parser.c only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docs/reform_prep1_report.md: the WP-1 span work (token spans + import/function name diagnostics), the span audit summary, bugs filed, verification, and the open follow-ups (WP-2/3/4, span_check oracle, FB fixes). - CLAUDE.md: a Spans note -- loc_t is a true span now, diagnostics point at the name, parse errors carry carets, consumed_loc helper, and the FB-020 gotcha (typ_t is loc-less). (Also carries a prior wording compaction of the bootstrap/build lines.) - found_bugs.md: FB-020 (typ_t carries no source location -- 't/type-name reform blocker), FB-021 (.op error surfaces inside the library), FB-022 (failed fold-valued val cascades past diag-1 recovery). Fenced, not chased. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WP-1d's suggest_module pulls in Filename.glob/basename/remove_extension, which regenerated compiler/bootstrap/Filename.c; the file was left unstaged. Committed now so the checked-in bootstrap is a fixpoint (update_compiler.py --check clean) and CI does not go red on a stale bootstrap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vadim's resolution direction: keep typ_t untouched (too fundamental) and later
introduce a typespec_t { typ; loc } wrapper, switching to it only where a source
position is needed (parser output / annotations).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Groundwork for the syntax-reform automigrator (and better diagnostics along the
way): source locations are now true spans, not points, and import/function
errors point at the right thing. Nothing here changes language behavior — only
locations and diagnostics.
Spans (the foundation)
Previously every token carried only its begin
(line, col); the parser stampeda point, so a caret was always a single
^. Now the lexer returns, per batch,(tokens, batch_begin, batch_end)— afternexttokens' body runs,possitsat the batch end, so
getloc(pos)is the end loc — and the parser applies thatspan to every token. So
loc_tis a real span, a caret shows the token width,and
loclist2locfolds true node spans:Internal recursive
nexttokens()calls (string interpolation, numeric-suffix,brace grouping) take
.0; no per-arm changes; the lexer's internal point locsare untouched.
Diagnostics point at the name
Imports (a common error — misspelled or off-path module): the caret lands
on the whole (possibly dotted) module name, not the first token or the
fromkeyword, with a Levenshtein
did you meanwhose candidates are the*.fxfiles on the include path:
Functions: a function's own diagnostics (implicit-return-type warning,
redeclaration, the overload candidate listing) point at the NAME, not
fun;operators span
operator X; dotted method names spanClass.method.Parse errors now carry the same caret excerpt as type errors (they are
frontend diagnostics with accurate locations); EOF errors degrade to no caret.
ExpCast/ExpTypedfold to the wholex :> T/x : Tconstruct.Helper
edit_distancemoved toAst.fxso the parser (unknown modules) and thetype checker (unknown identifiers) share one copy;
consumed_loc(before, after)folds a node's span out to the end of a sub-parser that returns no loc.
Reform-relevant findings (filed, not fixed)
typ_tcarries no source location: type-name diagnostics land onthe preceding
:, and a span-based migrator cannot locate't/half/vectoroccurrences via the AST. The't → [t]reform will need token-levelscanning for types, or locs added to
typ_t. The biggest gap..optype errors surface inside the library (__dot_mul__instantiation site), not at the user's
.*.fold-valuedvalcascades because the desugared shapesidesteps diag-1's DefVal recovery.
docs/problematic_spans_log.txtis the full audit (live compiler output foreach reform construct).
Verification
test_all 147,
fxtest all(unit + negative 87 + ir + cfold + corpus O0/O3),-pr-resolvecensus 356 unchanged, rettype gate clean, bootstrap fixpoint holds.~90 negative goldens migrated to carets (reviewed as a format change — wider
carets, name-column citations).
Not in scope (see
docs/reform_prep1_report.md)WP-2 migration census, WP-3 A/B renders, WP-4
list[t]parser spike, and thetools/span_check.pyacceptance oracle — measurement/tooling deliverables forthe design sessions, plus the FB-020/021/022 fixes.