Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add #print opaques command #966

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Batteries.lean
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import Batteries.Tactic.NoMatch
import Batteries.Tactic.OpenPrivate
import Batteries.Tactic.PermuteGoals
import Batteries.Tactic.PrintDependents
import Batteries.Tactic.PrintOpaques
import Batteries.Tactic.PrintPrefix
import Batteries.Tactic.SeqFocus
import Batteries.Tactic.ShowUnused
Expand Down
95 changes: 95 additions & 0 deletions Batteries/Tactic/PrintOpaques.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import Lean.Elab.Command
import Lean.Util.FoldConsts

open Lean Elab Command

namespace Batteries.Tactic.CollectOpaques

/-- Collects the result of a `CollectOpaques` query. -/
structure State where
/-- The set visited constants. -/
visited : NameSet := {}
/-- The collected opaque defs. -/
opaques : Array Name := #[]

/-- The monad used by `CollectOpaques`. -/
abbrev M := ReaderT Environment <| StateT State MetaM

/-- Collect the results for a given constant. -/
partial def collect (c : Name) : M Unit := do
let collectExpr (e : Expr) : M Unit := e.getUsedConstants.forM collect
let s ← get
unless s.visited.contains c do
modify fun s => { s with visited := s.visited.insert c }
let env ← read
match env.find? c with
| some (ConstantInfo.ctorInfo _)
| some (ConstantInfo.recInfo _)
| some (ConstantInfo.inductInfo _)
| some (ConstantInfo.quotInfo _) =>
pure ()
| some (ConstantInfo.defnInfo v)
| some (ConstantInfo.thmInfo v) =>
unless ← Meta.isProp v.type do collectExpr v.value
| some (ConstantInfo.axiomInfo v)
| some (ConstantInfo.opaqueInfo v) =>
unless ← Meta.isProp v.type do
modify fun s => { s with opaques := s.opaques.push c }
| none =>
modify fun s => { s with opaques := s.opaques.push c }

end CollectOpaques

/--
The command `#print opaques X` prints all opaque and partial definitions that `X` depends on.

For example, the follwing command prints all opaque and partial definitions that `Classical.choose`
depends on.
```
#print opaques Classical.choose
```
The output is:
```
'Classical.choose' depends on opaque or partial definitions: [Classical.choice]
```

Opaque and partial definitions that occur in a computationally irrelevant context are ignored.
For example,
```
#print opaques Classical.em
```
outputs `'Classical.em' does not use any opaque or partial definitions`. But
```
#print axioms Classical.em
```
reveals that `'Classical.em' depends on axioms: [propext, Classical.choice, Quot.sound]`. The
reason is that `Classical.em` is a `Prop` and it is itself computationally irrelevant.

Axioms are not the only opaque definitions. In fact, the outputs of `#print axioms` and
`#print opaques` are often quite different.
```
#print opaques Std.HashMap.insert
```
show that `'Std.HashMap.insert' depends on opaque or partial definitions:
[System.Platform.getNumBits, UInt64.toUSize]`. But
```
#print axioms Std.HashMap.insert
```
gives `'Std.HashMap.insert' depends on axioms: [propext, Classical.choice, Quot.sound]`. The axioms
`propext` and `Quot.sound` are computationally irrelevant. The axiom `Classical.choice` is
computationally relevant. The reason it does not appear in the list of opaques is that it is only
used inside proofs of propositions, so it is not used in a computationally relevant context.
-/
elab "#print" &"opaques" name:ident : command => do
let constName ← liftCoreM <| realizeGlobalConstNoOverloadWithInfo name
let env ← getEnv
let (_, s) ← liftTermElabM <| ((CollectOpaques.collect constName).run env).run {}
if s.opaques.isEmpty then
logInfo m!"'{constName}' does not use any opaque or partial definitions"
else
logInfo m!"'{constName}' depends on opaque or partial definitions: {s.opaques.toList}"
40 changes: 40 additions & 0 deletions test/print_opaques.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Batteries.Tactic.PrintOpaques

partial def foo : Unit → Nat := foo
def bar : Unit → Nat := foo

/--
info: 'bar' depends on opaque or partial definitions: [foo]
-/
#guard_msgs in
#print opaques bar

opaque qux : Nat
def quux : Bool := qux == 0

/--
info: 'quux' depends on opaque or partial definitions: [qux]
-/
#guard_msgs in
#print opaques quux

/-! Examples from the documentation. -/

/--
info: 'Classical.choose' depends on opaque or partial definitions: [Classical.choice]
-/
#guard_msgs in
#print opaques Classical.choose

/--
info: 'Classical.em' does not use any opaque or partial definitions
-/
#guard_msgs in
#print opaques Classical.em

/--
info: 'Std.HashMap.insert' depends on opaque or partial definitions: [System.Platform.getNumBits,
UInt64.toUSize]
-/
#guard_msgs in
#print opaques Std.HashMap.insert