Skip to content

Commit a0d161d

Browse files
committed
feat: environment linter for definitions containing an underscore (#34519)
This is almost certainly a violation of the naming convention. In the medium-term, this should become a syntax linter: at the moment, there are too many exceptions in mathlib. Making this an environment linter allows auto-generating a list of existing exceptions. We track the number of violations as a technical debt metric.
1 parent fbe2035 commit a0d161d

3 files changed

Lines changed: 697 additions & 1 deletion

File tree

Mathlib/Tactic/Linter/Style.lean

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public meta import Lean.Server.InfoUtils
1212
public meta import Mathlib.Tactic.Linter.Header -- shake: keep
1313
public import Lean.Parser.Command
1414
public import Mathlib.Tactic.DeclarationNames
15+
public import Batteries.Tactic.Lint.Basic
1516

1617
/-!
1718
## Style linters
@@ -38,6 +39,10 @@ This file defines the following linters:
3839
- the `openClassical` linter checks for `open (scoped) Classical` statements which are not
3940
scoped to a single declaration
4041
- the `show` linter checks for `show`s that change the goal and should be replaced by `change`
42+
- the `nameCheck` linter checks for declarations whose names are in non-standard style, such as
43+
by containing a double underscore. The `defsWithUnderscore` environment linter checks for
44+
definitions whose name contains an underscore: that is also very likely to be a violation of
45+
mathlib's naming convention.
4146
4247
All of these linters are enabled in mathlib by default, but disabled globally
4348
since they enforce conventions which are inherently subjective.
@@ -518,6 +523,51 @@ def doubleUnderscore : Linter where run := withSetOptionIn fun stx => do
518523

519524
initialize addLinter doubleUnderscore
520525

526+
/-- Check `name` is a `Name` containing an underscore that should be linted against
527+
by the `defsWithUnderscore` linter. Namely, we do not lint
528+
* names ending in an underscore, or in a namespace ending with an underscore,
529+
* names containing guillemets `«»` (these tend to be `term<something>` declarations,
530+
i.e. internal names for notation, not user-facing commands),
531+
* names with a component starting with `term` (e.g. `Nat.term_!`)
532+
* names starting with `Mathlib.Tactic`, `Mathlib.Parser` or containing a `Simps` component
533+
(these are probably custom simps projections, i.e. affect how `simps` names its auto-generated
534+
lemmas: we usually prefer a generated name `coe_support` over `coeSupport`, which requires a
535+
projection named `coe_support`)
536+
* names that end in `_<number>` or in `_mathlib` (these tend to be autogenerated instance names),
537+
* library notes.
538+
-/
539+
public def isBadNameWithUnderscore (name : Name) : Bool := Id.run do
540+
let s := name.toString
541+
let declName := name
542+
let last := declName.components.getLast?.getD `Default |>.toString
543+
if last.endsWith '_' ||
544+
s.contains '«' || declName.components.any (·.toString.startsWith "term") ||
545+
(`LibraryNote).isPrefixOf declName ||
546+
(`Mathlib.Tactic).isPrefixOf declName || (`Mathlib.Parser).isPrefixOf declName ||
547+
declName.components.any (· == `Simps) ||
548+
last.endsWith "_1" || last.endsWith "_2" || last.endsWith "_mathlib" ||
549+
declName.components.any (·.toString.endsWith '_') then
550+
return false
551+
if declName.toString.contains "_" then return true
552+
else return false
553+
554+
open Batteries.Tactic.Lint in
555+
/-- Linter that checks for definitions whose name contains an underscore:
556+
such names violate the naming convention. -/
557+
@[env_linter] public def defsWithUnderscore : Batteries.Tactic.Lint.Linter where
558+
noErrorsFound := "no definitions with an underscore in their name found."
559+
errorsFound := "FOUND definitions with an underscore in their name."
560+
test declName := do
561+
unless ((← getEnv).find? declName).get!.isDefinition && !(← isAutoDecl declName) do return none
562+
-- We also exclude simprocs: these should be named like normal lemmas.
563+
-- check if their type is `Lean.Meta.Simp.Simproc`.
564+
if ((← getEnv).find? declName).get!.type.isConstOf `Lean.Meta.Simp.Simproc then return none
565+
if isBadNameWithUnderscore declName then
566+
return m!"The definition `{declName}` contains an underscore. \
567+
This almost surely violates mathlib's naming convention; \
568+
use lowerCamelCase or UpperCamelCase instead."
569+
else return none
570+
521571
end Style.nameCheck
522572

523573
/-! ### The "openClassical" linter -/

MathlibTest/LintStyle.lean

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,36 @@ info: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
447447
#check " \" \
448448
"
449449

450+
/-! Tests for the `nameCheck` linter -/
451+
section
452+
453+
open Mathlib.Linter.Style.nameCheck
454+
455+
/- Unit tests for the `defsWithUnderscore` linter. -/
456+
#guard isBadNameWithUnderscore `Foo == false
457+
#guard isBadNameWithUnderscore `fooBarBaz == false
458+
#guard isBadNameWithUnderscore `foo_bar == true
459+
#guard isBadNameWithUnderscore `_fooFoo == true
460+
#guard isBadNameWithUnderscore `Foo._bar == true
461+
-- A namespace `Mathlib` in the middle does not silence the linter: we only test for a *prefix*
462+
-- `Mathlib`, `Mathlib.Parser` or `Mathlib.Tactic`.
463+
#guard isBadNameWithUnderscore `Nat.Mathlib.foo_bar == true
464+
#guard isBadNameWithUnderscore `Mathlib.Parser.foo_bar == false
465+
#guard isBadNameWithUnderscore `Mathlib.Tactic.foo_bar == false
466+
#guard isBadNameWithUnderscore `AlgebraicGeometry.Scheme.IdealSheafData.Simps.coe_support == false
467+
468+
#guard isBadNameWithUnderscore `Mathlib.Mat._Foo == true
469+
#guard isBadNameWithUnderscore `Mathlib.foo_ == false
470+
#guard isBadNameWithUnderscore `«termXYZ» == false
471+
#guard isBadNameWithUnderscore `ExteriorAlgebraterm⋀[_]^_» == false
472+
#guard isBadNameWithUnderscore `Nat.term_! == false
473+
#guard isBadNameWithUnderscore `Nat.foo_bar2 == true
474+
#guard isBadNameWithUnderscore `Nat.fooBar_2 == false
475+
#guard isBadNameWithUnderscore `Nat.foo_bar_2 == false
476+
#guard isBadNameWithUnderscore `LibraryNote.coercion_into_rings == false
477+
478+
end
479+
450480
/- Tests for the `openClassical` linter -/
451481
section openClassical
452482

0 commit comments

Comments
 (0)