Conversation
…ion) One compiler run now reports many independent type errors instead of dying on the first, with cascade noise suppressed. Recovery lives strictly above unification and rides the existing TypErr poison type (ctrlflow-1) and the existing per-item try/catch in check_eseq -- adapted, not rebuilt. Structural cascade suppression (the primary mechanism): - typ_has_typerr(t): detects a type already contaminated by an upstream error. - lookup_id: if the expected type has TypErr, suppress the resolution and pin the result to TypErr (poison_result_typ) instead of reporting -- the genuine error was already reported where the TypErr was produced. - scan_ IdDVal / commit_fun: a value/function whose stored type is TypErr propagates that poison to the use site (unify leaves the use-site var free because TypErr never binds). Only ever runs on already-failed paths, so a clean compile is unaffected: -pr-resolve census byte-identical (351 sites). Per-definition and per-branch recovery (report + poison + continue): - DefVal: annotation unify moved inside the recovery try; every name the pattern binds (tuple/record/as, via walk_pat) is poisoned -- with the declared type when annotated (the "annotations are firewalls" dividend), TypErr otherwise. - DefFun: body failure keeps the declared signature (pre-registration is the firewall); the return type is recovered from the branches that DID typecheck (e.g. an early `return 42`), poisoned to TypErr only if nothing pinned it. - check_cases / ExpIf: a failed match arm / if branch is reported and its siblings are still checked, so errors in several arms/branches surface in one run; a poisoned arm/branch does not constrain the result type -- the healthy siblings determine it, same logic as a jumping throw/ctrlflow-1 sibling. Template functions: body errors surface at instantiation and propagate/poison correctly; duplicate reports across N distinct instantiations are left for the Phase E (loc,msg) dedup. Verification: test_all 147, fxtest all, -pr-resolve census byte-identical, bootstrap fixpoint (only Ast_typecheck.c changed). Six negative goldens updated -- each dropped a spurious cascade line, now reporting exactly the root error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rected tests Makes multi-error runs readable and deterministic: - print_all_compile_errs now sorts diagnostics by (module, line, col) so the report reads top-to-bottom regardless of the order definitions were checked, and drops exact-duplicate messages keyed on the PRIMARY error line (a generic function body error is otherwise reported once per distinct instantiation -- same bug, one line, with whichever "when instantiating ..." context came first). - -fmax-errors=N (default 100): print at most N diagnostics, then a "further diagnostics suppressed" summary. Structural suppression keeps real runs well under the cap; it is a backstop for pathological inputs. Directed negative goldens (test/negative/230-234): three independent errors -> three diagnostics; a cascade -> exactly one; a broken body behind a declared signature -> the correct caller stays clean and only the genuinely mistyped caller errors; errors in two match arms -> both; -fmax-errors truncation line. Bootstrap: 9 compiler modules regenerate from the options_t layout change (new max_errors field) -- the same propagation as annotate-2; generated C for user programs is unaffected (options_t is compile-time only). test_all 147, fxtest all, negative 83, -pr-resolve census 351 unchanged, fixpoint holds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A not-found identifier / constructor / field that is close (Levenshtein) to a visible name now gets a gcc/clang-style suggestion, e.g. error: ... 'lenght' ... is not found; did you mean 'length'? - edit_distance: two-row Levenshtein DP (error-path only, cost irrelevant). - suggest_similar: up to two closest visible env names within a length-scaled threshold (<=1 for short names, <=2 longer), skipping the name itself and compiler-internal '_' names; "; did you mean 'a' [or 'b']?" tail, empty when nothing is close. - report_not_found_typed gains an `env` param and appends the suggestion ONLY when there are no candidates (a genuinely unknown name); when overloads exist but the call does not match, the candidate listing is shown as before. Only runs when a diagnostic is already being built, so clean compiles are unaffected: the -pr-resolve census grows by exactly 2 ranked sites (the two `min` calls in edit_distance) with the 7 under-constrained fallbacks unchanged -- no existing resolution altered. test_all 147, fxtest all, negative 84 (new 235_did_you_mean; 402_unknown_constructor now suggests 'A'/'B'), fixpoint holds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ision
A frontend diagnostic now shows the offending source line with a caret under
the column:
file.fx:2:9: error: ... 'lenght' ... is not found; did you mean 'length'?
2 | val x = lenght + 1
| ^
- loc_excerpt(loc): reads the source line lazily (cached per module via
src_lines_cache, reset in init_all), expands tabs 1:1 so the caret stays
aligned, and draws '^' (or '^~~~' when loc carries a real span).
- Built at RAISE time inside compile_err / compile_warning, not at print time:
precision depends on the current stage, which advances as the driver runs, so
by end-of-run every error would otherwise look like a backend error.
- Stage-honest precision via a new explicit Ast.compiler_stage
(CompilerInit|Frontend|Middle|Backend), separate from freeze_ids: the FRONTEND
spans lexer + parser + typecheck AND K-normalization (all consume the AST with
exact locations -- e.g. non-exhaustive-match errors now get a caret), while
the MIDDLE end (K optimizations: inlining/fusion) and BACKEND (C generation)
drop the caret because locations drift there and not all context reaches
C-gen intact. Compiler.process_all sets Frontend before parse_all, Middle
after k_normalize_all, Backend before k2c_all.
- One renderer serves errors and warnings.
- fxtest T3 harness made caret-aware: excerpt lines (" N | src" / " | ^")
are compared verbatim (exact spacing is the contract); all other lines keep
the whitespace-collapse. 60 negative goldens migrated to the new format.
Full ladder: test_all 147, fxtest all, negative 84, determinism, sanitize,
-pr-resolve census 356 (349 ranked + 7 fallback -- the 7 unchanged; the extra
ranked sites are the new diagnostic code, no existing resolution altered),
bootstrap fixpoint holds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ CLAUDE.md - Lift the "break/continue inside a @parallel loop" legality check from C-gen (finalize_loop_body) to the type checker. Whether a loop is @parallel is a pure nesting fact, so ScLoop now carries a `parallel` flag ((nested, parallel, block_idx), set from flags.for_flag_parallel), and check_inside_for rejects break/continue at the innermost parallel loop with an exact caret -- now visible under -no-c and golden-able (test/negative/236_parallel_continue). A break targeting a nested non-parallel loop inside a @parallel loop stays legal. The old C-gen check remains as an unreachable backstop. (A future WP may actually SUPPORT break/continue in parallel loops; then relax the check rather than reject.) The scope_t layout change reorders many bootstrap modules -- compile-time-only type (like options_t), generated C for programs unchanged, fixpoint holds. - docs/diag1_report.md: the Phase A map, tier decisions, before/after, the stage-honest precision model, and open follow-ups. - CLAUDE.md: a Diagnostics note (recovery + TypErr poison + structural suppression, sort/dedup/-fmax-errors, did-you-mean, caret gated on Ast.compiler_stage, and that every negative golden now carries a caret). Full ladder: test_all 147, fxtest all, negative 85, determinism 3/3, sanitize clean, -pr-resolve census 356 (349 ranked + 7 fallback, unchanged), fixpoint. 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 an LSP: one compiler run now reports many useful type errors
instead of dying on the first, with cascade noise suppressed, gcc/clang-style
caret excerpts, and "did you mean" suggestions.
What you get
val x = undef; x+1; f(x)reports onlythe root
undef.genuinely mistyped caller errors (the "annotations are firewalls" dividend).
position; exact duplicates deduped; capped by
-fmax-errors=N(default 100).How it works (recovery lives strictly above unification)
Recovery = report + poison with
TypErr+ continue. It rides the machinerythat already existed (a per-item
try/catchincheck_eseq, onecompile_err→
CompileError→push_compile_erraccumulation path) — adapted, not rebuilt.The key insight:
TypErr(the ctrlflow-1 poison type) already flows silentlythrough
unifywithout binding, so poisoning a failed symbol withTypErrgives structural cascade suppression almost for free.
typ_has_typerr+ three choke points (lookup_id,scan_'sIdDVal,commit_fun) suppress a resolution whose operand/callee is already poisonedand propagate the
TypErrto the result. They only fire on already-failedpaths, so a clean compile is unaffected — the
-pr-resolvecensus isbyte-identical (the 7 under-constrained fallbacks unchanged).
unifymoved inside the recoverytry; every namethe pattern binds (tuple/record/as, via
walk_pat) is poisoned — with thedeclared type when annotated (firewall),
TypErrotherwise.recovered from the branches that DID typecheck (an early
return 42pinsit), poisoned to
TypErronly if nothing did.contributes no type, so the healthy siblings determine the result (the
jumping-throw/ctrlflow-1 logic).
duplicates across N instantiations collapse in the dedup.
Stage-honest caret precision
loc_excerptdraws the excerpt at raise time (insidecompile_err/compile_warning), because precision depends on the compilation stage, whichadvances as the driver runs. Precision is governed by an explicit
Ast.compiler_stage(Init|Frontend|Middle|Backend), deliberately separatefrom the implicit
freeze_ids:consume the AST with exact locations, so even a non-exhaustive-match error
(raised in K-normalization) gets a caret.
drop the caret: locations drift there and not all context reaches C-gen
intact, so a confident caret would point at an innocent line.
One renderer serves errors and warnings. Legality checks were pulled the right
way across this line too:
break/continueinside a@parallelloop is now atypecheck diagnostic (exact caret, visible under
-no-c, golden-able)instead of a late C-gen error.
Test-format change (reviewers: read the golden diff as a format change)
Every
test/negative/*.errgolden now carries the source line + caret. The T3harness (
tools/fxtest/negative.py) was made caret-aware: excerpt lines(
N | src/| ^) are compared verbatim (their spacing is the contract);all other lines keep the whitespace-collapse. New directed goldens:
multi-error, cascade, firewall, match-arm recovery,
-fmax-errorstruncation,did-you-mean, and the lifted
@parallelcheck.Verification
test_all147;fxtest all(unit + negative 85 + ir + cfold + corpus O0/O3);determinism3/3;sanitizeclean.-pr-resolvecensus: byte-identical resolution — the extra sites are newdiagnostic code only (recovery choke points fire solely on poisoned types).
scope_t/ScLooplayout change (for the@parallellift) reorders many bootstrap modules — a compile-time-only type(like
options_t); generated C for user programs is unchanged.Not in scope (follow-ups, noted in
docs/diag1_report.md)^~~~column spans (most locs are single-point today).break/continuein@parallelloops (currentlyrejected); when that lands, relax the lifted check rather than reject.
Full write-up:
docs/diag1_report.md.