This PR implements new flags and annotations for `shake` for use in Mathlib: > Options: > --keep-implied > Preserves existing imports that are implied by other imports and thus not technically needed > anymore > > --keep-prefix > If an import `X` would be replaced in favor of a more specific import `X.Y...` it implies, > preserves the original import instead. More generally, prefers inserting `import X` even if it > was not part of the original imports as long as it was in the original transitive import closure > of the current module. > > --keep-public > Preserves all `public` imports to avoid breaking changes for external downstream modules > > --add-public > Adds new imports as `public` if they have been in the original public closure of that module. > In other words, public imports will not be removed from a module unless they are unused even > in the private scope, and those that are removed will be re-added as `public` in downstream > modules even if only needed in the private scope there. Unlike `--keep-public`, this may > introduce breaking changes but will still limit the number of inserted imports. > > Annotations: > The following annotations can be added to Lean files in order to configure the behavior of > `shake`. Only the substring `shake: ` directly followed by a directive is checked for, so multiple > directives can be mixed in one line such as `-- shake: keep-downstream, shake: keep-all`, and they > can be surrounded by arbitrary comments such as `-- shake: keep (metaprogram output dependency)`. > > * `module -- shake: keep-downstream`: > Preserves this module in all (current) downstream modules, adding new imports of it if needed. > > * `module -- shake: keep-all`: > Preserves all existing imports in this module as is. New imports now needed because of upstream > changes may still be added. > > * `import X -- shake: keep`: > Preserves this specific import in the current module. The most common use case is to preserve a > public import that will be needed in downstream modules to make sense of the output of a > metaprogram defined in this module. For example, if a tactic is defined that may synthesize a > reference to a theorem when run, there is no way for `shake` to detect this by itself and the > module of that theorem should be publicly imported and annotated with `keep` in the tactic's > module. > ``` > public import X -- shake: keep (metaprogram output dependency) > > ... > > elab \"my_tactic\" : tactic => do > ... mkConst ``f -- `f`, defined in `X`, may appear in the output of this tactic > ```
139 lines
6 KiB
Text
139 lines
6 KiB
Text
/-
|
|
Copyright (c) 2025 Lean FRO. All rights reserved.
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
Authors: Sebastian Ullrich
|
|
-/
|
|
module
|
|
|
|
prelude
|
|
public import Lean.CoreM
|
|
public import Lean.Compiler.MetaAttr -- TODO: public because of specializations
|
|
import Init.Data.Range.Polymorphic.Stream
|
|
|
|
/-!
|
|
Infrastructure for recording extra import dependencies not implied by the environment constants for
|
|
the benefit of `shake`.
|
|
-/
|
|
|
|
namespace Lean
|
|
|
|
public structure IndirectModUse where
|
|
kind : String
|
|
declName : Name
|
|
deriving BEq
|
|
|
|
public builtin_initialize indirectModUseExt : SimplePersistentEnvExtension IndirectModUse (Std.HashMap Name (Array ModuleIdx)) ←
|
|
registerSimplePersistentEnvExtension {
|
|
addEntryFn s _ := s
|
|
addImportedFn es := Id.run do
|
|
let mut s := {}
|
|
for es in es, modIdx in 0...* do
|
|
for e in es do
|
|
s := s.alter e.declName (·.getD #[] |>.push modIdx)
|
|
return s
|
|
asyncMode := .sync
|
|
}
|
|
|
|
public def getIndirectModUses (env : Environment) (modIdx : ModuleIdx) : Array IndirectModUse :=
|
|
indirectModUseExt.getModuleEntries env modIdx
|
|
|
|
variable [Monad m] [MonadEnv m] [MonadTrace m] [MonadOptions m] [MonadRef m] [AddMessageContext m]
|
|
|
|
/--
|
|
Lets `shake` know that references to `declName` may also require importing the current module due to
|
|
some additional metaprogramming dependency expressed by `kind`. Currently this is always the name of
|
|
an attribute applied to `declName`, which is not from the current module, in the current module.
|
|
`kind` is not currently used to further filter what references to `declName` require importing the
|
|
current module but may in the future.
|
|
-/
|
|
public def recordIndirectModUse (kind : String) (declName : Name) : m Unit := do
|
|
-- We can assume this is called from the main thread only and that the list of entries is short
|
|
if !(indirectModUseExt.getEntries (asyncMode := .mainOnly) (← getEnv) |>.contains { kind, declName }) then
|
|
trace[extraModUses] "recording indirect mod use of `{declName}` ({kind})"
|
|
modifyEnv (indirectModUseExt.addEntry · { kind, declName })
|
|
|
|
/-- Additional import dependency for elaboration. -/
|
|
public structure ExtraModUse where
|
|
/-- Dependency's module name. -/
|
|
module : Name
|
|
/-- Whether dependency must be imported as `public`. -/
|
|
isExported : Bool
|
|
/-- Whether dependency must be imported as `meta`. -/
|
|
isMeta : Bool
|
|
deriving BEq, Hashable, Repr
|
|
|
|
builtin_initialize extraModUses : SimplePersistentEnvExtension ExtraModUse (PHashSet ExtraModUse) ←
|
|
registerSimplePersistentEnvExtension {
|
|
addEntryFn m k := m.insert k
|
|
addImportedFn _ := {}
|
|
exportEntriesFnEx? := some fun _ _ entries => fun
|
|
| .private => entries.toArray
|
|
| _ => #[]
|
|
asyncMode := .sync
|
|
replay? := some <| SimplePersistentEnvExtension.replayOfFilter (·.contains ·) (·.insert ·)
|
|
}
|
|
|
|
/-- Returns additional recorded import dependencies of the given module. -/
|
|
public def getExtraModUses (env : Environment) (modIdx : ModuleIdx) : Array ExtraModUse :=
|
|
extraModUses.getModuleEntries env modIdx
|
|
|
|
/-- Copies additional recorded import dependencies from one environment to another. -/
|
|
public def copyExtraModUses (src dest : Environment) : Environment := Id.run do
|
|
let mut env := dest
|
|
for entry in extraModUses.getEntries (asyncMode := .local) src do
|
|
if !(extraModUses.getState (asyncMode := .local) env).contains entry then
|
|
env := extraModUses.addEntry env entry
|
|
env
|
|
|
|
def recordExtraModUseCore (mod : Name) (isMeta : Bool) (hint : Name := .anonymous) : m Unit := do
|
|
let entry := { module := mod, isExported := (← getEnv).isExporting, isMeta }
|
|
if !(extraModUses.getState (asyncMode := .local) (← getEnv)).contains entry then
|
|
trace[extraModUses] "recording {if entry.isExported then "public" else "private"} \
|
|
{if isMeta then "meta" else "regular"} extra mod use {mod}\
|
|
{if hint.isAnonymous then m!"" else m!" of {hint}"}"
|
|
modifyEnv (extraModUses.addEntry · entry)
|
|
|
|
/--
|
|
Records an additional import dependency for the current module, using `Environment.isExporting` as
|
|
the visibility level.
|
|
|
|
NOTE: Directly recording a module name does not trigger including indirect dependencies recorded via
|
|
`recordIndirectModUse`, prefer `recordExtraModUseFromDecl` instead.
|
|
-/
|
|
public def recordExtraModUse (modName : Name) (isMeta : Bool) : m Unit := do
|
|
if modName != (← getEnv).mainModule then
|
|
recordExtraModUseCore modName isMeta
|
|
|
|
/--
|
|
Records the module of the given declaration as an additional import dependency for the current
|
|
module, using `Environment.isExporting` as the visibility level. If the declaration itself is
|
|
already `meta`, the module dependency is recorded as a non-`meta` dependency.
|
|
-/
|
|
public def recordExtraModUseFromDecl (declName : Name) (isMeta : Bool) : m Unit := do
|
|
let env ← getEnv
|
|
if let some mod := env.getModuleIdxFor? declName |>.bind (env.header.modules[·]?) then
|
|
-- If the declaration itself is already `meta`, no need to mark the import.
|
|
let isMeta := isMeta && !isMarkedMeta (← getEnv) declName
|
|
recordExtraModUseCore mod.module isMeta (hint := declName)
|
|
for modIdx in (indirectModUseExt.getState (asyncMode := .local) env)[declName]?.getD #[] do
|
|
recordExtraModUseCore env.header.modules[modIdx]!.module (isMeta := false) (hint := declName)
|
|
|
|
builtin_initialize isExtraRevModUseExt : SimplePersistentEnvExtension Unit Unit ←
|
|
registerSimplePersistentEnvExtension {
|
|
addEntryFn s e := ()
|
|
addImportedFn _ := ()
|
|
asyncMode := .sync
|
|
}
|
|
|
|
/-- Checks whether this module should be preserved as an import by `shake`. -/
|
|
public def isExtraRevModUse (env : Environment) (modIdx : ModuleIdx) : Bool :=
|
|
!(isExtraRevModUseExt.getModuleEntries env modIdx |>.isEmpty)
|
|
|
|
/-- Records this module to be preserved as an import by `shake`. -/
|
|
public def recordExtraRevUseOfCurrentModule : m Unit := do
|
|
if isExtraRevModUseExt.getEntries (asyncMode := .local) (← getEnv) |>.isEmpty then
|
|
trace[extraModUses] "recording extra reverse use of current module"
|
|
modifyEnv (isExtraRevModUseExt.addEntry · ())
|
|
|
|
builtin_initialize
|
|
registerTraceClass `extraModUses
|