This PR makes it harder to create "fake" theorems about definitions that are stubbed-out with `sorry` by ensuring that each `sorry` is not definitionally equal to any other. For example, this now fails: ```lean example : (sorry : Nat) = sorry := rfl -- fails ``` However, this still succeeds, since the `sorry` is a single indeterminate `Nat`: ```lean def f (n : Nat) : Nat := sorry example : f 0 = f 1 := rfl -- succeeds ``` One can be more careful by putting parameters to the right of the colon: ```lean def f : (n : Nat) → Nat := sorry example : f 0 = f 1 := rfl -- fails ``` Most sources of synthetic sorries (recall: a sorry that originates from the elaborator) are now unique, except for elaboration errors, since making these unique tends to cause a confusing cascade of errors. In general, however, such sorries are labeled. This enables "go to definition" on `sorry` in the Infoview, which brings you to its origin. The option `set_option pp.sorrySource true` causes the pretty printer to show source position information on sorries. **Details:** * Adds `Lean.Meta.mkLabeledSorry`, which creates a sorry that is labeled with its source position. For example, `(sorry : Nat)` might elaborate to ``` sorryAx (Lean.Name → Nat) false `lean.foo.12.8.12.13.8.13._sorry._@.lean.foo._hyg.153 ``` It can either be made unique (like the above) or merely labeled. Labeled sorries use an encoding that does not impact defeq: ``` sorryAx (Unit → Nat) false (Function.const Lean.Name () `lean.foo.14.7.13.7.13.69._sorry._@.lean.foo._hyg.174) ``` * Makes the `sorry` term, the `sorry` tactic, and every elaboration failure create labeled sorries. Most are unique sorries, but some elaboration errors are labeled sorries. * Renames `OmissionInfo` to `DelabTermInfo` and adds configuration options to control LSP interactions. One field is a source position to use for "go to definition". This is used to implement "go to definition" on labeled sorries. * Makes hovering over a labeled `sorry` show something friendlier than that full `sorryAx` expression. Instead, the first hover shows the simplified ``sorry `«lean.foo:48:11»``. Hovering over that hover shows the full `sorryAx`. Setting `set_option pp.sorrySource true` makes `sorry` always start with printing with this source position information. * Removes `Lean.Meta.mkSyntheticSorry` in favor of `Lean.Meta.mkSorry` and `Lean.Meta.mkLabeledSorry`. * Changes `sorryAx` so that the `synthetic` argument is no longer optional. * Gives `addPPExplicitToExposeDiff` awareness of labeled sorries. It can set `pp.sorrySource` when source positions differ. * Modifies the delaborator framework so that delaborators can set Info themselves without it being overwritten. Incidentally closes #4972. Inspired by [this Zulip thread](https://leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/Is.20a.20.60definition_wanted.60.20keyword.20possible.3F/near/477260277).
115 lines
5.1 KiB
Text
115 lines
5.1 KiB
Text
/-
|
||
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Authors: Kyle Miller
|
||
-/
|
||
prelude
|
||
import Lean.Data.Lsp.Utf16
|
||
import Lean.Meta.InferType
|
||
import Lean.Util.Recognizers
|
||
|
||
/-!
|
||
# Utilities for creating and recognizing `sorry`
|
||
|
||
This module develops material for creating and recognizing `sorryAx` terms that encode originating source positions.
|
||
There are three orthogonal configurations for sorries:
|
||
|
||
- The sorry could be *synthetic*. When elaboration fails on some subterm, then we can use a sorry to fill in the missing subterm.
|
||
In this case elaboration marks the sorry as being "synthetic" while logging an error.
|
||
The presence of synthetic sorries tends to suppress further errors. For example, the "this declaration contains sorry" error
|
||
is not triggered for synthetic sorries as we assume there is already an error message logged.
|
||
|
||
- The sorry could be *unique*. A unique sorry is not definitionally equal to any other sorry, even if they have the same type.
|
||
Normally `sorryAx α s = sorryAx α s` is a definitional equality. Unique sorries insert a unique tag `t` using the encoding `sorryAx (τ → α) s t`.
|
||
|
||
- The sorry could be *labeled*. A labeled sorry contains source position information, supporting the LSP "go to definition" feature in the Infoview,
|
||
and also supporting pretty printing the sorry with an indication of source position when the option `pp.sorrySource` is true.
|
||
-/
|
||
|
||
namespace Lean.Meta
|
||
|
||
/--
|
||
Returns `sorryAx type synthetic`. Recall that `synthetic` is true if this sorry is from an error.
|
||
|
||
See also `Lean.Meta.mkLabeledSorry`, for creating a `sorry` that is labeled or unique.
|
||
-/
|
||
def mkSorry (type : Expr) (synthetic : Bool) : MetaM Expr := do
|
||
let u ← getLevel type
|
||
return mkApp2 (mkConst ``sorryAx [u]) type (toExpr synthetic)
|
||
|
||
structure SorryLabelView where
|
||
/--
|
||
Records the origin module name, logical source position, and LSP range for the `sorry`.
|
||
The logical source position is used when displaying the sorry when the `pp.sorrySource` option is true,
|
||
and the LSP range is used for "go to definition" in the Infoview.
|
||
-/
|
||
module? : Option DeclarationLocation := none
|
||
|
||
def SorryLabelView.encode (view : SorryLabelView) : CoreM Name :=
|
||
let name :=
|
||
if let some { module, range := { pos, endPos, charUtf16, endCharUtf16 } } := view.module? then
|
||
module
|
||
|>.num pos.line |>.num pos.column
|
||
|>.num endPos.line |>.num endPos.column
|
||
|>.num charUtf16 |>.num endCharUtf16
|
||
else
|
||
.anonymous
|
||
mkFreshUserName (name.str "_sorry")
|
||
|
||
def SorryLabelView.decode? (name : Name) : Option SorryLabelView := do
|
||
guard <| name.hasMacroScopes
|
||
let .str name "_sorry" := name.eraseMacroScopes | failure
|
||
if let .num (.num (.num (.num (.num (.num module posLine) posCol) endLine) endCol) charUtf16) endCharUtf16 := name then
|
||
return { module? := some { module, range := { pos := ⟨posLine, posCol⟩, endPos := ⟨endLine, endCol⟩, charUtf16, endCharUtf16 } } }
|
||
else
|
||
failure
|
||
|
||
/--
|
||
Constructs a `sorryAx`.
|
||
* If the current ref has a source position, then creates a labeled sorry.
|
||
This supports "go to definition" in the InfoView and pretty printing a source position when the `pp.sorrySource` option is true.
|
||
* If `synthetic` is true, then the `sorry` is regarded as being generated by the elaborator.
|
||
The caller should ensure that there is an associated error logged.
|
||
* If `unique` is true, the `sorry` is unique, in the sense that it is not defeq to any other `sorry` created by `mkLabeledSorry`.
|
||
-/
|
||
def mkLabeledSorry (type : Expr) (synthetic : Bool) (unique : Bool) : MetaM Expr := do
|
||
let tag ←
|
||
if let (some startSPos, some endSPos) := ((← getRef).getPos?, (← getRef).getTailPos?) then
|
||
let fileMap ← getFileMap
|
||
SorryLabelView.encode {
|
||
module? := some {
|
||
module := (← getMainModule)
|
||
range := {
|
||
pos := fileMap.toPosition startSPos
|
||
endPos := fileMap.toPosition endSPos
|
||
charUtf16 := (fileMap.utf8PosToLspPos startSPos).character
|
||
endCharUtf16 := (fileMap.utf8PosToLspPos endSPos).character
|
||
}
|
||
}
|
||
}
|
||
else
|
||
SorryLabelView.encode {}
|
||
if unique then
|
||
let e ← mkSorry (mkForall `tag .default (mkConst ``Lean.Name) type) synthetic
|
||
return .app e (toExpr tag)
|
||
else
|
||
let e ← mkSorry (mkForall `tag .default (mkConst ``Unit) type) synthetic
|
||
let tag' := mkApp4 (mkConst ``Function.const [levelOne, levelOne]) (mkConst ``Unit) (mkConst ``Lean.Name) (mkConst ``Unit.unit) (toExpr tag)
|
||
return .app e tag'
|
||
|
||
/--
|
||
Returns a `SorryLabelView` if `e` is an application of an expression returned by `mkLabeledSorry`.
|
||
If it is, then the `sorry` takes the first three arguments, and the tag is at argument 3.
|
||
-/
|
||
def isLabeledSorry? (e : Expr) : Option SorryLabelView := do
|
||
guard <| e.isAppOf ``sorryAx
|
||
let numArgs := e.getAppNumArgs
|
||
guard <| numArgs ≥ 3
|
||
let arg := e.getArg! 2
|
||
if let some tag := arg.name? then
|
||
SorryLabelView.decode? tag
|
||
else
|
||
guard <| arg.isAppOfArity ``Function.const 4
|
||
guard <| arg.appFn!.appArg!.isAppOfArity ``Unit.unit 0
|
||
let tag ← arg.appArg!.name?
|
||
SorryLabelView.decode? tag
|