Skip to content

Commit 34a8876

Browse files
grunwegParcly-Taxel
andcommitted
feat(lint-style): rewrite the 'broad imports' linters in Lean (#14059)
This time, the error codes are intentionally changed, as the old ones feel actively misleading. Co-authored-by: Jeremy Tan Jie Rui <reddeloostw@gmail.com>
1 parent 85a75af commit 34a8876

File tree

2 files changed

+56
-13
lines changed

2 files changed

+56
-13
lines changed

Mathlib/Tactic/Linter/TextBased.lean

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ further linters will be ported in subsequent PRs.
2222

2323
open Lean Elab System
2424

25+
/-- Different kinds of "broad imports" that are linted against. -/
26+
inductive BroadImports
27+
/-- Importing the entire "Mathlib.Tactic" folder -/
28+
| TacticFolder
29+
/-- Importing any module in `Lake`, unless carefully measured
30+
This has caused unexpected regressions in the past. -/
31+
| Lake
32+
deriving BEq
33+
2534
/-- Possible errors that text-based linters can report. -/
2635
-- We collect these in one inductive type to centralise error reporting.
2736
inductive StyleError where
@@ -33,11 +42,14 @@ inductive StyleError where
3342
/-- The bare string "Adaptation note" (or variants thereof): instead, the
3443
#adaptation_note command should be used. -/
3544
| adaptationNote
45+
/-- Lint against "too broad" imports, such as `Mathlib.Tactic` or any module in `Lake`
46+
(unless carefully measured) -/
47+
| broadImport (module : BroadImports)
3648
/-- The current file was too large: this error contains the current number of lines
3749
as well as a size limit (slightly larger). On future runs, this linter will allow this file
3850
to grow up to this limit. -/
3951
| fileTooLong (number_lines : ℕ) (new_size_limit : ℕ) : StyleError
40-
deriving BEq
52+
deriving BEq
4153

4254
/-- How to format style errors -/
4355
inductive ErrorFormat
@@ -59,6 +71,12 @@ def StyleError.errorMessage (err : StyleError) (style : ErrorFormat) : String :=
5971
"Authors line should look like: 'Authors: Jean Dupont, Иван Иванович Иванов'"
6072
| StyleError.adaptationNote =>
6173
"Found the string \"Adaptation note:\", please use the #adaptation_note command instead"
74+
| StyleError.broadImport BroadImports.TacticFolder =>
75+
"Files in mathlib cannot import the whole tactic folder"
76+
| StyleError.broadImport BroadImports.Lake =>
77+
"In the past, importing 'Lake' in mathlib has led to dramatic slow-downs of the linter (see \
78+
e.g. mathlib4#13779). Please consider carefully if this import is useful and make sure to \
79+
benchmark it. If this is fine, feel free to allow this linter."
6280
| StyleError.fileTooLong current_size size_limit =>
6381
match style with
6482
| ErrorFormat.github =>
@@ -74,14 +92,15 @@ def StyleError.errorCode (err : StyleError) : String := match err with
7492
| StyleError.copyright _ => "ERR_COP"
7593
| StyleError.authors => "ERR_AUT"
7694
| StyleError.adaptationNote => "ERR_ADN"
95+
| StyleError.broadImport _ => "ERR_IMP"
7796
| StyleError.fileTooLong _ _ => "ERR_NUM_LIN"
7897

7998
/-- Context for a style error: the actual error, the line number in the file we're reading
8099
and the path to the file. -/
81100
structure ErrorContext where
82101
/-- The underlying `StyleError`-/
83102
error : StyleError
84-
/-- The line number of the error -/
103+
/-- The line number of the error (1-based) -/
85104
lineNumber : ℕ
86105
/-- The path to the file which was linted -/
87106
path : FilePath
@@ -139,6 +158,12 @@ def parse?_errorContext (line : String) : Option ErrorContext := Id.run do
139158
| "ERR_COP" => some (StyleError.copyright "")
140159
| "ERR_AUT" => some (StyleError.authors)
141160
| "ERR_ADN" => some (StyleError.adaptationNote)
161+
| "ERR_IMP" =>
162+
-- XXX tweak exceptions messages to ease parsing?
163+
if (error_message.get! 0).containsSubstr "tactic" then
164+
some (StyleError.broadImport BroadImports.TacticFolder)
165+
else
166+
some (StyleError.broadImport BroadImports.Lake)
142167
| "ERR_NUM_LIN" =>
143168
-- Parse the error message in the script. `none` indicates invalid input.
144169
match (error_message.get? 0, error_message.get? 3) with
@@ -232,6 +257,34 @@ def adaptationNoteLinter : TextbasedLinter := fun lines ↦ Id.run do
232257
lineNumber := lineNumber + 1
233258
return errors
234259

260+
/-- Lint a collection of input strings if one of them contains an unnecessarily broad import. -/
261+
def broadImportsLinter : TextbasedLinter := fun lines ↦ Id.run do
262+
let mut errors := Array.mkEmpty 0
263+
-- All import statements must be placed "at the beginning" of the file:
264+
-- we can have any number of blank lines, imports and single or multi-line comments.
265+
-- Doc comments, however, are not allowed: there is no item they could document.
266+
let mut inDocComment : Bool := False
267+
let mut lineNumber := 1
268+
for line in lines do
269+
if inDocComment then
270+
if line.endsWith "-/" then
271+
inDocComment := False
272+
else
273+
-- If `line` is just a single-line comment (starts with "--"), we just continue.
274+
if line.startsWith "/-" then
275+
inDocComment := True
276+
else if let some (rest) := line.dropPrefix? "import " then
277+
-- If there is any in-line or beginning doc comment on that line, trim that.
278+
-- Small hack: just split the string on space, "/" and "-":
279+
-- none of these occur in module names, so this is safe.
280+
if let some name := ((toString rest).split (" /-".contains ·)).head? then
281+
if name == "Mathlib.Tactic" then
282+
errors := errors.push (StyleError.broadImport BroadImports.TacticFolder, lineNumber)
283+
else if name == "Lake" || name.startsWith "Lake." then
284+
errors := errors.push (StyleError.broadImport BroadImports.Lake, lineNumber)
285+
lineNumber := lineNumber + 1
286+
return errors
287+
235288
/-- Whether a collection of lines consists *only* of imports, blank lines and single-line comments.
236289
In practice, this means it's an imports-only file and exempt from almost all linting. -/
237290
def isImportsOnlyFile (lines : Array String) : Bool :=

scripts/lint-style.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@
3939

4040
ERR_MOD = 2 # module docstring
4141
ERR_LIN = 3 # line length
42-
ERR_TAC = 9 # imported Mathlib.Tactic
43-
ERR_TAC2 = 10 # imported Lake in Mathlib
4442
ERR_IBY = 11 # isolated by
4543
ERR_IWH = 22 # isolated where
4644
ERR_DOT = 12 # isolated or low focusing dot
@@ -67,11 +65,7 @@
6765
elif errno == "ERR_LIN":
6866
exceptions += [(ERR_LIN, path, None)]
6967
elif errno == "ERR_ADN":
70-
pass # maintained by the Lean style linter now
71-
elif errno == "ERR_TAC":
72-
exceptions += [(ERR_TAC, path, None)]
73-
elif errno == "ERR_TAC2":
74-
exceptions += [(ERR_TAC2, path, None)]
68+
pass # maintained by the Lean style Linter now
7569
elif errno == "ERR_NUM_LIN":
7670
pass # maintained by the Lean style linter now
7771
else:
@@ -352,10 +346,6 @@ def format_errors(errors):
352346
output_message(path, line_nr, "ERR_MOD", "Module docstring missing, or too late")
353347
if errno == ERR_LIN:
354348
output_message(path, line_nr, "ERR_LIN", "Line has more than 100 characters")
355-
if errno == ERR_TAC:
356-
output_message(path, line_nr, "ERR_TAC", "Files in mathlib cannot import the whole tactic folder")
357-
if errno == ERR_TAC2:
358-
output_message(path, line_nr, "ERR_TAC2", "In the past, importing 'Lake' in mathlib has led to dramatic slow-downs of the linter (see e.g. mathlib4#13779). Please consider carefully if this import is useful and make sure to benchmark it. If this is fine, feel free to allow this linter.")
359349
if errno == ERR_IBY:
360350
output_message(path, line_nr, "ERR_IBY", "Line is an isolated 'by'")
361351
if errno == ERR_IWH:

0 commit comments

Comments
 (0)