Skip to content

Commit f1ae38a

Browse files
committed
feat: the lambdaSyntax linter (#15896)
The mathlib style guide states clearly that using \lambda for anonymous functions is disallowed, and the fun keyword is preferred. This linter enforces this.
1 parent 9969491 commit f1ae38a

File tree

3 files changed

+175
-7
lines changed

3 files changed

+175
-7
lines changed

Mathlib/Tactic/Linter/Lint.lean

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Linter that checks whether a structure should be in Prop.
3030
-- remark: using `Lean.Meta.isProp` doesn't suffice here, because it doesn't (always?)
3131
-- recognize predicates as propositional.
3232
let isProp ← forallTelescopeReducing (← inferType (← mkConstWithLevelParams declName))
33-
fun _ ty => return ty == .sort .zero
33+
fun _ ty return ty == .sort .zero
3434
if isProp then return none
3535
let projs := (getStructureInfo? (← getEnv) declName).get!.fieldNames
3636
if projs.isEmpty then return none -- don't flag empty structures
@@ -87,18 +87,18 @@ def getIds : Syntax → Array Syntax
8787
| .node _ `Batteries.Tactic.Alias.alias args => args[2:3]
8888
| .node _ ``Lean.Parser.Command.export args => (args[3:4] : Array Syntax).map (·[0])
8989
| stx@(.node _ _ args) =>
90-
((args.attach.map fun ⟨a, _⟩ => getIds a).foldl (· ++ ·) #[stx]).filter (·.getKind == ``declId)
90+
((args.attach.map fun ⟨a, _⟩ getIds a).foldl (· ++ ·) #[stx]).filter (·.getKind == ``declId)
9191
| _ => default
9292

9393
@[inherit_doc linter.dupNamespace]
94-
def dupNamespace : Linter where run := withSetOptionIn fun stx => do
94+
def dupNamespace : Linter where run := withSetOptionIn fun stx do
9595
if Linter.getLinterValue linter.dupNamespace (← getOptions) then
9696
match getIds stx with
9797
| #[id] =>
9898
let ns := (← getScope).currNamespace
9999
let declName := ns ++ (if id.getKind == ``declId then id[0].getId else id.getId)
100100
let nm := declName.components
101-
let some (dup, _) := nm.zip (nm.tailD []) |>.find? fun (x, y) => x == y
101+
let some (dup, _) := nm.zip (nm.tailD []) |>.find? fun (x, y) x == y
102102
| return
103103
Linter.logLint linter.dupNamespace id
104104
m!"The namespace '{dup}' is duplicated in the declaration '{declName}'"
@@ -182,7 +182,7 @@ def findCDot : Syntax → Array Syntax
182182
| stx@(.node _ kind args) =>
183183
let dargs := (args.map findCDot).flatten
184184
match kind with
185-
| ``Lean.Parser.Term.cdot | ``cdotTk=> dargs.push stx
185+
| ``Lean.Parser.Term.cdot | ``cdotTk => dargs.push stx
186186
| _ => dargs
187187
|_ => #[]
188188

@@ -196,7 +196,7 @@ def unwanted_cdot (stx : Syntax) : Array Syntax :=
196196
namespace CDotLinter
197197

198198
@[inherit_doc linter.cdot]
199-
def cdotLinter : Linter where run := withSetOptionIn fun stx => do
199+
def cdotLinter : Linter where run := withSetOptionIn fun stx do
200200
unless Linter.getLinterValue linter.cdot (← getOptions) do
201201
return
202202
if (← MonadState.get).messages.hasErrors then
@@ -247,6 +247,52 @@ initialize addLinter dollarSyntaxLinter
247247

248248
end Style.dollarSyntax
249249

250+
/-!
251+
# The `lambdaSyntax` linter
252+
253+
The `lambdaSyntax` linter is a syntax linter that flags uses of the symbol `λ` to define anonymous
254+
functions, as opposed to the `fun` keyword. These are syntactically equivalent; mathlib style
255+
prefers the latter as it is considered more readable.
256+
-/
257+
258+
/--
259+
The `lambdaSyntax` linter flags uses of the symbol `λ` to define anonymous functions.
260+
This is syntactically equivalent to the `fun` keyword; mathlib style prefers using the latter.
261+
-/
262+
register_option linter.style.lambdaSyntax : Bool := {
263+
defValue := false
264+
descr := "enable the `lambdaSyntax` linter"
265+
}
266+
267+
namespace Style.lambdaSyntax
268+
269+
/--
270+
`findLambdaSyntax stx` extracts from `stx` all syntax nodes of `kind` `Term.fun`. -/
271+
partial
272+
def findLambdaSyntax : Syntax → Array Syntax
273+
| stx@(.node _ kind args) =>
274+
let dargs := (args.map findLambdaSyntax).flatten
275+
match kind with
276+
| ``Parser.Term.fun => dargs.push stx
277+
| _ => dargs
278+
|_ => #[]
279+
280+
@[inherit_doc linter.style.lambdaSyntax]
281+
def lambdaSyntaxLinter : Linter where run := withSetOptionIn fun stx ↦ do
282+
unless Linter.getLinterValue linter.style.lambdaSyntax (← getOptions) do
283+
return
284+
if (← MonadState.get).messages.hasErrors then
285+
return
286+
for s in findLambdaSyntax stx do
287+
if let .atom _ "λ" := s[0] then
288+
Linter.logLint linter.style.lambdaSyntax s[0] m!"\
289+
Please use 'fun' and not 'λ' to define anonymous functions.\n\
290+
The 'λ' syntax is deprecated in mathlib4."
291+
292+
initialize addLinter lambdaSyntaxLinter
293+
294+
end Style.lambdaSyntax
295+
250296
/-! # The "longLine linter" -/
251297

252298
/-- The "longLine" linter emits a warning on lines longer than 100 characters.
@@ -279,7 +325,7 @@ def longLineLinter : Linter where run := withSetOptionIn fun stx ↦ do
279325
else return stx
280326
let sstr := stx.getSubstring?
281327
let fm ← getFileMap
282-
let longLines := ((sstr.getD default).splitOn "\n").filter fun line =>
328+
let longLines := ((sstr.getD default).splitOn "\n").filter fun line
283329
(100 < (fm.toPosition line.stopPos).column)
284330
for line in longLines do
285331
if !(line.containsSubstr "http") then

lakefile.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ abbrev mathlibOnlyLinters : Array LeanOption := #[
2727
`linter.missingEnd, true⟩,
2828
`linter.cdot, true⟩,
2929
`linter.dollarSyntax, true⟩,
30+
`linter.style.lambdaSyntax, true⟩,
3031
`linter.longLine, true⟩,
3132
`linter.oldObtain, true,⟩,
3233
`linter.refine, true⟩,

test/Lint.lean

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Mathlib.Tactic.Linter.Lint
22
import Mathlib.Tactic.ToAdditive
3+
import Mathlib.Order.SetNotation
34

45
-- TODO: the linter also runs on the #guard_msg, so disable it once
56
-- See https://leanprover.zulipchat.com/#narrow/stream/348111-std4/topic/.23guard_msgs.20doesn't.20silence.20warnings/near/423534679
@@ -87,6 +88,126 @@ note: this linter can be disabled with `set_option linter.cdot false`
8788
set_option linter.cdot true in
8889
example : Add Nat where add := (. + ·)
8990

91+
set_option linter.dollarSyntax false in
92+
/--
93+
warning: Please use '<|' instead of '$' for the pipe operator.
94+
note: this linter can be disabled with `set_option linter.dollarSyntax false`
95+
---
96+
warning: Please use '<|' instead of '$' for the pipe operator.
97+
note: this linter can be disabled with `set_option linter.dollarSyntax false`
98+
-/
99+
#guard_msgs in
100+
set_option linter.dollarSyntax true in
101+
attribute [instance] Int.add in
102+
instance (f g : Nat → Nat) : Inhabited Nat where
103+
default := by
104+
· have := 0
105+
· have : Nat := f $ g $ 0
106+
· exact 0
107+
108+
section lambdaSyntaxLinter
109+
110+
set_option linter.style.lambdaSyntax false
111+
112+
/--
113+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
114+
The 'λ' syntax is deprecated in mathlib4.
115+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
116+
-/
117+
#guard_msgs in
118+
set_option linter.style.lambdaSyntax true in
119+
example : ℕ → ℕ := λ _ ↦ 0
120+
121+
/--
122+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
123+
The 'λ' syntax is deprecated in mathlib4.
124+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
125+
-/
126+
#guard_msgs in
127+
set_option linter.style.lambdaSyntax true in
128+
def foo : Bool := by
129+
let _f : ℕ → ℕ := λ _ ↦ 0
130+
exact true
131+
132+
example : ℕ → ℕ := fun n ↦ n - 1
133+
134+
/--
135+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
136+
The 'λ' syntax is deprecated in mathlib4.
137+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
138+
-/
139+
#guard_msgs in
140+
set_option linter.style.lambdaSyntax true in
141+
example : ℕ → ℕ := by exact λ n ↦ 3 * n + 1
142+
143+
/--
144+
warning: declaration uses 'sorry'
145+
---
146+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
147+
The 'λ' syntax is deprecated in mathlib4.
148+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
149+
---
150+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
151+
The 'λ' syntax is deprecated in mathlib4.
152+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
153+
---
154+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
155+
The 'λ' syntax is deprecated in mathlib4.
156+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
157+
-/
158+
#guard_msgs in
159+
set_option linter.style.lambdaSyntax true in
160+
example : ℕ → ℕ → ℕ → ℕ := by
161+
have (n : ℕ) : True := trivial
162+
have : (Set.univ : Set ℕ) = ⋃ (i : ℕ), (Set.iUnion λ j ↦ ({0, j} : Set ℕ)) := sorry
163+
have : ∃ m : ℕ, ⋃ i : ℕ, (Set.univ : Set ℕ) = ∅ := sorry
164+
exact λ _a ↦ fun _b ↦ λ _c ↦ 0
165+
166+
/--
167+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
168+
The 'λ' syntax is deprecated in mathlib4.
169+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
170+
---
171+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
172+
The 'λ' syntax is deprecated in mathlib4.
173+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
174+
---
175+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
176+
The 'λ' syntax is deprecated in mathlib4.
177+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
178+
-/
179+
#guard_msgs in
180+
set_option linter.style.lambdaSyntax true in
181+
example : True := by
182+
have : 0 = 00 = 01 + 3 = 4 := by
183+
refine ⟨by trivial, by
184+
let _f := λ n : ℕ ↦ 0;
185+
have : ℕ := by
186+
· -- comment
187+
· have := λ k : ℕ ↦ -5
188+
· exact 0
189+
refine ⟨by trivial, have := λ k : ℕ ↦ -5; by simp⟩
190+
191+
trivial
192+
193+
-- Code such as the following would require walking the infotree instead:
194+
-- the inner set_option is ignore (in either direction).
195+
-- As this seems unlikely to occur by accident and its use is dubious, we don't worry about this.
196+
/--
197+
warning: Please use 'fun' and not 'λ' to define anonymous functions.
198+
The 'λ' syntax is deprecated in mathlib4.
199+
note: this linter can be disabled with `set_option linter.style.lambdaSyntax false`
200+
-/
201+
#guard_msgs in
202+
set_option linter.style.lambdaSyntax true in
203+
example : ℕ → ℕ := set_option linter.style.lambdaSyntax false in λ _ ↦ 0
204+
205+
set_option linter.style.lambdaSyntax false
206+
#guard_msgs in
207+
example : ℕ → ℕ := set_option linter.style.lambdaSyntax true in λ _ ↦ 0
208+
209+
end lambdaSyntaxLinter
210+
90211
set_option linter.dollarSyntax false in
91212
/--
92213
warning: Please use '<|' instead of '$' for the pipe operator.

0 commit comments

Comments
 (0)