Zulip Chat Archive
Stream: general
Topic: Checking which axioms are used in a project
Riccardo Brasca (Nov 10 2025 at 19:25):
Is there a linter or something similar to check that an axioms is not used in a whole project? By this I mean that if I run #print axioms foo for all foo in the project then axiom bar is not shown.
For a little of context, bar is Classical.choice, and, in the case it matters, the project actually contains one specific axiom. If you are curious I am working on Synthetic Differential Geometry, a theory that is "anticlassical" (I know Lean + mathlib is probably not the best proof assistant for this, but I am curious to see if something nontrivial can be done) and so I need to be sure that choice is never used.
Damiano Testa (Nov 10 2025 at 19:33):
I did not use this in a while, but there used to be .
Sebastian Ullrich (Nov 10 2025 at 19:38):
nanoda has an option for that: https://github.com/ammkrn/nanoda_lib?tab=readme-ov-file#configuration-options:~:text=The%20%22permitted_axioms%22%20list%20is%20where%20users%20specify%20the%20axioms%20an%20export%20file%20is%20permitted%20to%20use.
Riccardo Brasca (Nov 10 2025 at 19:49):
Thanks! Learning how to use an external checker seems more complicated than checking the last declaration by hand (hoping it uses everything...), but it's very nice that is doable.
Riccardo Brasca (Nov 10 2025 at 21:38):
@Damiano Testa of course being a couple of moths old your code does not work anymore. Do you mind to have a look? It is probable simple to fix it (I am trying, but my metaprogramming skills are very limited)
Damiano Testa (Nov 10 2025 at 21:39):
I'll take a look right now!
Riccardo Brasca (Nov 10 2025 at 21:40):
ah, maybe it is enough to use Linter.getLinterOptions.
Riccardo Brasca (Nov 10 2025 at 21:42):
Yes, it works now, thanks!
Damiano Testa (Nov 10 2025 at 21:43):
Great, I was going to mention that as well!
Damiano Testa (Nov 10 2025 at 21:44):
I also updated the code snippet in the other thread.
Riccardo Brasca (Nov 10 2025 at 22:04):
In case someone else is interested, it's enough to add weak.linter.verbose.detectClassical = true to the [leanOptions] section of lakefile.toml and the linter works perfectly. (In my case I modified it to not say anything on declaration that don't depend on choice).
Riccardo Brasca (Jan 23 2026 at 17:56):
@Damiano Testa do you have an idea on how to make this work using the new module system? Currently, I am using this version
/-
This file has been written essentially ba Damiano Testa.
-/
module
public meta import Lean.Util.CollectAxioms
public meta import Mathlib.Tactic.DeclarationNames
public meta import Mathlib.Init
/-!
# The "detectClassical" linter
The "detectClassical" linter emits a warning on declarations that depend on the `Classical.choice`
and/or `sorryAx`.
-/
public meta section
open Lean Elab Linter Command
namespace Mathlib.Linter
/--
The "detectClassical" linter emits a warning on declarations that depend on the `Classical.choice`
axiom.
-/
register_option linter.detectClassical : Bool := {
defValue := true
descr := "enable the detectClassical linter"
}
namespace DetectClassical
@[inherit_doc Mathlib.Linter.linter.detectClassical]
def detectClassicalLinter : Linter where run := withSetOptionIn fun stx โฆ do
unless Linter.getLinterValue linter.detectClassical (โ getLinterOptions) do
return
if (โ get).messages.hasErrors then
return
let nms := (โ getNamesFrom (stx.getPos?.getD default)).filter (! ยท.getId.isInternal)
for constStx in nms do
let constName := constStx.getId
let axioms โ collectAxioms constName
if !axioms.contains `Classical.choice && !axioms.contains `sorryAx then return
else
if !axioms.contains `Classical.choice then
Linter.logLint linter.detectClassical constStx m!"'{constName}' depends on 'sorry'.\n"
else
if !axioms.contains `sorryAx then
Linter.logLint linter.detectClassical constStx m!"'{constName}' depends on 'choice'.\n"
else Linter.logLint linter.detectClassical constStx m!"'{constName}' depends on
'sorry and choice'.\n"
initialize addLinter detectClassicalLinter
end DetectClassical
end Mathlib.Linter
but it seems to only understand sorryAx. Indeed I inspected axioms in a random declaration that depends on the axiom of choice because of grind but Classical.choice doesn't show up. Note that I know it depends on choice since if I do
#print axiom foo
in a file that is not a module I see the axiom (in a module #print axioms doesn't work at all :frown: ).
Damiano Testa (Jan 23 2026 at 18:38):
I do not know how to access axiom information from a module. The lnter seems to work on non-modules, though, right?
Damiano Testa (Jan 23 2026 at 18:40):
I suspect that the reason this is not working on modules is the same reason why #print axioms also does not work with the module system.
Riccardo Brasca (Jan 24 2026 at 09:31):
OK, I will just use it in a non-module. Thanks!
Riccardo Brasca (Feb 16 2026 at 17:39):
Another related question: @Damiano Testa do you think is it possible to count how many declarations depend on the axiom of choice in the current environment? I would like to have this number for mathlib and I don't care if it takes a couple of hours (or even a day, but if it takes one month it is a little slow).
Paul Lezeau (Feb 16 2026 at 19:04):
That seems very doable! Iโm not at my computer now but I can send a script to do that as soon as Iโm back:)
Riccardo Brasca (Feb 16 2026 at 19:05):
Thanks!
Paul Lezeau (Feb 16 2026 at 22:17):
This should do the trick:
import Mathlib
open Lean
run_cmd do
let env โ getEnv
let mut count := 0
for (constName, _) in env.constants do
let axioms โ collectAxioms constName
if axioms.contains `Classical.choice then count := count + 1
Lean.logInfo m!"Found {count} constants depending on Classical.choice"
Riccardo Brasca (Feb 16 2026 at 22:28):
Thanks! Let me test it with a very light import to see how long does it take.
Robin Arnez (Feb 16 2026 at 22:50):
This might be a bit more reasonable in terms of performance:
import Mathlib
open Lean
structure State where
visited : NameMap Bool := {}
abbrev M := ReaderT Environment <| StateM (NameMap Bool)
partial def collect (c : Name) : M Bool := do
let collectExpr (e : Expr) : M Bool := e.getUsedConstants.anyM collect
let s โ get
if let some res := s.find? c then
return res
let env โ read
modify fun s => s.insert c false
let res โ match env.checked.get.find? c with
| some (ConstantInfo.axiomInfo v) => collectExpr v.type
| some (ConstantInfo.defnInfo v) => collectExpr v.type <||> collectExpr v.value
| some (ConstantInfo.thmInfo v) => collectExpr v.type <||> collectExpr v.value
| some (ConstantInfo.opaqueInfo v) => collectExpr v.type <||> collectExpr v.value
| some (ConstantInfo.quotInfo _) => pure false
| some (ConstantInfo.ctorInfo v) => collectExpr v.type
| some (ConstantInfo.recInfo v) => collectExpr v.type
| some (ConstantInfo.inductInfo v) => collectExpr v.type <||> v.ctors.anyM collect
| none => pure false
modify fun s => s.insert c res
return res
partial def collectAll : M Nat := do
modify fun s => s.insert ``Classical.choice true
let env โ read
let mut count := 0
for (constName, _) in env.constants do
let hasChoice โ collect constName
if hasChoice then count := count + 1
return count
run_cmd do
let env โ getEnv
let count := collectAll.run env |>.run' {}
Lean.logInfo m!"Found {count} constants depending on Classical.choice"
Riccardo Brasca (Feb 16 2026 at 22:51):
Oh yes, the other one is still running and this has already finished!
Riccardo Brasca (Feb 16 2026 at 22:51):
(on a light import)
Riccardo Brasca (Feb 16 2026 at 22:54):
It seems we have 386779 declaration that depend on choice, and 707013 in total (this gives a little less than 55%).
It's exactly what I wanted, thanks!
Last updated: Feb 28 2026 at 14:05 UTC