Skip to content

Commit f3b824e

Browse files
committed
feat: check that every file imports Mathlib.Init (#18281)
We add a text-based check to the `lint-style` script, that every file either (transitively) imports Mathlib.Init, or is (transitively) imported by it. Co-authored-by: grunweg <rothgami@math.hu-berlin.de>
1 parent 6a85082 commit f3b824e

File tree

3 files changed

+74
-13
lines changed

3 files changed

+74
-13
lines changed

Mathlib/Init.lean

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Mathlib.Tactic.Linter.DocPrime
2-
import Mathlib.Tactic.Linter.HashCommandLinter
32
import Mathlib.Tactic.Linter.GlobalAttributeIn
3+
import Mathlib.Tactic.Linter.HashCommandLinter
44
import Mathlib.Tactic.Linter.Header
55
-- This file imports Batteries.Tactic.Lint, where the `env_linter` attribute is defined.
66
import Mathlib.Tactic.Linter.Lint
@@ -21,6 +21,9 @@ Here are some general guidelines:
2121
* strong preference for avoiding files that themselves have imports beyond `Lean`, and
2222
any exception to this rule should by accompanied by a comment explaining the transitive imports.
2323
24+
A linter verifies that every file in Mathlib imports `Mathlib.Init`
25+
(perhaps indirectly) --- except for the imports in this file, of course.
26+
2427
## Linters
2528
2629
All syntax linters defined in Mathlib which are active by default are imported here.
@@ -30,7 +33,7 @@ as early as possible.
3033
All linters imported here have no bulk imports;
3134
**Not** imported in this file are
3235
- the text-based linters in `Linters/TextBased.lean`, as they can be imported later
33-
- the `minImports` linter, as that is still disabled by default.
36+
- the `minImports` linter, as that is still disabled by default
3437
- the `haveLet` linter, as it is currently disabled by default
3538
3639
-/

Mathlib/Tactic/Linter/TextBased.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ Return the number of files which had new style errors.
265265
`moduleNames` are all the modules to lint,
266266
`mode` specifies what kind of output this script should produce,
267267
`fix` configures whether fixable errors should be corrected in-place. -/
268-
def lintModules (moduleNames : Array String) (style : ErrorFormat) (fix : Bool) : IO UInt32 := do
268+
def lintModules (moduleNames : Array Lean.Name) (style : ErrorFormat) (fix : Bool) : IO UInt32 := do
269269
-- Read the `nolints` file, with manual exceptions for the linter.
270270
let nolints ← IO.FS.lines ("scripts" / "nolints-style.txt")
271271
let styleExceptions := parseStyleExceptions nolints
@@ -274,7 +274,7 @@ def lintModules (moduleNames : Array String) (style : ErrorFormat) (fix : Bool)
274274
let mut allUnexpectedErrors := #[]
275275
for module in moduleNames do
276276
-- Convert the module name to a file name, then lint that file.
277-
let path := (mkFilePath (module.split (· == '.'))).addExtension "lean"
277+
let path := mkFilePath (module.components.map toString)|>.addExtension "lean"
278278

279279
let (errors, changed) := ← lintFile path styleExceptions
280280
if let some c := changed then

scripts/lint-style.lean

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,77 @@ Released under Apache 2.0 license as described in the file LICENSE.
44
Authors: Michael Rothgang
55
-/
66

7+
import Lean.Elab.ParseImportsFast
8+
import Batteries.Data.String.Basic
79
import Mathlib.Tactic.Linter.TextBased
810
import Cli.Basic
911

1012
/-!
1113
# Text-based style linters
1214
13-
This files defines the `lint-style` executable which runs all text-based style linters.
15+
This file defines the `lint-style` executable which runs all text-based style linters.
1416
The linters themselves are defined in `Mathlib.Tactic.Linter.TextBased`.
1517
16-
In addition, this checks that every file in `scripts` is documented in its top-level README.
18+
In addition, this checks that
19+
- `Mathlib.Init` is (transitively) imported in all of mathlib, and
20+
- every file in `scripts` is documented in its top-level README.
1721
-/
1822

19-
open Cli Mathlib.Linter.TextBased
23+
open Cli Mathlib.Linter.TextBased System.FilePath
24+
25+
/-- Parse all imports in a text file at `path` and return just their names:
26+
this is just a thin wrapper around `Lean.parseImports'`.
27+
Omit `Init (which is part of the prelude). -/
28+
def findImports (path : System.FilePath) : IO (Array Lean.Name) := do
29+
return (← Lean.parseImports' (← IO.FS.readFile path) path.toString)
30+
|>.map (fun imp ↦ imp.module) |>.erase `Init
31+
32+
33+
/-- Check that `Mathlib.Init` is transitively imported in all of Mathlib.
34+
35+
Every file imported in `Mathlib.Init` should in turn import the `Header` linter
36+
(except for the header linter itself, of course).
37+
Return `true` iff there was an error.
38+
-/
39+
def checkInitImports : IO Bool := do
40+
-- Find any file in the Mathlib directory which does not contain any Mathlib import.
41+
-- We simply parse `Mathlib.lean`, as CI ensures this file is up to date.
42+
let allModuleNames := (← findImports "Mathlib.lean").erase `Batteries
43+
let mut modulesWithoutMathlibImports := #[]
44+
let mut importsHeaderLinter := #[]
45+
for module in allModuleNames do
46+
let path := System.mkFilePath (module.components.map fun n ↦ n.toString)|>.addExtension "lean"
47+
let imports ← findImports path
48+
let hasNoMathlibImport := imports.all fun name ↦ name.getRoot != `Mathlib
49+
if hasNoMathlibImport then
50+
modulesWithoutMathlibImports := modulesWithoutMathlibImports.push module
51+
if imports.contains `Mathlib.Tactic.Linter.Header then
52+
importsHeaderLinter := importsHeaderLinter.push module
53+
54+
-- Every file importing the `header` linter should be imported in `Mathlib/Init.lean` itself.
55+
-- (Downstream files should import `Mathlib.Init` and not the header linter.)
56+
-- The only exception are auto-generated import-only files.
57+
let initImports ← findImports ("Mathlib" / "Init.lean")
58+
let mismatch := importsHeaderLinter.filter (fun mod ↦
59+
![`Mathlib, `Mathlib.Tactic, `Mathlib.Init].contains mod && !initImports.contains mod)
60+
-- This file is transitively imported by `Mathlib.Init`.
61+
|>.erase `Mathlib.Tactic.DeclarationNames
62+
if mismatch.size > 0 then
63+
IO.eprintln s!"error: the following {mismatch.size} module(s) import the `header` linter \
64+
directly, but should import Mathlib.Init instead: {mismatch}\n\
65+
The `header` linter is included in Mathlib.Init, and every file in Mathlib \
66+
should import Mathlib.Init.\nPlease adjust the imports accordingly."
67+
return true
68+
69+
-- Now, it only remains to check that every module (except for the Header linter itself)
70+
-- imports some file in Mathlib.
71+
let missing := modulesWithoutMathlibImports.erase `Mathlib.Tactic.Linter.Header
72+
if missing.size > 0 then
73+
IO.eprintln s!"error: the following {missing.size} module(s) do not import Mathlib.Init: \
74+
{missing}"
75+
return true
76+
return false
2077

21-
open System.FilePath
2278

2379
/-- Verifies that every file in the `scripts` directory is documented in `scripts/README.md`.
2480
Return `True` if there are undocumented scripts, otherwise `False`. -/
@@ -40,19 +96,21 @@ def allScriptsDocumented : IO Bool := do
4096
{String.intercalate "," undocumented.toList}"
4197
return undocumented.size == 0
4298

99+
43100
/-- Implementation of the `lint-style` command line program. -/
44101
def lintStyleCli (args : Cli.Parsed) : IO UInt32 := do
45102
let style : ErrorFormat := match args.hasFlag "github" with
46103
| true => ErrorFormat.github
47104
| false => ErrorFormat.humanReadable
48105
let fix := args.hasFlag "fix"
49106
-- Read all module names to lint.
50-
let mut allModules := #[]
107+
let mut allModuleNames := #[]
51108
for s in ["Archive.lean", "Counterexamples.lean", "Mathlib.lean"] do
52-
allModules := allModules.append ((← IO.FS.lines s).map (·.stripPrefix "import "))
53-
-- Note: since we manually add "Batteries" to "Mathlib.lean", we remove it here manually.
54-
allModules := allModules.erase "Batteries"
55-
let mut numberErrors ← lintModules allModules style fix
109+
allModuleNames := allModuleNames.append (← findImports s)
110+
-- Note: since "Batteries" is added explicitly to "Mathlib.lean", we remove it here manually.
111+
allModuleNames := allModuleNames.erase `Batteries
112+
let mut numberErrors ← lintModules allModuleNames style fix
113+
if ← checkInitImports then numberErrors := numberErrors + 1
56114
if !(← allScriptsDocumented) then numberErrors := numberErrors + 1
57115
-- If run with the `--fix` argument, return a zero exit code.
58116
-- Otherwise, make sure to return an exit code of at most 125,

0 commit comments

Comments
 (0)