Skip to content

Commit e81e752

Browse files
YaelDillieshargoniXKyle Millerdwrenshakmill
committed
feat: #print sorries, a command to find usage of sorry (#25179)
Adds the `#print sorries` command. Without arguments, prints all sorries used by all declarations in the current file. `#print sorries id1 ... idn` prints all sorries by declarations `id1`, ..., `idn`. If the sorry has source position information, it supports "go to definition" to see exactly where in the source code it was introduced. For convenience, the types of the sorries are pretty printed as well. This also gives `#print sorries in` (like `whatsnew in`) to print all sorries used by the following declaration. The needs from a `#print sorries` command are different from `#print axioms`. Users add `sorry` as a placeholder for unfinished work, and the elaborator adds `sorry` to replace erroneous work. The fact that `sorry` is an axiom is an implementation detail. The command serves the need of finding what should be worked on next. Such a command is a recurrent need of the community, see eg [here](https://leanprover.zulipchat.com/#narrow/channel/217875-Is-there-code-for-X.3F/topic/Hunting.20down.20axioms/with/495448595) on Zulip. This PR combines code from toric and code written at the ICERM "Autoformalization for the Working Mathematician" workshop. Co-authored-by: Henrik Böving <henrik@boeving-net.de> Co-authored-by: Kyle Miller <kymiller@ucsc.edu> Co-authored-by: David Renshaw <dwrenshaw@gmail.com> Co-authored-by: Kyle Miller <kmill31415@gmail.com>
1 parent 7f2eaff commit e81e752

File tree

4 files changed

+379
-0
lines changed

4 files changed

+379
-0
lines changed

Mathlib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6764,6 +6764,7 @@ import Mathlib.Util.MemoFix
67646764
import Mathlib.Util.Notation3
67656765
import Mathlib.Util.PPOptions
67666766
import Mathlib.Util.ParseCommand
6767+
import Mathlib.Util.PrintSorries
67676768
import Mathlib.Util.Qq
67686769
import Mathlib.Util.Simp
67696770
import Mathlib.Util.SleepHeartbeats

Mathlib/Tactic/Common.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ import Mathlib.Tactic.Widget.LibraryRewrite
111111
import Mathlib.Tactic.WLOG
112112
import Mathlib.Util.AssertExists
113113
import Mathlib.Util.CountHeartbeats
114+
import Mathlib.Util.PrintSorries
114115
import Mathlib.Util.TransImports
115116
import Mathlib.Util.WhatsNew
116117

Mathlib/Util/PrintSorries.lean

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/-
2+
Copyright (c) 2025 Henrik Böving, Yaël Dillies, Kyle Miller. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Henrik Böving, Yaël Dillies, Kyle Miller
5+
-/
6+
import Mathlib.Lean.Expr.Basic
7+
8+
/-!
9+
# Tracking uses of `sorry`
10+
11+
This file provides a `#print sorries` command to help find out why a given declaration is not
12+
sorry-free. `#print sorries foo` returns a non-sorry-free declaration `bar` which `foo` depends on,
13+
if such a `bar` exists.
14+
15+
The `#print sorries in CMD` combinator prints all sorries appearing in the declarations defined
16+
by the given command.
17+
18+
## TODO
19+
20+
* Add configuration options. `#print sorries +positions -types` would print file/line/col
21+
information and not print the types.
22+
* Make versions for other axioms/constants.
23+
The `#print sorries` command itself shouldn't be generalized, since `sorry` is a special concept,
24+
representing unfinished proofs, and it has special support for "go to definition", etc.
25+
* Move to ImportGraph?
26+
-/
27+
28+
open Lean Meta Elab Command
29+
30+
namespace Mathlib.PrintSorries
31+
32+
/-- Type of intermediate computation of sorry-tracking. -/
33+
structure State where
34+
/-- The set of already visited declarations. -/
35+
visited : NameSet := {}
36+
/-- The set of `sorry` expressions that have been found.
37+
Note that unlabeled sorries will only be reported in the *first* declaration that uses them,
38+
even if a later definition independently has a direct use of `sorryAx`. -/
39+
sorries : Std.HashSet Expr := {}
40+
/-- The uses of `sorry` that were found. -/
41+
sorryMsgs : Array MessageData := #[]
42+
43+
/--
44+
Collects all uses of `sorry` by the declaration `c`.
45+
It finds all transitive uses as well.
46+
47+
This is a version of `Lean.CollectAxioms.collect` that keeps track of enough information to print
48+
each use of `sorry`.
49+
-/
50+
partial def collect (c : Name) : StateT State MetaM Unit := do
51+
let collectExpr (e : Expr) : StateT State MetaM Unit := do
52+
/-
53+
We assume most declarations do not contain sorry.
54+
The `getUsedConstants` function is very efficient compared to `forEachExpr'`,
55+
since `forEachExpr'` needs to instantiate fvars.
56+
Visiting constants first also guarantees that we attribute sorries to the first
57+
declaration that included it. Recall that `sorry` might appear in the type of a theorem,
58+
which leads to the `sorry` appearing directly in any declarations that use it.
59+
This is one reason we need the `State.sorries` set as well.
60+
The other reason is that we match entire sorry applications,
61+
so `forEachExpr'`'s cache won't prevent over-reporting if `sorry` is a function.
62+
-/
63+
let consts := e.getUsedConstants
64+
consts.forM collect
65+
if consts.contains ``sorryAx then
66+
let visitSorry (e : Expr) : StateT State MetaM Unit := do
67+
unless (← get).sorries.contains e do
68+
let mut msg := m!"{.ofConstName c} has {e}"
69+
if e.isSyntheticSorry then
70+
msg := msg ++ " (from error)"
71+
try
72+
msg := msg ++ " of type" ++ indentExpr (← inferType e)
73+
catch _ => pure ()
74+
msg ← addMessageContext msg
75+
modify fun s =>
76+
{ s with
77+
sorries := s.sorries.insert e
78+
sorryMsgs := s.sorryMsgs.push msg }
79+
Meta.forEachExpr' e fun e => do
80+
if e.isSorry then
81+
if let some _ := isLabeledSorry? e then
82+
visitSorry <| e.getBoundedAppFn (e.getAppNumArgs - 3)
83+
else
84+
visitSorry <| e.getBoundedAppFn (e.getAppNumArgs - 2)
85+
return false
86+
else
87+
-- Otherwise continue visiting subexpressions
88+
return true
89+
let s ← get
90+
unless s.visited.contains c do
91+
modify fun s => { s with visited := s.visited.insert c }
92+
let env ← getEnv
93+
match env.checked.get.find? c with
94+
| some (.axiomInfo v) => collectExpr v.type
95+
| some (.defnInfo v) => collectExpr v.type *> collectExpr v.value
96+
| some (.thmInfo v) => collectExpr v.type *> collectExpr v.value
97+
| some (.opaqueInfo v) => collectExpr v.type *> collectExpr v.value
98+
| some (.quotInfo _) => pure ()
99+
| some (.ctorInfo v) => collectExpr v.type
100+
| some (.recInfo v) => collectExpr v.type
101+
| some (.inductInfo v) => collectExpr v.type *> v.ctors.forM collect
102+
| none => pure ()
103+
104+
/--
105+
Prints all uses of `sorry` inside a list of declarations.
106+
Displayed sorries are hoverable and support "go to definition".
107+
-/
108+
def collectSorries (constNames : Array Name) : MetaM (Array MessageData) := do
109+
let (_, s) ← (constNames.forM collect).run {}
110+
pure s.sorryMsgs
111+
112+
/--
113+
- `#print sorries` prints all sorries that the current module depends on.
114+
- `#print sorries id1 id2 ... idn` prints all sorries that the provided declarations depend on.
115+
- `#print sorries in CMD` prints all the sorries in declarations added by the command.
116+
117+
Displayed sorries are hoverable and support "go to definition".
118+
-/
119+
syntax (name := printSorriesStx) "#print " &"sorries" (ppSpace ident)* : command
120+
121+
/--
122+
Collects sorries in the given constants and logs a message.
123+
-/
124+
def evalCollectSorries (names : Array Name) : CommandElabM Unit := do
125+
let msgs ← liftTermElabM <| collectSorries names
126+
if msgs.isEmpty then
127+
logInfo m!"Declarations are sorry-free!"
128+
else
129+
logInfo <| MessageData.joinSep msgs.toList "\n"
130+
131+
elab_rules : command
132+
| `(#print%$tk1 sorries%$tk2 $idents*) => do
133+
let mut names ← liftCoreM <| idents.flatMapM fun id =>
134+
return (← realizeGlobalConstWithInfos id).toArray
135+
if names.isEmpty then
136+
names ← (← getEnv).checked.get.constants.map₂.foldlM (init := #[]) fun acc name _ =>
137+
return if ← name.isBlackListed then acc else acc.push name
138+
withRef (mkNullNode #[tk1, tk2]) <| evalCollectSorries names
139+
140+
@[inherit_doc printSorriesStx]
141+
syntax "#print " &"sorries" " in " command : command
142+
143+
elab_rules : command
144+
| `(#print%$tk1 sorries%$tk2 in $cmd:command) => do
145+
let oldEnv ← getEnv
146+
try
147+
elabCommand cmd
148+
finally
149+
let newEnv ← getEnv
150+
let names ← newEnv.checked.get.constants.map₂.foldlM (init := #[]) fun acc name _ => do
151+
if oldEnv.constants.map₂.contains name then
152+
return acc
153+
else if ← name.isBlackListed then
154+
return acc
155+
else
156+
return acc.push name
157+
withRef (mkNullNode #[tk1, tk2]) <| evalCollectSorries names
158+
159+
end Mathlib.PrintSorries

MathlibTest/Util/PrintSorries.lean

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import Mathlib.Util.PrintSorries
2+
3+
set_option pp.mvars false
4+
5+
/-!
6+
Direct use of `sorry`
7+
-/
8+
9+
/-- warning: declaration uses 'sorry' -/
10+
#guard_msgs in
11+
theorem thm1 : 1 = 2 := by sorry
12+
13+
/--
14+
info: thm1 has sorry of type
15+
1 = 2
16+
-/
17+
#guard_msgs in
18+
#print sorries thm1
19+
20+
/-!
21+
Indirect use of `sorry`
22+
-/
23+
theorem thm2 : 1 = 2 := thm1
24+
25+
/--
26+
info: thm1 has sorry of type
27+
1 = 2
28+
-/
29+
#guard_msgs in
30+
#print sorries thm2
31+
32+
/-!
33+
Print all uses of `sorry` in the current module.
34+
-/
35+
/--
36+
info: thm1 has sorry of type
37+
1 = 2
38+
-/
39+
#guard_msgs in
40+
#print sorries
41+
42+
/-!
43+
Don't overreport. Remembers that `thm1` already reported the `sorry`.
44+
-/
45+
/--
46+
info: thm1 has sorry of type
47+
1 = 2
48+
-/
49+
#guard_msgs in
50+
#print sorries thm1 thm2
51+
52+
/-!
53+
Reports sorries (indirectly) appearing in the types of theorems.
54+
-/
55+
56+
/-- warning: declaration uses 'sorry' -/
57+
#guard_msgs in
58+
def f (n : Nat) : Nat := sorry
59+
theorem thm3 : f 1 = f 2 := rfl -- (!) This works since it's a fixed `sorry : Nat`
60+
61+
/--
62+
info: f has sorry of type
63+
Nat
64+
-/
65+
#guard_msgs in
66+
#print sorries thm3
67+
68+
/-!
69+
If `sorry` appears in the type of a theorem, when it's used, it reports the theorem
70+
with the `sorry`, even though `sorry` appears in the theorem that used it as well.
71+
-/
72+
/-- warning: declaration uses 'sorry' -/
73+
#guard_msgs in
74+
theorem thm_sorry (n : Nat) : n = sorry := sorry
75+
76+
/-- warning: declaration uses 'sorry' -/
77+
#guard_msgs in
78+
theorem thm_use_it (m n : Nat) : m = n := by
79+
rw [thm_sorry m, thm_sorry n]
80+
81+
/--
82+
info: thm_sorry has sorry of type
83+
Nat
84+
thm_sorry has sorry of type
85+
n = sorry
86+
-/
87+
#guard_msgs in
88+
#print sorries thm_use_it
89+
90+
/-!
91+
Reports synthetic sorries specially.
92+
-/
93+
94+
/-- warning: declaration uses 'sorry' -/
95+
#guard_msgs in
96+
def f' : Nat → Nat := sorry
97+
/--
98+
error: Not a definitional equality: the left-hand side
99+
f' 1
100+
is not definitionally equal to the right-hand side
101+
f' 2
102+
---
103+
error: Type mismatch
104+
rfl
105+
has type
106+
?_ = ?_
107+
but is expected to have type
108+
f' 1 = f' 2
109+
-/
110+
#guard_msgs in
111+
theorem thm3' : f' 1 = f' 2 := rfl -- fails as expected, `f'` is an unknown `sorry : Nat → Nat`
112+
113+
/--
114+
info: f' has sorry of type
115+
Nat → Nat
116+
thm3' has sorry (from error) of type
117+
f' 1 = f' 2
118+
-/
119+
#guard_msgs in
120+
#print sorries thm3'
121+
122+
/-!
123+
Unfolding functions can lead to many copies of `sorry` in a term.
124+
This proof contains 4 sorry terms.
125+
-/
126+
127+
/-- warning: declaration uses 'sorry' -/
128+
#guard_msgs in
129+
theorem thm : True := by
130+
let f : Nat → Nat := sorry
131+
have : f 1 = f 2 := sorry
132+
unfold f at this
133+
change id _ at this
134+
trivial
135+
136+
/--
137+
info: thm has sorry of type
138+
Nat → Nat
139+
thm has sorry of type
140+
f 1 = f 2
141+
-/
142+
#guard_msgs in
143+
#print sorries thm
144+
145+
/-!
146+
Raw, unlabeled sorry.
147+
Its "go to definition" unfortunately goes to `sorryAx` itself.
148+
-/
149+
150+
/-- warning: declaration uses 'sorry' -/
151+
#guard_msgs in
152+
theorem thm' : True := sorryAx _ false
153+
154+
/--
155+
info: thm' has sorry of type
156+
True
157+
-/
158+
#guard_msgs in
159+
#print sorries thm'
160+
161+
/-!
162+
The `sorry` pretty printing can handle free variables.
163+
-/
164+
/-- warning: declaration uses 'sorry' -/
165+
#guard_msgs in def g (α : Type) : α := sorry
166+
167+
/--
168+
info: g has sorry of type
169+
α
170+
-/
171+
#guard_msgs in #print sorries g
172+
173+
/-!
174+
`#print sorries in`
175+
-/
176+
177+
/--
178+
info: in_test_1 has sorry of type
179+
True
180+
---
181+
warning: declaration uses 'sorry'
182+
-/
183+
#guard_msgs in
184+
#print sorries in theorem in_test_1 : True := sorry
185+
186+
/-- info: Declarations are sorry-free! -/
187+
#guard_msgs in
188+
#print sorries in theorem in_test_2 : True := trivial
189+
190+
/-!
191+
### Other sorry-producing commands
192+
193+
Check that `admit` and `stop` are correctly handled
194+
-/
195+
196+
/-- info: thm4 has sorry of type
197+
True
198+
---
199+
warning: declaration uses 'sorry'
200+
---
201+
warning: 'admit' tactic does nothing
202+
203+
Note: This linter can be disabled with `set_option linter.unusedTactic false` -/
204+
#guard_msgs in
205+
#print sorries in
206+
theorem thm4 : True := by admit
207+
208+
/-- info: thm5 has sorry of type
209+
True
210+
---
211+
warning: declaration uses 'sorry'
212+
---
213+
warning: 'stop admit' tactic does nothing
214+
215+
Note: This linter can be disabled with `set_option linter.unusedTactic false` -/
216+
#guard_msgs in
217+
#print sorries in
218+
theorem thm5 : True := by stop admit

0 commit comments

Comments
 (0)