fix: reject attribute uses whose module is reachable only via IR (#13613)
This PR makes the elaborator reject `@[foo]` when the module that registers `foo` is not visibly imported into the current file but merely loaded as IR. Previously such uses silently elaborated but led to divergence of cmdline and server behavior and caused `lake shake --fix` to flip-flop on successive runs (#13599).
This commit is contained in:
parent
0e2088fc83
commit
e6dfdfdcee
5 changed files with 50 additions and 8 deletions
|
|
@ -63,6 +63,14 @@ def elabAttr [Monad m] [MonadEnv m] [MonadResolveName m] [MonadError m] [MonadMa
|
|||
| throwError "Unknown attribute `[{attrName}]`"
|
||||
if let .ok impl := getAttributeImpl (← getEnv) attrName then
|
||||
if regularInitAttr.getParam? (← getEnv) impl.ref |>.isSome then -- skip `builtin_initialize` attributes
|
||||
-- Reject attribute uses where the implementation's module has been loaded for IR only as
|
||||
-- this would make it `inServer`-dependent and confused shake as well (#13599).
|
||||
if let some idx := (← getEnv).getModuleIdxFor? impl.ref then
|
||||
let env ← getEnv
|
||||
if env.header.modules[idx]?.any (!·.hasData) then
|
||||
let modName := env.header.modules[idx]!.module
|
||||
throwError m!"Cannot use attribute `[{attrName}]`: module `{modName}` is loaded for IR \
|
||||
only (reached as a private `meta` dependency). Add an import of `{modName}`."
|
||||
recordExtraModUseFromDecl (isMeta := true) impl.ref
|
||||
/- The `AttrM` does not have sufficient information for expanding macros in `args`.
|
||||
So, we expand them before here before we invoke the attributer handlers implemented using `AttrM`. -/
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@ structure ModuleData where
|
|||
structure EffectiveImport extends Import where
|
||||
/-- Phases for which the import's IR is available. -/
|
||||
irPhases : IRPhases
|
||||
/-- Whether the import's `.olean*` data has been loaded (otherwise only the `.ir` is). -/
|
||||
hasData : Bool
|
||||
deriving Inhabited
|
||||
|
||||
/-- Environment fields that are not used often. -/
|
||||
|
|
@ -1980,14 +1982,12 @@ private structure ImportedModule extends EffectiveImport where
|
|||
parts : Array (ModuleData × CompactedRegion)
|
||||
/-- `.ir` data, if loaded. -/
|
||||
irData? : Option (ModuleData × CompactedRegion)
|
||||
/-- If true, `.olean*` data should be imported. -/
|
||||
needsData : Bool
|
||||
/-- If true, IR is loaded transitively. -/
|
||||
needsIRTrans : Bool
|
||||
|
||||
/-- The main module data that will eventually be used to construct the publicly accessible constants. -/
|
||||
private def ImportedModule.publicModule? (self : ImportedModule) : Option ModuleData := do
|
||||
if self.needsData then
|
||||
if self.hasData then
|
||||
self.parts[0]?.map (·.1)
|
||||
else
|
||||
-- (should not have any constants)
|
||||
|
|
@ -2000,7 +2000,7 @@ private def ImportedModule.getData? (self : ImportedModule) (level : OLeanLevel)
|
|||
|
||||
/-- The main module data that will eventually be used to construct the kernel environment. -/
|
||||
private def ImportedModule.mainModule? (self : ImportedModule) : Option ModuleData :=
|
||||
if self.needsData then
|
||||
if self.hasData then
|
||||
self.getData? (if self.importAll then .private else .exported)
|
||||
else
|
||||
self.irData?.map (·.1)
|
||||
|
|
@ -2150,16 +2150,16 @@ where
|
|||
-- when module is already imported, bump flags
|
||||
let importAll := importAll || mod.importAll
|
||||
let isExported := isExported || mod.isExported
|
||||
let needsData := needsData || mod.needsData
|
||||
let needsData := needsData || mod.hasData
|
||||
let needsIRTrans := needsIRTrans || mod.needsIRTrans
|
||||
let needsIR := needsIRTrans || importAll
|
||||
let irPhases := if irPhases == mod.irPhases then irPhases else .all
|
||||
let parts ← if needsData && mod.parts.isEmpty then loadData i else pure mod.parts
|
||||
let irData? ← if needsIR && mod.irData?.isNone then loadIR? i else pure mod.irData?
|
||||
if importAll != mod.importAll || isExported != mod.isExported ||
|
||||
needsIRTrans != mod.needsIRTrans || needsData != mod.needsData || irPhases != mod.irPhases then
|
||||
needsIRTrans != mod.needsIRTrans || needsData != mod.hasData || irPhases != mod.irPhases then
|
||||
modify fun s => { s with moduleNameMap := s.moduleNameMap.insert i.module { mod with
|
||||
importAll, isExported, irPhases, parts, irData?, needsData, needsIRTrans }}
|
||||
importAll, isExported, irPhases, parts, irData?, hasData := needsData, needsIRTrans }}
|
||||
-- bump entire closure
|
||||
goRec mod
|
||||
continue
|
||||
|
|
@ -2167,7 +2167,7 @@ where
|
|||
-- newly discovered module
|
||||
let parts ← if needsData then loadData i else pure #[]
|
||||
let irData? ← if needsIR then loadIR? i else pure none
|
||||
let mod := { i with importAll, isExported, irPhases, parts, irData?, needsIRTrans, needsData }
|
||||
let mod := { i with importAll, isExported, irPhases, parts, irData?, needsIRTrans, hasData := needsData }
|
||||
goRec mod
|
||||
modify fun s => { s with
|
||||
moduleNameMap := s.moduleNameMap.insert i.module mod
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import UserAttr.Tst
|
||||
import UserAttr.MetaUser
|
||||
|
||||
open Lean
|
||||
|
||||
|
|
|
|||
13
tests/pkg/user_attr/UserAttr/MetaMid.lean
Normal file
13
tests/pkg/user_attr/UserAttr/MetaMid.lean
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module
|
||||
|
||||
import UserAttr.BlaAttr
|
||||
|
||||
/-! Middle module that plain-imports `UserAttr.BlaAttr`, used by `UserAttr.MetaUser` to set up the
|
||||
`meta import` chain that triggers the regression in
|
||||
https://github.com/leanprover/lean4/issues/13599. -/
|
||||
|
||||
@[expose] public section
|
||||
|
||||
def midVal : Nat := 17
|
||||
|
||||
end
|
||||
20
tests/pkg/user_attr/UserAttr/MetaUser.lean
Normal file
20
tests/pkg/user_attr/UserAttr/MetaUser.lean
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
module
|
||||
|
||||
public meta import UserAttr.MetaMid
|
||||
meta import Lean.Elab.GuardMsgs
|
||||
|
||||
/-! Regression test for https://github.com/leanprover/lean4/issues/13599: using `@[my_simp]` (an
|
||||
attribute registered in `UserAttr.BlaAttr`) while reaching `BlaAttr` only via a `meta import`
|
||||
chain (`UserAttr.MetaMid → import UserAttr.BlaAttr`) used to make `shake --fix` flip-flop. The
|
||||
elaborator now rejects such uses with a clear error pointing at the missing import. -/
|
||||
|
||||
@[expose] public section
|
||||
|
||||
/--
|
||||
error: Cannot use attribute `[my_simp]`: module `UserAttr.BlaAttr` is loaded for IR only (reached as a private `meta` dependency). Add an import of `UserAttr.BlaAttr`.
|
||||
-/
|
||||
#guard_msgs in
|
||||
@[my_simp]
|
||||
theorem midVal_eq : midVal = 17 := rfl
|
||||
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue