Skip to content

Commit d3ee0be

Browse files
Khanomeata
andauthored
feat: show signature elaboration errors on body parse error (#4267)
Fixes #3556 --------- Co-authored-by: Joachim Breitner <[email protected]>
1 parent d1a96f6 commit d3ee0be

8 files changed

+48
-30
lines changed

src/Lean/Elab/DefView.lean

+14-9
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ instance : Language.ToSnapshotTree HeaderProcessedSnapshot where
8484
/-- State before elaboration of a mutual definition. -/
8585
structure DefParsed where
8686
/--
87-
Input substring uniquely identifying header elaboration result given the same `Environment`.
88-
If missing, results should never be reused.
87+
Unstructured syntax object comprising the full "header" of the definition from the modifiers
88+
(incl. docstring) up to the value, used for determining header elaboration reuse.
8989
-/
90-
headerSubstr? : Option Substring
90+
fullHeaderRef : Syntax
9191
/-- Elaboration result, unless fatal exception occurred. -/
9292
headerProcessedSnap : SnapshotTask (Option HeaderProcessedSnapshot)
9393
deriving Nonempty
@@ -106,6 +106,11 @@ end Snapshots
106106
structure DefView where
107107
kind : DefKind
108108
ref : Syntax
109+
/--
110+
An unstructured syntax object that comprises the "header" of the definition, i.e. everything up
111+
to the value. Used as a more specific ref for header elaboration.
112+
-/
113+
headerRef : Syntax
109114
modifiers : Modifiers
110115
declId : Syntax
111116
binders : Syntax
@@ -132,20 +137,20 @@ def mkDefViewOfAbbrev (modifiers : Modifiers) (stx : Syntax) : DefView :=
132137
let (binders, type) := expandOptDeclSig stx[2]
133138
let modifiers := modifiers.addAttribute { name := `inline }
134139
let modifiers := modifiers.addAttribute { name := `reducible }
135-
{ ref := stx, kind := DefKind.abbrev, modifiers,
140+
{ ref := stx, headerRef := mkNullNode stx.getArgs[:3], kind := DefKind.abbrev, modifiers,
136141
declId := stx[1], binders, type? := type, value := stx[3] }
137142

138143
def mkDefViewOfDef (modifiers : Modifiers) (stx : Syntax) : DefView :=
139144
-- leading_parser "def " >> declId >> optDeclSig >> declVal >> optDefDeriving
140145
let (binders, type) := expandOptDeclSig stx[2]
141146
let deriving? := if stx[4].isNone then none else some stx[4][1].getSepArgs
142-
{ ref := stx, kind := DefKind.def, modifiers,
147+
{ ref := stx, headerRef := mkNullNode stx.getArgs[:3], kind := DefKind.def, modifiers,
143148
declId := stx[1], binders, type? := type, value := stx[3], deriving? }
144149

145150
def mkDefViewOfTheorem (modifiers : Modifiers) (stx : Syntax) : DefView :=
146151
-- leading_parser "theorem " >> declId >> declSig >> declVal
147152
let (binders, type) := expandDeclSig stx[2]
148-
{ ref := stx, kind := DefKind.theorem, modifiers,
153+
{ ref := stx, headerRef := mkNullNode stx.getArgs[:3], kind := DefKind.theorem, modifiers,
149154
declId := stx[1], binders, type? := some type, value := stx[3] }
150155

151156
def mkDefViewOfInstance (modifiers : Modifiers) (stx : Syntax) : CommandElabM DefView := do
@@ -166,7 +171,7 @@ def mkDefViewOfInstance (modifiers : Modifiers) (stx : Syntax) : CommandElabM De
166171
trace[Elab.instance.mkInstanceName] "generated {(← getCurrNamespace) ++ id}"
167172
pure <| mkNode ``Parser.Command.declId #[mkIdentFrom stx id, mkNullNode]
168173
return {
169-
ref := stx, kind := DefKind.def, modifiers := modifiers,
174+
ref := stx, headerRef := mkNullNode stx.getArgs[:5], kind := DefKind.def, modifiers := modifiers,
170175
declId := declId, binders := binders, type? := type, value := stx[5]
171176
}
172177

@@ -179,7 +184,7 @@ def mkDefViewOfOpaque (modifiers : Modifiers) (stx : Syntax) : CommandElabM DefV
179184
let val ← if modifiers.isUnsafe then `(default_or_ofNonempty% unsafe) else `(default_or_ofNonempty%)
180185
`(Parser.Command.declValSimple| := $val)
181186
return {
182-
ref := stx, kind := DefKind.opaque, modifiers := modifiers,
187+
ref := stx, headerRef := mkNullNode stx.getArgs[:3], kind := DefKind.opaque, modifiers := modifiers,
183188
declId := stx[1], binders := binders, type? := some type, value := val
184189
}
185190

@@ -188,7 +193,7 @@ def mkDefViewOfExample (modifiers : Modifiers) (stx : Syntax) : DefView :=
188193
let (binders, type) := expandOptDeclSig stx[1]
189194
let id := mkIdentFrom stx `_example
190195
let declId := mkNode ``Parser.Command.declId #[id, mkNullNode]
191-
{ ref := stx, kind := DefKind.example, modifiers := modifiers,
196+
{ ref := stx, headerRef := mkNullNode stx.getArgs[:2], kind := DefKind.example, modifiers := modifiers,
192197
declId := declId, binders := binders, type? := type, value := stx[2] }
193198

194199
def isDefLike (stx : Syntax) : Bool :=

src/Lean/Elab/MutualDef.lean

+18-13
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ private def elabHeaders (views : Array DefView)
131131
(bodyPromises : Array (IO.Promise (Option BodyProcessedSnapshot)))
132132
(tacPromises : Array (IO.Promise Tactic.TacticParsedSnapshot)) :
133133
TermElabM (Array DefViewElabHeader) := do
134-
let expandedDeclIds ← views.mapM fun view => withRef view.ref do
134+
let expandedDeclIds ← views.mapM fun view => withRef view.headerRef do
135135
Term.expandDeclId (← getCurrNamespace) (← getLevelNames) view.declId view.modifiers
136136
withAutoBoundImplicitForbiddenPred (fun n => expandedDeclIds.any (·.shortName == n)) do
137137
let mut headers := #[]
@@ -181,9 +181,13 @@ private def elabHeaders (views : Array DefView)
181181
reuseBody := false
182182

183183
let header ← withRestoreOrSaveFull reusableResult? fun save => do
184-
withRef view.ref do
185-
addDeclarationRanges declName view.ref
184+
withRef view.headerRef do
185+
addDeclarationRanges declName view.ref -- NOTE: this should be the full `ref`
186186
applyAttributesAt declName view.modifiers.attrs .beforeElaboration
187+
-- do not hide header errors on partial body syntax as these two elaboration parts are
188+
-- sufficiently independent
189+
withTheReader Core.Context ({ · with suppressElabErrors :=
190+
view.headerRef.hasMissing && !Command.showPartialSyntaxErrors.get (← getOptions) }) do
187191
withDeclName declName <| withAutoBoundImplicit <| withLevelNames levelNames <|
188192
elabBindersEx view.binders.getArgs fun xs => do
189193
let refForElabFunType := view.value
@@ -961,40 +965,41 @@ end Term
961965
namespace Command
962966

963967
def elabMutualDef (ds : Array Syntax) : CommandElabM Unit := do
968+
let opts ← getOptions
964969
withAlwaysResolvedPromises ds.size fun headerPromises => do
965-
let substr? := (mkNullNode ds).getSubstring?
966970
let snap? := (← read).snap?
967971
let mut views := #[]
968972
let mut defs := #[]
973+
let mut reusedAllHeaders := true
969974
for h : i in [0:ds.size], headerPromise in headerPromises do
970975
let d := ds[i]
971976
let modifiers ← elabModifiers d[0]
972977
if ds.size > 1 && modifiers.isNonrec then
973978
throwErrorAt d "invalid use of 'nonrec' modifier in 'mutual' block"
974979
let mut view ← mkDefView modifiers d[1]
980+
let fullHeaderRef := mkNullNode #[d[0], view.headerRef]
975981
if let some snap := snap? then
976-
-- overapproximation: includes previous bodies as well
977-
let headerSubstr? := return { (← substr?) with stopPos := (← view.value.getPos?) }
978982
view := { view with headerSnap? := some {
979983
old? := do
980-
-- transitioning from `Context.snap?` to `DefView.snap?` invariant: if the elaboration
981-
-- context and state are unchanged, and the substring from the beginning of the first
982-
-- header to the beginning of the current body is unchanged, then the elaboration result for
983-
-- this header (which includes state from elaboration of previous headers!) should be
984-
-- unchanged.
984+
-- transitioning from `Context.snap?` to `DefView.headerSnap?` invariant: if the
985+
-- elaboration context and state are unchanged, and the syntax of this as well as all
986+
-- previous headers is unchanged, then the elaboration result for this header (which
987+
-- includes state from elaboration of previous headers!) should be unchanged.
988+
guard reusedAllHeaders
985989
let old ← snap.old?
986990
-- blocking wait, `HeadersParsedSnapshot` (and hopefully others) should be quick
987991
let old ← old.val.get.toTyped? DefsParsedSnapshot
988992
let oldParsed ← old.defs[i]?
989-
guard <| (← headerSubstr?).sameAs (← oldParsed.headerSubstr?)
993+
guard <| fullHeaderRef.structRangeEqWithTraceReuse opts oldParsed.fullHeaderRef
990994
-- no syntax guard to store, we already did the necessary checks
991995
return ⟨.missing, oldParsed.headerProcessedSnap⟩
992996
new := headerPromise
993997
} }
994998
defs := defs.push {
995-
headerSubstr?
999+
fullHeaderRef
9961000
headerProcessedSnap := { range? := d.getRange?, task := headerPromise.result }
9971001
}
1002+
reusedAllHeaders := reusedAllHeaders && view.headerSnap?.any (·.old?.isSome)
9981003
views := views.push view
9991004
if let some snap := snap? then
10001005
-- no non-fatal diagnostics at this point

tests/lean/3989_1.lean.expected.out

+4
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
3989_1.lean:4:0: error: expected else-alternative for `match_expr`, i.e., `| _ => ...`
2+
3989_1.lean:1:22-1:32: error: function expected at
3+
MetaM
4+
term has type
5+
?m

tests/lean/3989_2.lean.expected.out

+4
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
3989_2.lean:6:0: error: expected else-alternative for `match_expr`, i.e., `| _ => ...`
2+
3989_2.lean:1:22-1:32: error: function expected at
3+
MetaM
4+
term has type
5+
?m
26
Nat : Type

tests/lean/799.lean.expected.out

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
799.lean:3:0-3:11: error: invalid declaration name 'p', there is a section variable with the same name
1+
799.lean:3:0-3:5: error: invalid declaration name 'p', there is a section variable with the same name
22
p : Prop
3-
799.lean:7:0-7:13: error: invalid declaration name 'p', there is a section variable with the same name
3+
799.lean:7:0-7:5: error: invalid declaration name 'p', there is a section variable with the same name
44
p : Prop
55
p : Nat
+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
mutualdef1.lean:9:0-11:12: error: invalid mutually recursive definitions, cannot mix theorems and definitions
2-
mutualdef1.lean:21:0-22:4: error: invalid mutually recursive definitions, cannot mix examples and definitions
3-
mutualdef1.lean:32:7-34:12: error: invalid mutually recursive definitions, cannot mix unsafe and safe definitions
1+
mutualdef1.lean:9:0-9:31: error: invalid mutually recursive definitions, cannot mix theorems and definitions
2+
mutualdef1.lean:21:0-21:13: error: invalid mutually recursive definitions, cannot mix examples and definitions
3+
mutualdef1.lean:32:7-32:34: error: invalid mutually recursive definitions, cannot mix unsafe and safe definitions

tests/lean/sanitychecks.lean.expected.out

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ with errors
44
structural recursion cannot be used
55

66
well-founded recursion cannot be used, 'unsound' does not take any (non-fixed) arguments
7-
sanitychecks.lean:4:8-5:10: error: 'partial' theorems are not allowed, 'partial' is a code generation directive
8-
sanitychecks.lean:7:7-8:10: error: 'unsafe' theorems are not allowed
7+
sanitychecks.lean:4:8-4:32: error: 'partial' theorems are not allowed, 'partial' is a code generation directive
8+
sanitychecks.lean:7:7-7:31: error: 'unsafe' theorems are not allowed
99
sanitychecks.lean:10:0-10:23: error: failed to synthesize
1010
Inhabited False
1111
use `set_option diagnostics true` to get diagnostic information
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
false
1+
false

0 commit comments

Comments
 (0)