Skip to content
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
10 changes: 10 additions & 0 deletions src/Lean/Elab/Attributes.lean
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ 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 home module has been loaded for IR only:
-- in that case `metaExt` is missing and shake's `recordExtraModUseFromDecl` would record a
-- stale `isMeta` flag (issue #13599). Force the user to import the module as a regular dep.
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 a non-private 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`. -/
Expand Down
16 changes: 8 additions & 8 deletions src/Lean/Environment.lean
Original file line number Diff line number Diff line change
Expand Up @@ -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. -/
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -2150,24 +2150,24 @@ 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

-- 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
Expand Down
1 change: 1 addition & 0 deletions tests/pkg/user_attr/UserAttr.lean
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import UserAttr.Tst
import UserAttr.MetaUser

open Lean

Expand Down
13 changes: 13 additions & 0 deletions tests/pkg/user_attr/UserAttr/MetaMid.lean
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions tests/pkg/user_attr/UserAttr/MetaUser.lean
Original file line number Diff line number Diff line change
@@ -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 a non-private import of `UserAttr.BlaAttr`.
-/
#guard_msgs in
@[my_simp]
theorem midVal_eq : midVal = 17 := rfl

end
Loading