Skip to content

Commit 481c60b

Browse files
committed
fix
1 parent d1c7eb7 commit 481c60b

File tree

4 files changed

+62
-14
lines changed

4 files changed

+62
-14
lines changed

ImportGraph/RequiredModules.lean

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,31 +123,40 @@ partial def Environment.transitivelyRequiredModules' (env : Environment) (module
123123
CoreM (NameMap NameSet) := do
124124
let N := env.header.moduleNames.size
125125
let mut c2m : NameMap (BitVec N) := {}
126+
let mut pushed : NameSet := {}
126127
let mut result : NameMap NameSet := {}
127128
for m in modules do
128129
if verbose then
129130
IO.println s!"Processing module {m}"
130131
let mut r : BitVec N := 0
131132
for n in env.header.moduleData[(env.header.moduleNames.getIdx? m).getD 0]!.constNames do
133+
if ! n.isInternal then
132134
-- This is messy: Mathlib is big enough that writing a recursive function causes a stack overflow.
133135
-- So we use an explicit stack instead. We visit each constant twice:
134136
-- once to record the constants transitively used by it,
135137
-- and again to record the modules which defined those constants.
136-
let mut stack : Array (ConstantInfo × Option NameSet) := #[⟨← getConstInfo n, none⟩]
138+
let mut stack : List (Name × Option NameSet) := [⟨n, none⟩]
139+
pushed := pushed.insert n
137140
while !stack.isEmpty do
138-
let (ci, used?) := stack.back
139-
stack := stack.pop
140-
match used? with
141-
| none =>
142-
if !c2m.contains ci.name then
143-
let used := ci.getUsedConstantsAsSet
144-
stack := stack.push ⟨ci, some used⟩
145-
for u in used do
146-
if !c2m.contains u then
147-
stack := stack.push ⟨← getConstInfo u, none⟩
148-
| some used =>
149-
let transitivelyUsed : BitVec N := used.fold (init := toBitVec used) (fun s u => s ||| ((c2m.find? u).getD 0))
150-
c2m := c2m.insert ci.name transitivelyUsed
141+
match stack with
142+
| [] => panic! "Stack is empty"
143+
| (c, used?) :: tail =>
144+
stack := tail
145+
match used? with
146+
| none =>
147+
if !c2m.contains c then
148+
let used := (← getConstInfo c).getUsedConstantsAsSet
149+
stack := ⟨c, some used⟩ :: stack
150+
for u in used do
151+
if !pushed.contains u then
152+
stack := ⟨u, none⟩ :: stack
153+
pushed := pushed.insert u
154+
| some used =>
155+
let usedModules : NameSet :=
156+
used.fold (init := {}) (fun s u => if let some m := env.getModuleFor? u then s.insert m else s)
157+
let transitivelyUsed : BitVec N :=
158+
used.fold (init := toBitVec usedModules) (fun s u => s ||| ((c2m.find? u).getD 0))
159+
c2m := c2m.insert c transitivelyUsed
151160
r := r ||| ((c2m.find? n).getD 0)
152161
result := result.insert m (toNameSet r)
153162
return result
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import ImportGraph.Imports
2+
3+
open Lean
4+
5+
def Core.withImportModules (modules : Array Name) {α} (f : CoreM α) : IO α := do
6+
searchPathRef.set compile_time_search_path%
7+
unsafe Lean.withImportModules (modules.map (fun m => {module := m})) {} (trustLevel := 1024)
8+
fun env => Prod.fst <$> Core.CoreM.toIO
9+
(ctx := { fileName := "<CoreM>", fileMap := default }) (s := { env := env }) do f
10+
11+
/--
12+
`lake exe unused_transitive_imports m1 m2 ...`
13+
14+
For each specified module `m`, prints those `n` from the argument list which are imported, but transitively unused by `m`.
15+
-/
16+
def main (args : List String) : IO UInt32 := do
17+
let (flags, args) := args.partition (fun s => s.startsWith "-")
18+
let mut modules := args.map (fun s => s.toName)
19+
Core.withImportModules modules.toArray do
20+
let r ← unusedTransitiveImports modules (verbose := flags.contains "-v" || flags.contains "--verbose")
21+
for (n, u) in r do
22+
IO.println s!"{n}: {u}"
23+
return 0

ImportGraphTest/Imports.lean

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ImportGraph.Imports
22
import ImportGraph.RequiredModules
3+
import ImportGraphTest.Used
34

45
open Lean
56

@@ -36,6 +37,7 @@ elab "#unused_transitive_imports" names:ident* : command => do
3637
logInfo <| s!"Transitively unused imports of {n}:\n{"\n".intercalate (u.map (fun i => s!" {i}"))}"
3738

3839
-- This test case can be removed after nightly-2024-10-24, because these imports have been cleaned up.
40+
-- It should be replaced with another test case!
3941
/--
4042
info: Transitively unused imports of Init.Control.StateRef:
4143
Init.System.IO
@@ -46,6 +48,13 @@ info: Transitively unused imports of Init.System.IO:
4648
#guard_msgs in
4749
#unused_transitive_imports Init.Control.StateRef Init.System.IO Init.Control.Reader Init.Control.Basic
4850

51+
/--
52+
info: Transitively unused imports of ImportGraphTest.Used:
53+
ImportGraphTest.Unused
54+
-/
55+
#guard_msgs in
56+
#unused_transitive_imports ImportGraphTest.Used ImportGraphTest.Unused Init.Control.Reader
57+
4958
-- This is a spurious unused transitive import, because it relies on notation from `Init.Core`.
5059
/--
5160
info: Transitively unused imports of Init.Control.Basic:

lakefile.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,12 @@ root = "Main"
2828
# Remove this line if you do not need such functionality.
2929
supportInterpreter = true
3030

31+
# `lake exe unused_transitive_imports` prints unused transitive imports from amongst a given list of modules.
32+
[[lean_exe]]
33+
name = "unused_transitive_imports"
34+
root = "ImportGraph.UnusedTransitiveImports"
35+
supportInterpreter = true
36+
3137
[[lean_lib]]
3238
name = "ImportGraphTest"
39+

0 commit comments

Comments
 (0)