fix: mark auxiliary definitions from normalizeInstance as meta (#13043)

This PR fixes a bug where `inferInstanceAs` and the default `deriving`
handler, when used inside a `meta section`, would create auxiliary
definitions (via `normalizeInstance`) that were not marked as `meta`.
This caused the compiler to reject the parent `meta` definition with:

```
Invalid `meta` definition `instEmptyCollectionNamePrefixRel`, `instEmptyCollectionNamePrefixRel._aux_1` not marked `meta`
```

The fix adds an `isMeta` parameter to `normalizeInstance` that is
propagated from the elaboration context (`isMarkedMeta` for
`inferInstanceAs`, `Scope.isMeta` for the deriving handler), and marks
each auxiliary definition created by `mkAuxDefinition` as `meta` when
appropriate.

Found while adapting Mathlib to
https://github.com/leanprover/lean4/pull/12897.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kim Morrison 2026-03-23 09:41:57 +11:00 committed by GitHub
parent cffacf1b10
commit 8ae39633d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 27 additions and 6 deletions

View file

@ -329,7 +329,8 @@ private def mkSilentAnnotationIfHole (e : Expr) : TermElabM Expr := do
let inst ← if backward.inferInstanceAs.wrap.get (← getOptions) then
-- Normalize to instance normal form.
let logCompileErrors := !(← read).isNoncomputableSection && !(← read).declName?.any (Lean.isNoncomputable (← getEnv))
withNewMCtxDepth <| normalizeInstance inst expectedType (logCompileErrors := logCompileErrors)
let isMeta := (← read).isMetaSection
withNewMCtxDepth <| normalizeInstance inst expectedType (logCompileErrors := logCompileErrors) (isMeta := isMeta)
else
pure inst
ensureHasType expectedType? inst

View file

@ -666,7 +666,8 @@ private def mkTermContext (ctx : Context) (s : State) : CommandElabM Term.Contex
return {
macroStack := ctx.macroStack
sectionVars := sectionVars
isNoncomputableSection := scope.isNoncomputable }
isNoncomputableSection := scope.isNoncomputable
isMetaSection := scope.isMeta }
/--
Lift the `TermElabM` monadic action `x` into a `CommandElabM` monadic action.

View file

@ -220,10 +220,12 @@ def processDefDeriving (view : DerivingClassView) (decl : Expr) (isNoncomputable
instName ← liftMacroM <| mkUnusedBaseName instName
if isPrivateName declName then
instName := mkPrivateName env instName
let isMeta := (← read).isMetaSection
let inst ← if backward.inferInstanceAs.wrap.get (← getOptions) then
withDeclNameForAuxNaming instName <| withNewMCtxDepth <|
normalizeInstance result.instVal result.instType
(logCompileErrors := false) -- covered by noncomputable check below
(isMeta := isMeta)
else
pure result.instVal
let closure ← Closure.mkValueTypeClosure result.instType inst (zetaDelta := true)

View file

@ -309,6 +309,8 @@ structure Context where
heedElabAsElim : Bool := true
/-- Noncomputable sections automatically add the `noncomputable` modifier to any declaration we cannot generate code for. -/
isNoncomputableSection : Bool := false
/-- `true` when inside a `meta section`. -/
isMetaSection : Bool := false
/-- When `true` we skip TC failures. We use this option when processing patterns. -/
ignoreTCFailures : Bool := false
/-- `true` when elaborating patterns. It affects how we elaborate named holes. -/

View file

@ -99,7 +99,7 @@ Normalize an instance value to "instance normal form".
See the module docstring for the full algorithm specification.
-/
partial def normalizeInstance (inst expectedType : Expr) (compile : Bool := true)
(logCompileErrors : Bool := true) : MetaM Expr := withTransparency .instances do
(logCompileErrors : Bool := true) (isMeta : Bool := false) : MetaM Expr := withTransparency .instances do
withTraceNode `Meta.instanceNormalForm
(fun _ => return m!"type: {expectedType}") do
let some className ← isClass? expectedType
@ -124,9 +124,11 @@ partial def normalizeInstance (inst expectedType : Expr) (compile : Bool := true
return inst
else
let name ← mkAuxDeclName
let wrapped ← mkAuxDefinition name expectedType inst (compile := compile)
(logCompileErrors := logCompileErrors)
let wrapped ← mkAuxDefinition name expectedType inst (compile := false)
setReducibilityStatus name .implicitReducible
if isMeta then modifyEnv (markMeta · name)
if compile then
compileDecls (logErrors := logCompileErrors) #[name]
enableRealizationsForConst name
return wrapped
else
@ -169,7 +171,7 @@ partial def normalizeInstance (inst expectedType : Expr) (compile : Bool := true
catch _ => pure ()
mvarId.assign (← normalizeInstance arg argExpectedType (compile := compile)
(logCompileErrors := logCompileErrors))
(logCompileErrors := logCompileErrors) (isMeta := isMeta))
else
-- For data fields, assign directly or wrap in aux def to fix types.
if backward.inferInstanceAs.wrap.data.get (← getOptions) then
@ -180,6 +182,7 @@ partial def normalizeInstance (inst expectedType : Expr) (compile : Bool := true
let name ← mkAuxDeclName
mvarId.assign (← mkAuxDefinition name argExpectedType arg (compile := false))
setInlineAttribute name
if isMeta then modifyEnv (markMeta · name)
if compile then
compileDecls (logErrors := logCompileErrors) #[name]
enableRealizationsForConst name

12
tests/elab/12897.lean Normal file
View file

@ -0,0 +1,12 @@
-- Tests that `inferInstanceAs` auxiliary definitions are properly marked `meta`
-- when used inside a `meta` section.
module
meta section
def Foo := List Nat
instance : EmptyCollection Foo := inferInstanceAs (EmptyCollection (List Nat))
end