Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: apply’s error message should show implicit arguments as needed #3929

Merged
merged 28 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ffef5ca
feat: apply’s error message should show implict arguments as needed
nomeata Jan 30, 2024
7fe8396
Update test for #1870
nomeata Jan 30, 2024
9a52d9d
experimenting with .ofPPFormat
nomeata Jan 31, 2024
dc23970
Merge branch 'nightly-with-mathlib' of github.com:leanprover/lean4 in…
nomeata Apr 9, 2024
9e04a3a
Introduce MessageData.thunk
nomeata Apr 9, 2024
dcab3e8
And use it
nomeata Apr 9, 2024
d2a34fd
Undo test output change
nomeata Apr 9, 2024
b89bb35
Move functions around and document them
nomeata Apr 9, 2024
b4dcec3
More tests
nomeata Apr 9, 2024
ddec688
Merge branch 'nightly-with-mathlib' of github.com:leanprover/lean4 in…
nomeata Apr 13, 2024
3fd5fb8
Use PPContext to store MetaM state in thunk
nomeata Apr 13, 2024
19b7d64
Implement FormatWithInfos.append and use here
nomeata Apr 16, 2024
e3f22db
Remove all the .thunk stuff
nomeata Apr 16, 2024
6d7f392
Third try: Now MessageData.ofThunk using dynamic
nomeata Apr 16, 2024
d97c2ab
Remove FormatWithInfos.append code
nomeata Apr 16, 2024
4718b2b
Merge branch 'master' of github.com:leanprover/lean4 into joachim/fai…
nomeata Apr 16, 2024
bf61b5c
Update tests/lean/issue3232.lean
nomeata Apr 18, 2024
7051c2a
Update src/Lean/Message.lean
nomeata Apr 18, 2024
5d7e45c
Panic, not throw
nomeata Apr 18, 2024
a55cc59
MessageData.lazy: Assume context exists
nomeata Apr 18, 2024
c2a3d0c
Merge branch 'master' of github.com:leanprover/lean4 into joachim/fai…
nomeata Apr 22, 2024
43d10c4
Rename ofFormatWithInfos to ofFormatWithInfosM
nomeata Apr 22, 2024
4e4c8d9
Update src/Lean/Message.lean
nomeata Apr 22, 2024
9d2ccd6
Merge branch 'nightly-with-mathlib' of github.com:leanprover/lean4 in…
nomeata May 8, 2024
0fc055c
Update src/Lean/Message.lean
nomeata May 17, 2024
b8f220c
refactor: remove .ofFormat constructor
Vtec234 May 17, 2024
efedf70
refactor: remove PPFormat
Vtec234 May 17, 2024
8316658
doc: breaking changes
Vtec234 May 17, 2024
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
12 changes: 12 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@ fact.def :

* `Option.toMonad` has been renamed to `Option.getM` and the unneeded `[Monad m]` instance argument has been removed.

* The `MessageData.ofPPFormat` constructor has been removed.
Its functionality has been split into two:

- for lazy structured messages, please use `MessageData.lazy`;
- for embedding `Format` or `FormatWithInfos`, use `MessageData.ofFormatWithInfos`.

An example migration can be found in [#3929](https://github.com/leanprover/lean4/pull/3929/files#diff-5910592ab7452a0e1b2616c62d22202d2291a9ebb463145f198685aed6299867L109).

* The `MessageData.ofFormat` constructor has been turned into a function.
If you need to inspect `MessageData`,
you can pattern-match on `MessageData.ofFormatWithInfos`.

v4.7.0
---------

Expand Down
3 changes: 2 additions & 1 deletion src/Lean/CoreM.lean
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,8 @@ export Core (CoreM mkFreshUserName checkSystem withCurrHeartbeats)
We used a similar hack at `Exception.isMaxRecDepth` -/
def Exception.isMaxHeartbeat (ex : Exception) : Bool :=
match ex with
| Exception.error _ (MessageData.ofFormat (Std.Format.text msg)) => "(deterministic) timeout".isPrefixOf msg
| Exception.error _ (MessageData.ofFormatWithInfos ⟨Std.Format.text msg, _⟩) =>
"(deterministic) timeout".isPrefixOf msg
| _ => false

/-- Creates the expression `d → b` -/
Expand Down
2 changes: 1 addition & 1 deletion src/Lean/Exception.lean
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ been defined yet.
-/
def Exception.isMaxRecDepth (ex : Exception) : Bool :=
match ex with
| error _ (MessageData.ofFormat (Std.Format.text msg)) => msg == maxRecDepthErrorMessage
| error _ (MessageData.ofFormatWithInfos ⟨Std.Format.text msg, _⟩) => msg == maxRecDepthErrorMessage
| _ => false

/--
Expand Down
81 changes: 49 additions & 32 deletions src/Lean/Message.lean
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@ structure NamingContext where
currNamespace : Name
openDecls : List OpenDecl

/-- Lazily formatted text to be used in `MessageData`. -/
structure PPFormat where
/-- Pretty-prints text using surrounding context, if any. -/
pp : Option PPContext → IO FormatWithInfos
/-- Searches for synthetic sorries in original input. Used to filter out certain messages. -/
hasSyntheticSorry : MetavarContext → Bool := fun _ => false

structure TraceData where
/-- Trace class, e.g. `Elab.step`. -/
cls : Name
Expand All @@ -60,10 +53,9 @@ structure TraceData where

/-- Structured message data. We use it for reporting errors, trace messages, etc. -/
inductive MessageData where
/-- Eagerly formatted text. We inspect this in various hacks, so it is not immediately subsumed by `ofPPFormat`. -/
| ofFormat : Format → MessageData
/-- Lazily formatted text. -/
| ofPPFormat : PPFormat → MessageData
/-- Eagerly formatted text with info annotations.
This constructor is inspected in various hacks. -/
| ofFormatWithInfos : FormatWithInfos → MessageData
nomeata marked this conversation as resolved.
Show resolved Hide resolved
| ofGoal : MVarId → MessageData
/-- `withContext ctx d` specifies the pretty printing context `(env, mctx, lctx, opts)` for the nested expressions in `d`. -/
| withContext : MessageDataContext → MessageData → MessageData
Expand All @@ -78,12 +70,45 @@ inductive MessageData where
Example: an inspector that tries to find "definitional equality failures" may look for the tag "DefEqFailure". -/
| tagged : Name → MessageData → MessageData
| trace (data : TraceData) (msg : MessageData) (children : Array MessageData)
deriving Inhabited
/-- A lazy message.
The provided thunk will not be run until it is about to be displayed.
This can save computation in cases where the message may never be seen,
e.g. when nested inside a collapsed trace.

The `Dynamic` value is expected to be a `MessageData`,
which is a workaround for the positivity restriction.

If the thunked message is produced for a term that contains a synthetic sorry,
`hasSyntheticSorry` should return `true`.
This is used to filter out certain messages. -/
| ofLazy (f : Option PPContext → IO Dynamic) (hasSyntheticSorry : MetavarContext → Bool)
deriving Inhabited, TypeName

namespace MessageData

/-- Eagerly formatted text. -/
def ofFormat (fmt : Format) : MessageData := .ofFormatWithInfos ⟨fmt, .empty⟩

/--
Lazy message data production, with access to the context as given by
a surrounding `MessageData.withContext` (which is expected to exist).
-/
def lazy (f : PPContext → IO MessageData)
(hasSyntheticSorry : MetavarContext → Bool := fun _ => false) : MessageData :=
.ofLazy (hasSyntheticSorry := hasSyntheticSorry) fun ctx? => do
let msg ← match ctx? with
| .none => pure (.ofFormat "(invalid MessageData.lazy, missing context)")
| .some ctx => f ctx
return Dynamic.mk msg

variable (p : Name → Bool) in
/-- Returns true when the message contains a `MessageData.tagged tag ..` constructor where `p tag` is true. -/
/-- Returns true when the message contains a `MessageData.tagged tag ..` constructor where `p tag`
is true.

This does not descend into lazily generated subtress (`.ofLazy`); message tags
of interest (like those added by `logLinter`) are expected to be near the root
of the `MessageData`, and not hidden inside `.ofLazy`.
-/
partial def hasTag : MessageData → Bool
| withContext _ msg => hasTag msg
| withNamingContext _ msg => hasTag msg
Expand All @@ -106,34 +131,22 @@ def mkPPContext (nCtx : NamingContext) (ctx : MessageDataContext) : PPContext :=
def ofSyntax (stx : Syntax) : MessageData :=
-- discard leading/trailing whitespace
let stx := stx.copyHeadTailInfoFrom .missing
.ofPPFormat {
pp := fun
| some ctx => ppTerm ctx ⟨stx⟩ -- HACK: might not be a term
| none => return stx.formatStx
}
.lazy fun ctx => ofFormat <$> ppTerm ctx ⟨stx⟩ -- HACK: might not be a term

def ofExpr (e : Expr) : MessageData :=
.ofPPFormat {
pp := fun
| some ctx => ppExprWithInfos ctx e
| none => return format (toString e)
hasSyntheticSorry := (instantiateMVarsCore · e |>.1.hasSyntheticSorry)
}
.lazy (fun ctx => ofFormatWithInfos <$> ppExprWithInfos ctx e)
(fun mctx => instantiateMVarsCore mctx e |>.1.hasSyntheticSorry)

def ofLevel (l : Level) : MessageData :=
.ofPPFormat {
pp := fun
| some ctx => ppLevel ctx l
| none => return format l
}
.lazy fun ctx => ofFormat <$> ppLevel ctx l

def ofName (n : Name) : MessageData := ofFormat (format n)

partial def hasSyntheticSorry (msg : MessageData) : Bool :=
visit none msg
where
visit (mctx? : Option MetavarContext) : MessageData → Bool
| ofPPFormat f => f.hasSyntheticSorry (mctx?.getD {})
| ofLazy _ f => f (mctx?.getD {})
| withContext ctx msg => visit ctx.mctx msg
| withNamingContext _ msg => visit mctx? msg
| nest _ msg => visit mctx? msg
Expand All @@ -144,8 +157,7 @@ where
| _ => false

partial def formatAux : NamingContext → Option MessageDataContext → MessageData → IO Format
| _, _, ofFormat fmt => return fmt
| nCtx, ctx?, ofPPFormat f => (·.fmt) <$> f.pp (ctx?.map (mkPPContext nCtx))
| _, _, ofFormatWithInfos fmt => return fmt.1
| _, none, ofGoal mvarId => return "goal " ++ format (mkMVar mvarId)
| nCtx, some ctx, ofGoal mvarId => ppGoal (mkPPContext nCtx ctx) mvarId
| nCtx, _, withContext ctx d => formatAux nCtx ctx d
Expand All @@ -161,6 +173,11 @@ partial def formatAux : NamingContext → Option MessageDataContext → MessageD
msg := f!"{msg} {(← formatAux nCtx ctx header).nest 2}"
let children ← children.mapM (formatAux nCtx ctx)
return .nest 2 (.joinSep (msg::children.toList) "\n")
| nCtx, ctx?, ofLazy pp _ => do
let dyn ← pp (ctx?.map (mkPPContext nCtx))
let some msg := dyn.get? MessageData
| panic! s!"MessageData.ofLazy: expected MessageData in Dynamic, got {dyn.typeName}"
formatAux nCtx ctx? msg

protected def format (msgData : MessageData) : IO Format :=
formatAux { currNamespace := Name.anonymous, openDecls := [] } none msgData
Expand Down
12 changes: 10 additions & 2 deletions src/Lean/Meta/Tactic/Apply.lean
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Lean.Util.FindMVar
import Lean.Meta.SynthInstance
import Lean.Meta.CollectMVars
import Lean.Meta.Tactic.Util
import Lean.PrettyPrinter

namespace Lean.Meta
/-- Controls which new mvars are turned in to goals by the `apply` tactic.
Expand Down Expand Up @@ -50,8 +51,15 @@ def getExpectedNumArgs (e : Expr) : MetaM Nat := do
let (numArgs, _) ← getExpectedNumArgsAux e
pure numArgs

private def throwApplyError {α} (mvarId : MVarId) (eType : Expr) (targetType : Expr) : MetaM α :=
throwTacticEx `apply mvarId m!"failed to unify{indentExpr eType}\nwith{indentExpr targetType}"
private def throwApplyError {α} (mvarId : MVarId) (eType : Expr) (targetType : Expr) : MetaM α := do
let explanation := MessageData.lazy
(f := fun ppctxt => ppctxt.runMetaM do
let (eType, targetType) ← addPPExplicitToExposeDiff eType targetType
return m!"{indentExpr eType}\nwith{indentExpr targetType}")
(hasSyntheticSorry := fun mvarctxt =>
(instantiateMVarsCore mvarctxt eType |>.1.hasSyntheticSorry) ||
(instantiateMVarsCore mvarctxt targetType |>.1.hasSyntheticSorry))
throwTacticEx `apply mvarId m!"failed to unify{explanation}"

def synthAppInstances (tacticName : Name) (mvarId : MVarId) (newMVars : Array Expr) (binderInfos : Array BinderInfo)
(synthAssignedInstances : Bool) (allowSynthFailures : Bool) : MetaM Unit :=
Expand Down
20 changes: 9 additions & 11 deletions src/Lean/PrettyPrinter.lean
Original file line number Diff line number Diff line change
Expand Up @@ -110,30 +110,28 @@ namespace MessageData
open Lean PrettyPrinter Delaborator

/--
Turns a `MetaM FormatWithInfos` into a `MessageData` using `.ofPPFormat` and running the monadic value in the given context.
Uses the `pp.tagAppFns` option to annotate constants with terminfo, which is necessary for seeing the type on mouse hover.
Turns a `MetaM FormatWithInfos` into a `MessageData.lazy` which will run the monadic value.
Uses the `pp.tagAppFns` option to annotate constants with terminfo,
which is necessary for seeing the type on mouse hover.
-/
def ofFormatWithInfos
(fmt : MetaM FormatWithInfos)
(noContext : Unit → Format := fun _ => "<no context, could not generate MessageData>") : MessageData :=
.ofPPFormat
{ pp := fun
| some ctx => ctx.runMetaM <| withOptions (pp.tagAppFns.set · true) fmt
| none => return noContext () }
def ofFormatWithInfosM (fmt : MetaM FormatWithInfos) : MessageData :=
.lazy fun ctx => ctx.runMetaM <|
withOptions (pp.tagAppFns.set · true) <|
.ofFormatWithInfos <$> fmt

/-- Pretty print a const expression using `delabConst` and generate terminfo.
This function avoids inserting `@` if the constant is for a function whose first
argument is implicit, which is what the default `toMessageData` for `Expr` does.
Panics if `e` is not a constant. -/
def ofConst (e : Expr) : MessageData :=
if e.isConst then
.ofFormatWithInfos (PrettyPrinter.ppExprWithInfos (delab := delabConst) e) fun _ => f!"{e}"
.ofFormatWithInfosM (PrettyPrinter.ppExprWithInfos (delab := delabConst) e)
else
panic! "not a constant"

/-- Generates `MessageData` for a declaration `c` as `c.{<levels>} <params> : <type>`, with terminfo. -/
def signature (c : Name) : MessageData :=
.ofFormatWithInfos (PrettyPrinter.ppSignature c) fun _ => f!"{c}"
.ofFormatWithInfosM (PrettyPrinter.ppSignature c)

end MessageData

Expand Down
4 changes: 4 additions & 0 deletions src/Lean/Util/PPExt.lean
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ structure PPContext where
openDecls : List OpenDecl := []

abbrev PrettyPrinter.InfoPerPos := RBMap Nat Elab.Info compare
/-- A format tree with `Elab.Info` annotations.
Each `.tag n _` node is annotated with `infos[n]`.
This is used to attach semantic data such as expressions
to pretty-printer outputs. -/
structure FormatWithInfos where
fmt : Format
infos : PrettyPrinter.InfoPerPos
Expand Down
11 changes: 7 additions & 4 deletions src/Lean/Widget/InteractiveDiagnostic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,8 @@ where
}

go (nCtx : NamingContext) : Option MessageDataContext → MessageData → MsgFmtM Format
| _, ofFormat fmt => withIgnoreTags fmt
| none, ofPPFormat fmt => (·.fmt) <$> fmt.pp none
| some ctx, ofPPFormat fmt => do
let ⟨fmt, infos⟩ ← fmt.pp (mkPPContext nCtx ctx)
| none, ofFormatWithInfos ⟨fmt, _⟩ => withIgnoreTags fmt
| some ctx, ofFormatWithInfos ⟨fmt, infos⟩ => do
let t ← pushEmbed <| EmbedFmt.code (mkContextInfo nCtx ctx) infos
return Format.tag t fmt
| none, ofGoal mvarId => pure $ "goal " ++ format (mkMVar mvarId)
Expand Down Expand Up @@ -162,6 +160,11 @@ where
pure (.strict (← children.mapM (go nCtx ctx)))
let e := .trace data.cls header data.collapsed nodes
return .tag (← pushEmbed e) ".\n"
| ctx?, ofLazy f _ => do
let dyn ← f (ctx?.map (mkPPContext nCtx))
let some msg := dyn.get? MessageData
| throw <| IO.userError "MessageData.ofLazy: expected MessageData in Dynamic"
go nCtx ctx? msg

/-- Recursively moves child nodes after the first `blockSize` into a new "more" node. -/
chopUpChildren (cls : Name) (blockSize : Nat) (children : Subarray MessageData) :
Expand Down
11 changes: 11 additions & 0 deletions tests/lean/issue3232.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
inductive foo {n : Nat} : Prop

-- Check that the error message will print the types with implicit arguments shown
example (h : @foo 42) : @foo 23 := by
apply h

example : (1 : Nat) = 1 := by
apply (rfl : (1 : Int) = 1)

example : PUnit.{0} = PUnit.{0} := by
apply Eq.refl PUnit.{1} -- TODO: addPPExplicitToExposeDiff is not handling this yet
16 changes: 16 additions & 0 deletions tests/lean/issue3232.lean.expected.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
issue3232.lean:5:2-5:9: error: tactic 'apply' failed, failed to unify
@foo 42
with
@foo 23
h : foo
⊢ foo
issue3232.lean:8:2-8:29: error: tactic 'apply' failed, failed to unify
@OfNat.ofNat Int 1 instOfNat = 1
with
@OfNat.ofNat Nat 1 (instOfNatNat 1) = 1
⊢ 1 = 1
issue3232.lean:11:2-11:25: error: tactic 'apply' failed, failed to unify
PUnit = PUnit
with
PUnit = PUnit
⊢ PUnit = PUnit
Loading