feat: use omission dots for hidden let values in Infoview (#8041)

This PR changes the behavior of `pp.showLetValues` to use a hoverable
`⋯` to hide let values. This is now false by default, and there is a new
option `pp.showLetValues.threshold` for allowing small expressions to be
shown anyway. For tactic metavariables, there is an additional option
`pp.showLetValues.tactic.threshold`, which by default is set to the
maximal value, since in tactic states local values are usually
significant.
This commit is contained in:
Kyle Miller 2025-05-27 16:09:11 -07:00 committed by GitHub
parent 5187cb37a9
commit 921ce7682e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 184 additions and 15 deletions

View file

@ -568,7 +568,7 @@ def findBelowIdx (xs : Array Expr) (motive : Expr) : MetaM $ Option (Expr × Nat
let (_, belowTy) ← belowType motive xs idx
let below ← mkFreshExprSyntheticOpaqueMVar belowTy
try
trace[Meta.IndPredBelow.match] "{←Meta.ppGoal below.mvarId!}"
trace[Meta.IndPredBelow.match] "{below.mvarId!}"
if (← below.mvarId!.applyRules { backtracking := false, maxDepth := 1 } []).isEmpty then
trace[Meta.IndPredBelow.match] "Found below term in the local context: {below}"
if (← xs.anyM (isDefEq below)) then pure none else pure (below, idx)

View file

@ -27,11 +27,44 @@ register_builtin_option pp.inaccessibleNames : Bool := {
}
register_builtin_option pp.showLetValues : Bool := {
defValue := true
defValue := false
group := "pp"
descr := "display let-declaration values in the info view"
descr := "always display let-declaration values in the info view"
}
register_builtin_option pp.showLetValues.threshold : Nat := {
defValue := 0
group := "pp"
descr := "when `pp.showLetValues` is false, the maximum size of a term allowed before it is replaced by `⋯`"
}
register_builtin_option pp.showLetValues.tactic.threshold : Nat := {
defValue := 255
group := "pp"
descr := "when `pp.showLetValues` is false, the maximum size of a term allowed before it is replaced by `⋯`, for tactic goals"
}
/--
Given the current values of the options `pp.showLetValues` and `pp.showLetValues.threshold`,
determines whether the local let declaration's value should be omitted.
- `tactic` is whether the goal is for a tactic metavariable.
In that case, uses the maximum of `pp.showLetValues.tactic.threshold` and `pp.showLetValues.threshold` for the threshold.
In tactics, we usually want to see let values.
In contrast, for the "expected type" view we usually do not.
-/
def ppGoal.shouldShowLetValue (tactic : Bool) (e : Expr) : MetaM Bool := do
-- Atomic expressions never get omitted by the following logic, so we can do an early return here.
if e.isAtomic then
return true
let options ← getOptions
if pp.showLetValues.get options then
return true
let threshold := pp.showLetValues.threshold.get options
let threshold := max threshold (if tactic then pp.showLetValues.tactic.threshold.get options else 0)
let threshold := min 254 threshold
return e.approxDepth.toNat ≤ threshold
private def addLine (fmt : Format) : Format :=
if fmt.isNil then fmt else fmt ++ "\n"
@ -47,9 +80,11 @@ def ppGoal (mvarId : MVarId) : MetaM Format := do
| none => return "unknown goal"
| some mvarDecl =>
let indent := 2 -- Use option
let showLetValues := pp.showLetValues.get (← getOptions)
let ppAuxDecls := pp.auxDecls.get (← getOptions)
let ppImplDetailHyps := pp.implementationDetailHyps.get (← getOptions)
-- Heuristic: synthetic opaque metavariables are only used by tactics,
-- and tactics should always be creating synthetic opaque metavariables for new goals.
let tactic := mvarDecl.kind.isSyntheticOpaque
let lctx := mvarDecl.lctx
let lctx := lctx.sanitizeNames.run' { options := (← getOptions) }
withLCtx lctx mvarDecl.localInstances do
@ -82,10 +117,12 @@ def ppGoal (mvarId : MVarId) : MetaM Format := do
let type ← instantiateMVars type
let typeFmt ← ppExpr type
let mut fmtElem := format varName ++ " : " ++ typeFmt
if showLetValues then
let val ← instantiateMVars val
let val ← instantiateMVars val
if ← ppGoal.shouldShowLetValue tactic val then
let valFmt ← ppExpr val
fmtElem := fmtElem ++ " :=" ++ Format.nest indent (Format.line ++ valFmt)
else
fmtElem := fmtElem ++ " := ⋯"
let fmt := fmt ++ fmtElem.group
return ([], none, fmt)
let (varNames, type?, fmt) ← lctx.foldlM (init := ([], none, Format.nil)) fun (varNames, prevType?, fmt) (localDecl : LocalDecl) =>

View file

@ -323,11 +323,13 @@ inductive OmissionReason
| deep
| proof
| maxSteps
| string (s : String)
def OmissionReason.toString : OmissionReason → String
| deep => "Term omitted due to its depth (see option `pp.deepTerms`)."
| proof => "Proof omitted (see option `pp.proofs`)."
| maxSteps => "Term omitted due to reaching the maximum number of steps allowed for pretty printing this expression (see option `pp.maxSteps`)."
| string s => s
def addOmissionInfo (pos : Pos) (stx : Syntax) (e : Expr) (reason : OmissionReason) : DelabM Unit := do
addDelabTermInfo pos stx e (docString? := reason.toString) (explicit := false)

View file

@ -116,7 +116,7 @@ instance : EmptyCollection InteractiveGoals := ⟨{goals := #[]}⟩
open Meta in
/-- Extend an array of hypothesis bundles with another bundle. -/
def addInteractiveHypothesisBundle (hyps : Array InteractiveHypothesisBundle)
(ids : Array (String × FVarId)) (type : Expr) (value? : Option Expr := none) :
(ids : Array (String × FVarId)) (type : Expr) (value? : Option Expr := none) (tactic := false ) :
MetaM (Array InteractiveHypothesisBundle) := do
if ids.size == 0 then
throwError "Can only add a nonzero number of ids as an InteractiveHypothesisBundle."
@ -126,10 +126,22 @@ def addInteractiveHypothesisBundle (hyps : Array InteractiveHypothesisBundle)
names
fvarIds
type := (← ppExprTagged type)
val? := (← value?.mapM ppExprTagged)
val? := (← value?.mapM ppLetValueExprTagged)
isInstance? := if (← isClass? type).isSome then true else none
isType? := if (← instantiateMVars type).isSort then true else none
}
where
ppLetValueExprTagged (value : Expr) : MetaM CodeWithInfos := do
if ← ppGoal.shouldShowLetValue tactic value then
ppExprTagged value
else
ppExprTagged value do PrettyPrinter.Delaborator.omission <| .string <|
if tactic then
"Value omitted since `pp.showLetValues` is false and \
the expression's depth exceeds both `pp.showLetValues.tactic.threshold` and `pp.showLetValues.threshold`."
else
"Value omitted since `pp.showLetValues` is false and \
the expression's depth exceeds `pp.showLetValues.threshold`."
open Meta in
variable [MonadControlT MetaM n] [Monad n] [MonadError n] [MonadOptions n] [MonadMCtx n] in
@ -145,8 +157,9 @@ open Meta in
def goalToInteractive (mvarId : MVarId) : MetaM InteractiveGoal := do
let ppAuxDecls := pp.auxDecls.get (← getOptions)
let ppImplDetailHyps := pp.implementationDetailHyps.get (← getOptions)
let showLetValues := pp.showLetValues.get (← getOptions)
withGoalCtx mvarId fun lctx mvarDecl => do
-- See comment in `Lean.Meta.ppGoal` about this synthetic opaque heuristic.
let tactic := mvarDecl.kind.isSyntheticOpaque
let pushPending (ids : Array (String × FVarId)) (type? : Option Expr) (hyps : Array InteractiveHypothesisBundle)
: MetaM (Array InteractiveHypothesisBundle) :=
if ids.isEmpty then
@ -179,8 +192,8 @@ def goalToInteractive (mvarId : MVarId) : MetaM InteractiveGoal := do
let varName := toString varName
hyps ← pushPending varNames prevType? hyps
let type ← instantiateMVars type
let val? if showLetValues then pure (some (← instantiateMVars val)) else pure none
hyps ← addInteractiveHypothesisBundle hyps #[(varName, fvarId)] type val?
let val ← instantiateMVars val
hyps ← addInteractiveHypothesisBundle hyps #[(varName, fvarId)] type val tactic
varNames := #[]
prevType? := none
hyps ← pushPending varNames prevType? hyps

View file

@ -8,7 +8,7 @@ y : Nat := g x + g x
holes.lean:11:4-11:5: error: don't know how to synthesize placeholder
context:
x : Nat
y : Nat := g x + g x
y : Nat :=
⊢ Nat
holes.lean:10:15-10:18: error: don't know how to synthesize implicit argument 'β'
@g Nat (?m x) x
@ -26,7 +26,7 @@ holes.lean:19:0-19:3: error: don't know how to synthesize implicit argument 'β'
@f Nat (?m a) a
context:
a : Nat
f : {α : Type} → {β : ?m a} → αα := fun {α} {β} a => a
f : {α : Type} → {β : ?m a} → αα :=
⊢ ?m a
holes.lean:18:9-18:10: error: failed to infer binder type
holes.lean:21:25-22:4: error: failed to infer type of `f7`

View file

@ -2,4 +2,4 @@
"position": {"line": 3, "character": 5}}
{"range":
{"start": {"line": 3, "character": 4}, "end": {"line": 3, "character": 23}},
"goal": "x : Nat := 10\n⊢ IO Unit"}
"goal": "x : Nat := \n⊢ IO Unit"}

View file

@ -0,0 +1,81 @@
/-!
# Tests of `pp.showLetValues`
-/
-- Ensure default values
set_option pp.showLetValues false
set_option pp.showLetValues.threshold 0
set_option pp.showLetValues.tactic.threshold 255
/-!
Non-tactic context. No let value (see ⋯)
-/
example (x : Nat) : Nat :=
let y := x + x
y
--^ $/lean/plainTermGoal
/-!
Non-tactic context. Enable showing values (see x + x)
-/
set_option pp.showLetValues true in
example (x : Nat) : Nat :=
let y := x + x
y
--^ $/lean/plainTermGoal
/-!
Non-tactic context. Turn up threshold (see x + x)
-/
set_option pp.showLetValues.threshold 10 in
example (x : Nat) : Nat :=
let y := x + x
y
--^ $/lean/plainTermGoal
/-!
Tactic context. See x + x
-/
example (x : Nat) : Nat := by
let y := x + x
exact y
--^ $/lean/plainGoal
/-!
Tactic context. Turn down threshold (see ⋯)
-/
set_option pp.showLetValues.tactic.threshold 0 in
example (x : Nat) : Nat := by
let y := x + x
exact y
--^ $/lean/plainGoal
/-!
Tactic context. Uses max of both thresholds (see x + x)
-/
set_option pp.showLetValues.threshold 10 in
set_option pp.showLetValues.tactic.threshold 0 in
example (x : Nat) : Nat := by
let y := x + x
exact y
--^ $/lean/plainGoal
/-!
Non-tactic context. Atomic always shown (see x)
-/
set_option pp.showLetValues.threshold 0 in
set_option pp.showLetValues.tactic.threshold 0 in
example (x : Nat) : Nat :=
let y := x
y
--^ $/lean/plainTermGoal
/-!
Tactic context. Atomic always shown (see x)
-/
set_option pp.showLetValues.threshold 0 in
set_option pp.showLetValues.tactic.threshold 0 in
example (x : Nat) : Nat := by
let y := x
exact y
--^ $/lean/plainGoal

View file

@ -0,0 +1,36 @@
{"textDocument": {"uri": "file:///ppShowLetValues.lean"},
"position": {"line": 14, "character": 2}}
{"range":
{"start": {"line": 14, "character": 2}, "end": {"line": 14, "character": 3}},
"goal": "x : Nat\ny : Nat := ⋯\n⊢ Nat"}
{"textDocument": {"uri": "file:///ppShowLetValues.lean"},
"position": {"line": 23, "character": 2}}
{"range":
{"start": {"line": 23, "character": 2}, "end": {"line": 23, "character": 3}},
"goal": "x : Nat\ny : Nat := x + x\n⊢ Nat"}
{"textDocument": {"uri": "file:///ppShowLetValues.lean"},
"position": {"line": 32, "character": 2}}
{"range":
{"start": {"line": 32, "character": 2}, "end": {"line": 32, "character": 3}},
"goal": "x : Nat\ny : Nat := x + x\n⊢ Nat"}
{"textDocument": {"uri": "file:///ppShowLetValues.lean"},
"position": {"line": 40, "character": 2}}
{"rendered": "```lean\nx : Nat\ny : Nat := x + x\n⊢ Nat\n```",
"goals": ["x : Nat\ny : Nat := x + x\n⊢ Nat"]}
{"textDocument": {"uri": "file:///ppShowLetValues.lean"},
"position": {"line": 49, "character": 2}}
{"rendered": "```lean\nx : Nat\ny : Nat := ⋯\n⊢ Nat\n```",
"goals": ["x : Nat\ny : Nat := ⋯\n⊢ Nat"]}
{"textDocument": {"uri": "file:///ppShowLetValues.lean"},
"position": {"line": 59, "character": 2}}
{"rendered": "```lean\nx : Nat\ny : Nat := x + x\n⊢ Nat\n```",
"goals": ["x : Nat\ny : Nat := x + x\n⊢ Nat"]}
{"textDocument": {"uri": "file:///ppShowLetValues.lean"},
"position": {"line": 69, "character": 2}}
{"range":
{"start": {"line": 69, "character": 2}, "end": {"line": 69, "character": 3}},
"goal": "x : Nat\ny : Nat := x\n⊢ Nat"}
{"textDocument": {"uri": "file:///ppShowLetValues.lean"},
"position": {"line": 79, "character": 2}}
{"rendered": "```lean\nx : Nat\ny : Nat := x\n⊢ Nat\n```",
"goals": ["x : Nat\ny : Nat := x\n⊢ Nat"]}

View file

@ -2,5 +2,5 @@ mvarAtDefaultValue.lean:5:7-5:8: error: failed to infer default value for field
mvarAtDefaultValue.lean:8:7-8:8: error: don't know how to synthesize placeholder
context:
x : Nat
toA : A := { x := x }
toA : A :=
⊢ Nat