Skip to content

lsp: one walk per check, not two - #208

Merged
cmj0121 merged 4 commits into
mainfrom
fix/22-one-walk
Sep 22, 2026
Merged

cmj0121 merged 4 commits into
mainfrom
fix/22-one-walk

Conversation

@cmj0121

@cmj0121 cmj0121 commented Sep 22, 2026

Copy link
Copy Markdown
Owner

Closes #22. Part of #15.

A check now answers diagnostics and lint in one lowering walk. The server used to run check_files_diag (a merge and walk #1) and then lint_program, whose lint_conversions merged again and ran walk #2 to collect the L5xx notes. lint.check_and_lint merges once, walks once keeping both the errors and the notes, and runs the tree rules on the same merged program. zerg lint and zerg build behave as before; zerg lint also stops parsing the program twice.

On the compiler's own program, one check went from 2.07 walks to 1.09, measured in instructions retired against zerg build --emit check.

Commits

  • fix(lsp): one lowering walk per check, not two — emit.lint_conversions_of (one walk over a merged program; the no-notes-when-errors rule lives only here), lint.check_and_lint, one guarded call in ls_check
  • test(lsp): a check of the compiler's program costs one walk — scripts/lsp-check.sh: section 6 opens src/compiler/zergc.zg in a real session, measures the server from outside against zerg build --emit check, fails at one and a half walks or on any error; section 1 holds a buffer with an error and an L502 literal to "an error and no note", and compares lint findings in order
  • build(ci): the language-server gate runs without the corpus — the make lsp step no longer waits on the corpus fetch; gates-check lists lsp as tolerating its absence
  • doc(lsp): diagnostics are one walk, and the gate that says so — docs/tooling/lsp.md and lsp.zh-TW.md

Evidence

  • section 6 red on main's server (2.07 walks), green here (1.09)
  • the no-note assertion red with the drop removed from lint_conversions_of, green with it
  • 190 server/lint outputs byte-identical to main in review; make test green on the branch head

Not in this PR

🤖 Generated with Claude Code

@cmj0121 cmj0121 added this to the 0.6.0 milestone Sep 22, 2026
    A check asked `check_files_diag` for the errors and then `lint_program`
    for the lint findings, and `lint_program` reaches the L5xx notes through
    `lint_conversions` — a second merge and a second full lowering walk over
    the same program, asking again what the first had just computed. On the
    compiler's own program that second walk was about half of every check.

    `check_and_lint` (lint.zg) asks both in one walk. `lint_conversions_of`
    (emit.zg) is `lint_conversions` over a program already merged that also
    hands back the walk's findings, and it keeps the one copy of the rule
    that a program with errors gets no conversion advice. Keeping the notes
    changes no finding — `want_lints` is read by `chk_note_at` alone — so
    the errors are still the ones `check-equal` holds to the build's.
    `lint_program` and `lint_conversions` keep their shape; the rules they
    share moved into `lint_merged`, and `lint_program` now merges once.

    One check of `src/compiler/zerg/lint.zg` in `zerg lsp`: 11.7 s -> 6.6 s,
    172 G -> 91 G instructions retired, 217 MB -> 154 MB peak.

    Refs #22.
    `make lsp` now opens `src/compiler/zergc.zg` in a real session and
    measures what the check cost from OUTSIDE the process, against
    `zerg build --emit check` on the same program — one walk by
    definition. At 1.5 walks or more it fails, and so it does when the
    check publishes any error: the program builds, so an error there is a
    check that aborted part-way, which costs less than a walk and would
    otherwise pass. The unit is instructions retired where macOS `time -l`
    reports them, CPU time elsewhere: not wall time, and not CPU time on
    Apple silicon, where the core a process lands on moves the seconds and
    not the work.

    Every other case asks WHAT a check says, and a check that walks twice
    says the same thing — `check-equal` is green on both servers.

    Two holes the one walk opened are closed in section 1 as well. The
    server now takes its L5xx notes from the walk that found the errors,
    and the drop of those notes when the program has an error is shared
    with `zerg lint` (`lint_conversions_of`) — so no buffer could see that
    drop go missing, and the comparison with `zerg lint` cannot either,
    since both lose it together. A buffer with an error AND a literal the
    walk notes is added, held to `zerg lint`, and asserted outright to
    show an error and no note; the assertion is what goes red, shown
    below with the drop deleted. And the lint half is compared
    in order rather than sorted, because the order — tree rules, notes,
    what `#[allow(…)]` says — is the linter's answer too.

    Red on the two-walk server (main):
      WALK  one check of src/compiler/zergc.zg costs 2.07 walks
            (instructions: check 83162423161, server 172225314890)
    Red with the drop of the notes deleted from `lint_conversions_of`:
      NOTED  a buffer with an error: errors ['E3033 …'], conversion notes
             ['L502 the literal `1` is a float here …', …]
    Red with the check made to abort on the compiler's program:
      WALK  the server reports an error in src/compiler/zergc.zg, which
            builds: simulated abort
    Green on the one-walk one:
      WALK  one check of src/compiler/zergc.zg costs 1.09 walks
            (instructions: check 83258518710, server 90846980400)

    Refs #22.
    The `make lsp` step was guarded on the private corpus fetch, so a
    broken corpus secret switched it off — and with it the one-walk case,
    which measures a check of src/compiler and needs nothing the corpus
    holds. The corpus only widens the list of buffers; the examples alone
    clear every floor the gate sets. The guard is gone, and `lsp` joins
    gates-check's ledger of gates that read the corpus and tolerate its
    absence, so the registry and the workflow agree.

    Refs #22.
    The tool chapter said `publishDiagnostics` walks the program twice and
    named one walk as the next thing to measure. It is measured and built:
    the request is answered by `lex_diags` and `check_and_lint`, and the
    paragraph records the cost the gate measures — 2.07 walks before, 1.09
    after, in instructions retired against `zerg build --emit check` — and
    that `make lsp` fails at one and a half. Instructions rather than
    seconds, for the reason the gate gives. en and zh-TW together.

    Refs #22.
@cmj0121
cmj0121 merged commit 210f4dd into main Sep 22, 2026
8 checks passed
@cmj0121
cmj0121 deleted the fix/22-one-walk branch September 22, 2026 04:23
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.

lsp: one walk per check, not two

1 participant