Skip to content

Commit 429734c

Browse files
authored
feat: suggest deriving an instance when the instance might be derivable (#11346)
This PR modifies the error message for type synthesis failure for the case where the type class in question is potentially derivable using a `deriving` command. Also changes the error explanation for type class instance synthesis failure with an illustration of this pattern. ## Example ```lean4 inductive MyColor where | chartreuse | sienna | thistle def forceColor (oc : Option MyColor) := oc.get! ``` Before this PR, this gives the potentially confusing impression that Lean may have decided that `MyColor` is _not_ inhabited — people used to Rust may be especially inclined towards this confusion. ``` failed to synthesize instance of type class Inhabited MyColor Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command. ``` After this PR, a targeted hint suggests precisely the command that will fix the issue: ``` error: failed to synthesize instance of type class Inhabited MyColor Hint: Adding the command `deriving instance Inhabited for MyColor` may allow Lean to derive the missing instance. ```
1 parent 35a36ae commit 429734c

File tree

6 files changed

+178
-6
lines changed

6 files changed

+178
-6
lines changed

src/Lean/Elab/Deriving/Basic.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ example, `deriving instance Foo for Bar, Baz` invokes ``fooHandler #[`Bar, `Baz]
223223
def registerDerivingHandler (className : Name) (handler : DerivingHandler) : IO Unit := do
224224
unless (← initializing) do
225225
throw (IO.userError "failed to register deriving handler, it can only be registered during initialization")
226+
Term.registerDerivableClass className
226227
derivingHandlersRef.modify fun m => match m.find? className with
227228
| some handlers => m.insert className (handler :: handlers)
228229
| none => m.insert className [handler]

src/Lean/Elab/Term/TermElabM.lean

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,10 +1119,27 @@ If the `trace.Meta.synthInstance` option is not already set, gives a hint explai
11191119
def useTraceSynthMsg : MessageData :=
11201120
MessageData.lazy fun ctx =>
11211121
if trace.Meta.synthInstance.get ctx.opts then
1122-
pure ""
1122+
pure .nil
11231123
else
11241124
pure <| .hint' s!"Type class instance resolution failures can be inspected with the `set_option {trace.Meta.synthInstance.name} true` command."
11251125

1126+
builtin_initialize derivableRef : IO.Ref NameSet ← IO.mkRef {}
1127+
1128+
/--
1129+
Registers a deriving handler for the purpose of error message delivery in `synthesizeInstMVarCore`.
1130+
This should only be called by `Lean.Elab.Term.registerDerivingHandler`.
1131+
-/
1132+
def registerDerivableClass (className : Name) : IO Unit := do
1133+
unless (← initializing) do
1134+
throw (IO.userError "failed to register derivable class, it can only be registered during initialization")
1135+
derivableRef.modify fun m => m.insert className
1136+
1137+
/--
1138+
Returns whether `className` has a `deriving` handler installed.
1139+
-/
1140+
def hasDerivingHandler (className : Name) : IO Bool := do
1141+
return (← derivableRef.get).contains className
1142+
11261143
/--
11271144
Try to synthesize metavariable using type class resolution.
11281145
This method assumes the local context and local instances of `instMVar` coincide
@@ -1181,7 +1198,16 @@ def synthesizeInstMVarCore (instMVar : MVarId) (maxResultSize? : Option Nat := n
11811198
if (← read).ignoreTCFailures then
11821199
return false
11831200
else
1184-
throwNamedError lean.synthInstanceFailed "failed to synthesize instance of type class{indentExpr type}{extraErrorMsg}{useTraceSynthMsg}"
1201+
let msg ← match type with
1202+
| .app (.const cls _) (.const typ _) =>
1203+
-- This has the structure of a `deriving`-style type class, alter feedback accordingly
1204+
if ← hasDerivingHandler cls then
1205+
pure <| extraErrorMsg ++ .hint' m!"Adding the command `deriving instance {cls} for {typ}` may allow Lean to derive the missing instance."
1206+
else
1207+
pure <| extraErrorMsg ++ useTraceSynthMsg
1208+
| _ =>
1209+
pure <| extraErrorMsg ++ useTraceSynthMsg
1210+
throwNamedError lean.synthInstanceFailed "failed to synthesize instance of type class{indentExpr type}{msg}"
11851211

11861212
def mkCoe (expectedType : Expr) (e : Expr) (f? : Option Expr := none) (errorMsgHeader? : Option String := none)
11871213
(mkErrorMsg? : Option (MVarId → (expectedType e : Expr) → MetaM MessageData) := none)

src/Lean/ErrorExplanations/SynthInstanceFailed.lean

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ The binary operation `+` is associated with the `HAdd` type class, and there's n
4949
strings. The binary operation `++`, associated with the `HAppend` type class, is the correct way to
5050
append strings.
5151
52-
## Modifying the Type of an Argument
52+
## Arguments Have the Wrong Type
5353
5454
```lean broken
5555
def x : Int := 3
@@ -69,6 +69,55 @@ def x : Int := 3
6969
Lean does not allow integers and strings to be added directly. The function `ToString.toString` uses
7070
type class overloading to convert values to strings; by successfully searching for an instance of
7171
`ToString Int`, the second example will succeed.
72+
73+
## Missing Type Class Instance
74+
75+
```lean broken
76+
inductive MyColor where
77+
| chartreuse | sienna | thistle
78+
79+
def forceColor (oc : Option MyColor) :=
80+
oc.get!
81+
```
82+
```output
83+
failed to synthesize instance of type class
84+
Inhabited MyColor
85+
86+
Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
87+
```
88+
```lean fixed (title := "Fixed (derive instance when defining type)")
89+
inductive MyColor where
90+
| chartreuse | sienna | thistle
91+
deriving Inhabited
92+
93+
def forceColor (oc : Option MyColor) :=
94+
oc.get!
95+
```
96+
```lean fixed (title := "Fixed (derive instance separately)")
97+
inductive MyColor where
98+
| chartreuse | sienna | thistle
99+
100+
deriving instance Inhabited for MyColor
101+
102+
def forceColor (oc : Option MyColor) :=
103+
oc.get!
104+
```
105+
```lean fixed (title := "Fixed (define instance)")
106+
inductive MyColor where
107+
| chartreuse | sienna | thistle
108+
109+
instance : Inhabited MyColor where
110+
default := .sienna
111+
112+
def forceColor (oc : Option MyColor) :=
113+
oc.get!
114+
```
115+
116+
Type class synthesis can fail because an instance of the type class simply needs to be provided.
117+
This commonly happens for type classes like `Repr`, `BEq`, `ToJson` and `Inhabited`. Lean can often
118+
[automatically generate instances of the type class with the `deriving` keyword](lean-manual://section/type-class),
119+
either when the type is defined or with the stand-alone `deriving` command.
120+
72121
-/
73122
register_error_explanation lean.synthInstanceFailed {
74123
summary := "Failed to synthesize instance of type class"

tests/lean/defInst.lean.expected.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
defInst.lean:8:26-8:32: error(lean.synthInstanceFailed): failed to synthesize instance of type class
33
BEq Foo
44

5-
Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
5+
Hint: Adding the command `deriving instance BEq for Foo` may allow Lean to derive the missing instance.
66
fun x y => sorry : (x y : Foo) → ?m x y
77
[4, 5, 6]
88
fun x y => x == y : Foo → Foo → Bool

tests/lean/run/derivingReflBEq.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ info: RegularBEq.instLawfulBEqWithHEq.{u_1} {α✝ : Type u_1} {a✝ : Nat} [BEq
6161
error: failed to synthesize instance of type class
6262
BEq Foo
6363
64-
Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
64+
Hint: Adding the command `deriving instance BEq for RegularBEq.Foo` may allow Lean to derive the missing instance.
6565
-/
6666
#guard_msgs in
6767
structure Foo where
@@ -162,7 +162,7 @@ info: LinearBEq.instLawfulBEqWithHEq.{u_1} {α✝ : Type u_1} {a✝ : Nat} [BEq
162162
error: failed to synthesize instance of type class
163163
BEq Foo
164164
165-
Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
165+
Hint: Adding the command `deriving instance BEq for LinearBEq.Foo` may allow Lean to derive the missing instance.
166166
-/
167167
#guard_msgs in
168168
structure Foo where
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import Lean
2+
open Lean
3+
4+
inductive MyColor where
5+
| chartreuse | sienna | thistle
6+
7+
/--
8+
error: failed to synthesize instance of type class
9+
Inhabited MyColor
10+
11+
Hint: Adding the command `deriving instance Inhabited for MyColor` may allow Lean to derive the missing instance.
12+
-/
13+
#guard_msgs in
14+
def forceColor (oc : Option MyColor) :=
15+
oc.get!
16+
17+
/--
18+
error: failed to synthesize instance of type class
19+
ToJson MyColor
20+
21+
Hint: Adding the command `deriving instance Lean.ToJson for MyColor` may allow Lean to derive the missing instance.
22+
-/
23+
#guard_msgs in
24+
def jsonMaybeColor (oc : MyColor) :=
25+
ToJson.toJson oc
26+
27+
/--
28+
error: failed to synthesize instance of type class
29+
ToJson MyColor
30+
31+
Hint: Adding the command `deriving instance Lean.ToJson for MyColor` may allow Lean to derive the missing instance.
32+
---
33+
error: failed to synthesize instance of type class
34+
ToExpr MyColor
35+
36+
Hint: Adding the command `deriving instance Lean.ToExpr for MyColor` may allow Lean to derive the missing instance.
37+
---
38+
error: No deriving handlers have been implemented for class `ToString`
39+
-/
40+
#guard_msgs in
41+
inductive MyList where
42+
| nil : MyList
43+
| cons : MyColor → MyList
44+
deriving ToJson, ToExpr, ToString, Nonempty
45+
46+
47+
-- It would be very cool if this case could give us "maybe derive ToJson MyColor", but that's more sophisticated than we can presently manage
48+
/--
49+
error: failed to synthesize instance of type class
50+
ToJson (Option MyColor)
51+
52+
Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
53+
-/
54+
#guard_msgs in
55+
def jsonColor (oc : Option MyColor) :=
56+
ToJson.toJson oc
57+
58+
-- It would be very cool if this case could give us "maybe derive DecidableEq MyColor"
59+
/--
60+
error: failed to synthesize instance of type class
61+
Decidable (oc = some MyColor.thistle)
62+
63+
Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
64+
-/
65+
#guard_msgs in
66+
def checkColor (oc : Option MyColor) :=
67+
if oc = .some .thistle then 1 else 2
68+
69+
inductive MyTree where
70+
| nil : MyTree
71+
| cons : MyColor → MyTree → MyTree
72+
73+
-- The suggestion here will fail, it would be super awesome if we gave the full recommendation,
74+
-- but each hint makes progress
75+
/--
76+
error: failed to synthesize instance of type class
77+
ToJson MyTree
78+
79+
Hint: Adding the command `deriving instance Lean.ToJson for MyTree` may allow Lean to derive the missing instance.
80+
-/
81+
#guard_msgs in
82+
def jsonListColor (oc : MyTree) :=
83+
ToJson.toJson oc
84+
85+
/--
86+
error: failed to synthesize instance of type class
87+
ToJson MyColor
88+
89+
Hint: Adding the command `deriving instance Lean.ToJson for MyColor` may allow Lean to derive the missing instance.
90+
-/
91+
#guard_msgs in
92+
deriving instance Lean.ToJson for MyTree
93+
94+
deriving instance Lean.ToJson for MyColor
95+
96+
deriving instance Lean.ToJson for MyTree

0 commit comments

Comments
 (0)