| /- | |
| 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 => | |
| { exported := #[], server := #[], «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 |