Skip to content

# reform-prep-1: parser spans + import/function diagnostics#38

Merged
vpisarev merged 9 commits into
masterfrom
reform-prep-1
Jul 10, 2026
Merged

# reform-prep-1: parser spans + import/function diagnostics#38
vpisarev merged 9 commits into
masterfrom
reform-prep-1

Conversation

@vpisarev

Copy link
Copy Markdown
Owner

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 stamped
a point, so a caret was always a single ^. Now the lexer returns, per batch,
(tokens, batch_begin, batch_end) — after nexttokens' body runs, pos sits
at the batch end, so getloc(pos) is the end loc — and the parser applies that
span to every token. So loc_t is a real span, a caret shows the token width,
and loclist2loc folds true node spans:

val x = lenght + 1
        ^~~~~~        (was a single ^)

Internal recursive nexttokens() calls (string interpolation, numeric-suffix,
brace grouping) take .0; no per-arm changes; the lexer's internal point locs
are 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 from
    keyword, with a Levenshtein did you mean whose candidates are the *.fx
    files on the include path:

    import Strig
           ^~~~~   error: module Strig is not found; did you mean 'String'?
    
  • 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 span Class.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/ExpTyped fold to the whole x :> T / x : T construct.

Helper edit_distance moved to Ast.fx so the parser (unknown modules) and the
type 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)

  • FB-020 — typ_t carries no source location: type-name diagnostics land on
    the preceding :, and a span-based migrator cannot locate 't / half /
    vector occurrences via the AST. The 't → [t] reform will need token-level
    scanning for types, or locs added to typ_t. The biggest gap.
  • FB-021.op type errors surface inside the library (__dot_mul__
    instantiation site), not at the user's .*.
  • FB-022 — a failed fold-valued val cascades because the desugared shape
    sidesteps diag-1's DefVal recovery.

docs/problematic_spans_log.txt is the full audit (live compiler output for
each reform construct).

Verification

test_all 147, fxtest all (unit + negative 87 + ir + cfold + corpus O0/O3),
-pr-resolve census 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 the
tools/span_check.py acceptance oracle — measurement/tooling deliverables for
the design sessions, plus the FB-020/021/022 fixes.

vpisarev and others added 9 commits July 10, 2026 19:08
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>
@vpisarev vpisarev merged commit 9503085 into master Jul 10, 2026
3 checks passed
@vpisarev vpisarev deleted the reform-prep-1 branch July 10, 2026 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant