Zulip Chat Archive
Stream: lean4
Topic: Bug? Local instances not populated during delaboration
Thomas Murrills (Oct 14 2025 at 23:12):
I was testing #30266, which delaborates an algebra equivalence into Galois notation if a Field instance can be synthesized for some of its arguments, when I noticed that the local instances are not populated in DelabM. This leads to synthInstance? for Field A failing despite there being a variable [Field A] present (and present in the local context during delaboration).
MWE
Is this a bug, or is this deliberate to avoid overhead during delaboration? (Should #30266 crawl the local context/populate the local instances manually?)
Andrew Yang (Oct 16 2025 at 00:18):
Here's some thing related:
The infoview seems to have different behaviour when pretty printing Tactic state and Expected type?
import Lean.PrettyPrinter.Delaborator.Basic
open Lean Meta PrettyPrinter Delaborator
class Bar where
def NoBar : Type := Unit
macro "YesBar":term => `(NoBar)
/-- Delaborates `NoBar` to `YesBar` if `Bar` instance is present. -/
@[app_delab NoBar]
partial def delabNoBar : Delab := whenPPOption getPPNotation do
let some _ ← synthInstance? (mkConst ``Bar) | failure
`(YesBar)
example [Bar] : NoBar := by
/-
Infoview when you place your cursor here:
▼ Tactic state
1 goal
inst✝ : Bar
⊢ YesBar
-/
exact .unit
/-
Infoview when you place your cursor on the end of the previous line:
▼ Tactic state
No goals
▼ Expected type
inst✝ : Bar
⊢ NoBar
-/
Aaron Liu (Oct 16 2025 at 00:20):
we have docs#Mathlib.Meta.delabInf and docs#Mathlib.Meta.delabSup what do they do
Andrew Yang (Oct 16 2025 at 00:22):
They add the local instances back manually (i.e. withLocalInstances (← getLCtx).decls.toList.reduceOption do)
Thomas Murrills (Oct 16 2025 at 01:45):
Nice find! :) For visibility, #23558 is the PR in which that approach was introduced, and on which this issue was encountered was discussed.
@Kyle Miller mentioned on that PR that this was a 4.19 limitation; Kyle, do you happen to know if there's an issue associated with it and/or still plans to address it?
Jovan Gerbscheid (Oct 16 2025 at 12:04):
Yes, I made a PR to Lean that tried to fix this issue (and this is the fix that @Kyle Miller was referring to), but it turns out that the PR didn't fix it (or at least not in all situations)
Thomas Murrills (Oct 16 2025 at 18:17):
Ah, okay! Did that end up with a planned fix, a wontfix, or just a general postponement? I'm trying to figure out if this should be filed as an issue.
Jovan Gerbscheid (Oct 16 2025 at 18:33):
I think nobody else cared about it at that time, and I didn't make an issue, so feel free to make an issue about it now.
Jovan Gerbscheid (Oct 16 2025 at 18:38):
(My attempted fix was in lean#8022)
Thomas Murrills (Oct 17 2025 at 23:09):
I see, thanks! :) Issue: lean4#10830
Thomas Murrills (Oct 18 2025 at 02:18):
Aha, I think I've gotten to the bottom of this.
Pretty-printing works by starting with a PPContext, which looks like this:
structure PPContext where
env : Environment
mctx : MetavarContext := {}
lctx : LocalContext := {}
opts : Options := {}
currNamespace : Name := Name.anonymous
openDecls : List OpenDecl := []
(Note the lack of localInstances....)
To pretty-print viaLean.ppExprWithInfos (ctx : PPContext) (e : Expr) : BaseIO FormatWithInfos using some such ctx, we look in the ppExt extension's state in ctx.env, which in turn is set by looking in the pretty-printing ref ppFnsRef. In there, you find an instance of a structure PPFns, which looks like this:
structure PPFns where
ppExprWithInfos : PPContext → Expr → IO FormatWithInfos
ppConstNameWithInfos : PPContext → Name → IO FormatWithInfos
ppTerm : PPContext → Term → IO Format
ppLevel : PPContext → Level → BaseIO Format
ppGoal : PPContext → MVarId → IO Format
What do we set this field ppExprWithInfos to?
builtin_initialize
ppFnsRef.set {
ppExprWithInfos := fun ctx e => ctx.runMetaM <| withoutContext <| ppExprWithInfos e
ppConstNameWithInfos := fun ctx n => ctx.runMetaM <| withoutContext <| ppConstNameWithInfos n
ppTerm := fun ctx stx => ctx.runCoreM <| withoutContext <| ppTerm stx
ppLevel := fun ctx l => return l.format (mvars := getPPMVarsLevels ctx.opts)
ppGoal := fun ctx mvarId => ctx.runMetaM <| withoutContext <| Meta.ppGoal mvarId
}
Note: that's Lean.Meta.ppExprWithInfos in the body, not to be confused with Lean.ppExprWithInfos above or the name of the field itself!
That ctx is a PPContext, and PPContext.runMetaM (Lean.Meta.Basic) looks like this:
def runMetaM {α : Type} (ppCtx : PPContext) (x : MetaM α) : IO α :=
ppCtx.runCoreM <| x.run' { lctx := ppCtx.lctx } { mctx := ppCtx.mctx }
It's there that localInstances acquires the value #[] by a default field value, I believe.
Thomas Murrills (Oct 18 2025 at 02:26):
So, we can either
- give
PPContexta fieldlocalInstances--this would mean constructing those at the different points we construct aPPContext - add the local instances to the context in
PPContext.runMetaM - provide better documentation around
DelabMif both of these are too burdensome performance-wise
Thomas Murrills (Oct 18 2025 at 04:49):
I'm experimenting with option 2 in lean4#10832. If it's not too bad when benched (fingers crossed! But my hopes aren't too high...), I'll clean it up and propose it... :)
Thomas Murrills (Oct 18 2025 at 07:15):
Here are the bench results (comparing the commit incorporating lean4#10832 to the one just before it, via benching PRs #30642 and #30643), though I'm not 100% sure how they should be interpreted. There's a good bit of orange, but not much shows up in the stddev column...
I'm also not sure if benching like this actually stress-tests pretty printing sufficiently well, especially if it's focused on building. If it doesn't, how would we tell if this change was making things more sluggish in the infoview?
Jovan Gerbscheid (Oct 18 2025 at 16:24):
I would think that
- this change doesn't affect performance much
- if it did affect performance, it would not be visible in the benchmarking since very little of the benchmarking time is spent on pretty printing.
Last updated: Dec 20 2025 at 21:32 UTC