Skip to content

refactor(Tactic/Linter/Header): make the header linter stateful - #42201

Open
marcelolynch wants to merge 17 commits into
leanprover-community:masterfrom
marcelolynch:2026/07/HeaderStateful
Open

marcelolynch wants to merge 17 commits into
leanprover-community:masterfrom
marcelolynch:2026/07/HeaderStateful

Conversation

@marcelolynch

@marcelolynch marcelolynch commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

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.Mutex that 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 minImports and longLine linters 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 the minImports report.

🤖 Generated with Claude Code

marcelolynch and others added 4 commits July 28, 2026 10:41
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>
@github-actions github-actions Bot added the t-linter Linter label Jul 28, 2026
@github-actions

github-actions Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

PR summary 48540ad78d

Import changes for modified files

Dependency changes

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 relative value is the weighted sum of the differences with weight given by the inverse of the current value of the statistic.
  • The absolute value is the relative value divided by the total sum of the inverses of the current values (i.e. the weighted average of the differences).

marcelolynch and others added 5 commits July 28, 2026 17:28
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>
@marcelolynch
marcelolynch force-pushed the 2026/07/HeaderStateful branch from 263f312 to a314edf Compare July 28, 2026 20:51
@mathlib-dependent-issues mathlib-dependent-issues Bot added the blocked-by-other-PR This PR depends on another PR (this label is automatically managed by a bot) label Jul 28, 2026
@mathlib-dependent-issues

Copy link
Copy Markdown

This PR/issue depends on:

marcelolynch and others added 5 commits August 11, 2026 14:25
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>
@mathlib-merge-conflicts mathlib-merge-conflicts Bot added the merge-conflict The PR has a merge conflict with master, and needs manual merging. (this label is managed by a bot) label Aug 14, 2026
@mathlib-merge-conflicts

Copy link
Copy Markdown

This pull request has conflicts, please merge master and resolve them.

…teful

# Conflicts:
#	Mathlib/Tactic/Linter/Style.lean
@github-actions github-actions Bot removed the merge-conflict The PR has a merge conflict with master, and needs manual merging. (this label is managed by a bot) label Aug 14, 2026
@marcelolynch
marcelolynch marked this pull request as ready for review August 14, 2026 23:30
@mathlib-merge-conflicts mathlib-merge-conflicts Bot added the merge-conflict The PR has a merge conflict with master, and needs manual merging. (this label is managed by a bot) label Sep 2, 2026
@mathlib-merge-conflicts

Copy link
Copy Markdown

This pull request has conflicts, please merge master and resolve them.

marcelolynch and others added 2 commits September 1, 2026 21:08
…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>
@github-actions github-actions Bot removed the merge-conflict The PR has a merge conflict with master, and needs manual merging. (this label is managed by a bot) label Sep 2, 2026
@mathlib-merge-conflicts mathlib-merge-conflicts Bot added the merge-conflict The PR has a merge conflict with master, and needs manual merging. (this label is managed by a bot) label Sep 28, 2026
@mathlib-merge-conflicts

Copy link
Copy Markdown

This pull request has conflicts, please merge master and resolve them.

@grunweg grunweg added the LLM-generated PRs with substantial input from LLMs - review accordingly label Oct 7, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blocked-by-other-PR This PR depends on another PR (this label is automatically managed by a bot) LLM-generated PRs with substantial input from LLMs - review accordingly merge-conflict The PR has a merge conflict with master, and needs manual merging. (this label is managed by a bot) t-linter Linter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants