Repository navigation
refactor(Tactic/Linter/Header): make the header linter stateful - #42201
marcelolynch wants to merge 17 commits into
Conversation
Rewrite the minImports linter with the stateful linter API (leanprover/lean4#14357). The cumulative import data moves from a global IO.Ref into the linter state, which the elaborator threads through the commands of the file. The IO.Ref was unsafe under parallel elaboration, so `#import_bumps` had to set `Elab.async false`; the stateful state is safe and the option stays on. Linter behavior is unchanged: MathlibTest/MinImports.lean passes as is (validated on a nightly-2026-07-27 toolchain). NOTE: CI stays red until the toolchain bump to v4.34.0-rc1, which first provides `registerStatefulLinter`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new section runs with Elab.async explicitly on: the linter state accumulates correctly across commands, and #import_bumps keeps parallel elaboration enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ionIn' Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ort_bumps Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR summary 48540ad78d
|
| File | Base Count | Head Count | Change |
|---|---|---|---|
| Mathlib.Tactic.Linter.Header | 4 | 3 | -1 (-25.00%) |
Import changes for all files
| Files | Import difference |
|---|---|
| ../mathlib-ci/scripts/pr_summary/import_trans_difference.sh all | |
| There are 8414 files with changed transitive imports taking up over 367408 characters: this is too many to display! | |
You can run this locally from your mathlib4 directory: |
git clone https://github.com/leanprover-community/mathlib-ci.git ../mathlib-ci
Declarations diff (regex)
+ HeaderState
+ async_uses_norm_num
+ headerPost
+ longLinePost
+ minImportsPost
- headerLinter
- longLineLinter
- minImportsLinter
You can run this locally as follows
## from your `mathlib4` directory:
git clone https://github.com/leanprover-community/mathlib-ci.git ../mathlib-ci
## summary with just the declaration names:
../mathlib-ci/scripts/pr_summary/declarations_diff.sh <optional_commit>
## more verbose report:
../mathlib-ci/scripts/pr_summary/declarations_diff.sh long <optional_commit>The doc-module for scripts/pr_summary/declarations_diff.sh in the mathlib-ci repository contains some details about this script.
Declarations diff (Lean)
✅ Lean-aware diff — post-build, computed from the Lean environment (commit
48540ad).
- +36 new declarations
- −1 removed declarations
+Mathlib.Linter.HeaderState
+Mathlib.Linter.HeaderState.casesOn
+Mathlib.Linter.HeaderState.ctorIdx
+Mathlib.Linter.HeaderState.headerSyntax
+Mathlib.Linter.HeaderState.mk
+Mathlib.Linter.HeaderState.mk.inj
+Mathlib.Linter.HeaderState.mk.injEq
+Mathlib.Linter.HeaderState.mk.noConfusion
+Mathlib.Linter.HeaderState.mk.sizeOf_spec
+Mathlib.Linter.HeaderState.noConfusion
+Mathlib.Linter.HeaderState.noConfusionType
+Mathlib.Linter.HeaderState.rec
+Mathlib.Linter.HeaderState.recOn
+Mathlib.Linter.HeaderState.settled
+Mathlib.Linter.ImportState
+Mathlib.Linter.ImportState.casesOn
+Mathlib.Linter.ImportState.ctorIdx
+Mathlib.Linter.ImportState.importSize
+Mathlib.Linter.ImportState.minImports
+Mathlib.Linter.ImportState.mk
+Mathlib.Linter.ImportState.mk.inj
+Mathlib.Linter.ImportState.mk.injEq
+Mathlib.Linter.ImportState.mk.noConfusion
+Mathlib.Linter.ImportState.mk.sizeOf_spec
+Mathlib.Linter.ImportState.noConfusion
+Mathlib.Linter.ImportState.noConfusionType
+Mathlib.Linter.ImportState.rec
+Mathlib.Linter.ImportState.recOn
+Mathlib.Linter.ImportState.transClosure
+Mathlib.Linter.MinImports.minImportsLinter
+Mathlib.Linter.Style.Header.headerLinter
+Mathlib.Linter.Style.longLine.longLineLinter
+Mathlib.Linter.instInhabitedHeaderState
+Mathlib.Linter.instInhabitedHeaderState.default
+Mathlib.Linter.instInhabitedImportState
+Mathlib.Linter.resetMinImports
-Mathlib.Linter.«command#reset_min_imports»No changes to strong technical debt.
No changes to weak technical debt.
Current commit 48540ad78d
Reference commit 37252fbc4b
This script lives in the mathlib-ci repository. To run it locally, from your mathlib4 directory:
git clone https://github.com/leanprover-community/mathlib-ci.git ../mathlib-ci
../mathlib-ci/scripts/reporting/technical-debt-metrics.py pr_summary
- The
relativevalue is the weighted sum of the differences with weight given by the inverse of the current value of the statistic. - The
absolutevalue is therelativevalue divided by the total sum of the inverses of the current values (i.e. the weighted average of the differences).
Rewrite the header linter with the stateful linter API (leanprover/lean4#14357). The linter state caches the library-root check, which replaces the Std.Mutex that guarded the cache against concurrent async linter runs, and records that the checks ran, so the linter checks the header of each module exactly once. A file without a module doc-string gets one set of warnings at its first command; the IO.Ref-era behavior repeated the full header parse and its warnings on every command of such a file. Linter behavior on well-formed files is unchanged; the header linter tests pass as is (validated on a nightly-2026-07-27 toolchain). NOTE: CI stays red until the toolchain bump to v4.34.0-rc1, which first provides `registerStatefulLinter`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The header linter state gains two things. The `headerSyntax` field stores the parsed module header, so other linters can read it instead of a re-parse of the file. The `settled` flag replaces `ran` and also covers exempt modules, so every command of an exempt file takes the same fast path as a checked file. The `withSetOptionIn'` helper moves to the `Mathlib.Linter` namespace and becomes public: the consumer linters share the single copy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eader linter state The end-of-file report positions its warnings with the parsed header from the state of the header linter. When the header checks did not run, the report falls back to the previous behavior: it reads the file and parses the header again. Two new tests pin both paths. MathlibTest/Linter/Header/MinImportsPayload.lean exercises the payload path, and a new section in MathlibTest/MinImports.lean exercises the fallback. A temporary throwError probe in the fallback verified that each test takes its intended path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… linter The longLine linter becomes a stateful linter with trivial state, so its end-of-file check can read the parsed header from the state of the header linter. When the header checks did not run, the check parses the header itself, as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
263f312 to
a314edf
Compare
|
This PR/issue depends on: |
Core's `withSetOptionIn` now has an arbitrary result type (leanprover/lean4#14581, in v4.34.0-rc1), so it fits the post phase of a stateful linter. The private polymorphic copy goes away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # Mathlib/Tactic/Linter/MinImports.lean
Core's `withSetOptionIn` now has an arbitrary result type (leanprover/lean4#14581, in v4.34.0-rc1), so it fits the phases of a stateful linter. The shared polymorphic copy goes away, and the `header` and `longLine` linters call the core function. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tate `linter.checkInitImports` requires that a module outside the import closure of `Mathlib.Init` reaches the `header` linter through `Mathlib.Init`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
This pull request has conflicts, please merge |
…teful # Conflicts: # Mathlib/Tactic/Linter/Style.lean
|
This pull request has conflicts, please merge |
…teful # Conflicts: # Mathlib/Tactic/Linter/Header.lean
…tion stateful The header linter on master changed with leanprover-community#40347: it now detects the first command with a fast parse of the imports on every command. This commit re-applies the stateful refactor on top of that implementation. The state records the verdict for the module, so every later command skips the per-command parse. The state replaces the library-root mutex and stores the parsed header for the minImports and longLine consumers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
This pull request has conflicts, please merge |
This PR rewrites the header linter as a stateful linter (leanprover/lean4#14357). The rewrite builds on the new implementation of the linter from #40347. Linter behavior does not change. The four pre-existing header linter test files pass without changes.
The linter state removes the last per-command cost of the linter. The current implementation runs a fast parse of the imports on every command of every file, to detect whether the current command is the first one. #40347 notes this residual cost and points to the new linting framework for the fix. The state now records a verdict for the module: the header checks ran, or the module is exempt. The linter reaches the verdict on the first command, and every later command returns after one read of a state field, with no parse.
The state also replaces the
Std.Mutexthat cached the library-root check. The check now runs at most once per module, so it needs no cache.The state also stores the parsed module header, and the header linter becomes a producer. The
minImportsandlongLinelinters read the parsed header from the state for their end-of-file checks, instead of a re-parse of the file. When the header checks did not run, both linters fall back to the previous re-parse. Two tests pin the payload path and the fallback path of theminImportsreport.🤖 Generated with Claude Code