Skip to content

Commit ab606ba

Browse files
authored
feat: hint when an autobound variable's type fails to be a function (#11518)
This PR provides an additional hint when the type of an autobound implicit is required to have function type or equality type — this fails, and the existing error message does not address the fact that the source of the error is an unknown identifier that was automatically bound. ## Example ``` import Lean example : MetaM String := pure "" ``` Current error message: ``` Function expected at MetaM but this term has type ?m Note: Expected a function because this term is being applied to the argument String ``` Additional error message provided by this PR: ``` Hint: The identifier `MetaM` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be a function, and a function is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement. ```
1 parent 6ca57a7 commit ab606ba

10 files changed

+144
-10
lines changed

src/Lean/Elab/App.lean

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ private def synthesizePendingAndNormalizeFunType : M Unit := do
245245
.note m!"Expected a function because this term is being applied to the argument\
246246
{indentD <| toMessageData arg}"
247247
else .nil
248-
throwError "Function expected at{indentExpr s.f}\nbut this term has type{indentExpr fType}{extra}"
248+
throwError "Function expected at{indentExpr s.f}\nbut this term has type{indentExpr fType}\
249+
{extra}\
250+
{← hintAutoImplicitFailure s.f}"
249251

250252
/-- Normalize and return the function type. -/
251253
private def normalizeFunType : M Expr := do

src/Lean/Elab/BuiltinNotation.lean

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,10 @@ private def withLocalIdentFor (stx : Term) (e : Expr) (k : Term → TermElabM Ex
461461
let heqType ← inferType heq
462462
let heqType ← instantiateMVars heqType
463463
match (← Meta.matchEq? heqType) with
464-
| none => throwError "invalid `▸` notation, argument{indentExpr heq}\nhas type{indentExpr heqType}\nequality expected"
464+
| none => throwError "invalid `▸` notation, argument{indentExpr heq}\n\
465+
has type{indentExpr heqType}\n\
466+
equality expected\
467+
{← Term.hintAutoImplicitFailure heq (expected := "an equality")}"
465468
| some (α, lhs, rhs) =>
466469
let mut lhs := lhs
467470
let mut rhs := rhs

src/Lean/Elab/ErrorUtils.lean

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ Authors: Rob Simmons
66
module
77

88
prelude
9-
import Init.Notation
10-
import Init.Data.String
9+
import Lean.Message
10+
11+
namespace Lean
1112

1213
/--
1314
Translate numbers (1, 2, 3, 212, 322 ...) to ordinals with appropriate English-language names for
@@ -35,19 +36,49 @@ def _root_.Nat.toOrdinal : Nat -> String
3536
else
3637
s!"{n}th"
3738

39+
class HasOxfordStrings α where
40+
emp : α
41+
and : α
42+
comma : α
43+
commaAnd : α
44+
45+
instance : HasOxfordStrings String where
46+
emp := ""
47+
and := " and "
48+
comma := ", "
49+
commaAnd := ", and "
50+
51+
instance : HasOxfordStrings MessageData where
52+
emp := ""
53+
and := " and "
54+
comma := ", "
55+
commaAnd := ", and "
56+
3857
/--
3958
Make an oxford-comma-separated list of strings.
4059
4160
- `["eats"].toOxford == "eats"`
4261
- `["eats", "shoots"].toOxford == "eats and shoots"`
4362
- `["eats", "shoots", "leaves"] == "eats, shoots, and leaves"`
4463
-/
45-
def _root_.List.toOxford : List String -> String
46-
| [] => ""
64+
def _root_.List.toOxford [Append α] [HasOxfordStrings α] : List α -> α
65+
| [] => HasOxfordStrings.emp
4766
| [a] => a
48-
| [a, b] => a ++ " and " ++ b
49-
| [a, b, c] => a ++ ", " ++ b ++ ", and " ++ c
50-
| a :: as => a ++ ", " ++ toOxford as
67+
| [a, b] => a ++ HasOxfordStrings.and ++ b
68+
| [a, b, c] => a ++ HasOxfordStrings.comma ++ b ++ HasOxfordStrings.commaAnd ++ c
69+
| a :: as => a ++ HasOxfordStrings.comma ++ as.toOxford
70+
71+
class HasPluralDefaults α where
72+
singular : α
73+
plural : α → α
74+
75+
instance : HasPluralDefaults String where
76+
singular := ""
77+
plural := (· ++ "s")
78+
79+
instance : HasPluralDefaults MessageData where
80+
singular := .nil
81+
plural := (m!"{·}s")
5182

5283
/--
5384
Give alternative forms of a string if the `count` is 1 or not.
@@ -59,7 +90,9 @@ Give alternative forms of a string if the `count` is 1 or not.
5990
- `(1).plural "it" "they" == "it"`
6091
- `(2).plural "it" "they" == "they"`
6192
-/
62-
def _root_.Nat.plural (count : Nat) (singular : String := "") (plural : String := singular ++ "s") :=
93+
def _root_.Nat.plural [HasPluralDefaults α] (count : Nat)
94+
(singular : α := HasPluralDefaults.singular)
95+
(plural : α := HasPluralDefaults.plural singular) :=
6396
if count = 1 then
6497
singular
6598
else

src/Lean/Elab/Term/TermElabM.lean

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2169,6 +2169,16 @@ def exprToSyntax (e : Expr) : TermElabM Term := withFreshMacroScope do
21692169
mvar.mvarId!.assign e
21702170
return result
21712171

2172+
def hintAutoImplicitFailure (exp : Expr) (expected := "a function") : TermElabM MessageData := do
2173+
let autoBound := (← readThe Context).autoBoundImplicitContext
2174+
unless autoBound.isSome && exp.isFVar && autoBound.get!.boundVariables.any (· == exp) do
2175+
return .nil
2176+
return .hint' m!"The identifier `{.ofName (← exp.fvarId!.getUserName)}` is unknown, \
2177+
and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly \
2178+
bound variable with an unknown type. \
2179+
However, the unknown type cannot be {expected}, and {expected} is what Lean expects here. \
2180+
This is often the result of a typo or a missing `import` or `open` statement."
2181+
21722182
end Term
21732183

21742184
open Term in

tests/lean/302.lean.expected.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ but this term has type
55

66
Note: Expected a function because this term is being applied to the argument
77
0
8+
9+
Hint: The identifier `m` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be a function, and a function is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ but this term has type
66

77
Note: Expected a function because this term is being applied to the argument
88
Expr
9+
10+
Hint: The identifier `MetaM` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be a function, and a function is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ but this term has type
66

77
Note: Expected a function because this term is being applied to the argument
88
Expr
9+
10+
Hint: The identifier `MetaM` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be a function, and a function is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.
911
Nat : Type

tests/lean/autoBoundPostponeLoop.lean.expected.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ autoBoundPostponeLoop.lean:5:12-5:18: error: invalid `▸` notation, argument
33
has type
44
?m
55
equality expected
6+
7+
Hint: The identifier `h` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be an equality, and an equality is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Lean
2+
3+
example : Option String := sorry
4+
example : Maybe String := sorry
5+
example : Result String Nat := sorry
6+
example : Nonsense String Nat := sorry
7+
example : MetaM String := sorry
8+
9+
set_option relaxedAutoImplicit false in
10+
example : MetaM String := sorry
11+
12+
set_option relaxedAutoImplicit false in
13+
example : Nonsense String := sorry
14+
15+
example (h₁ : α = β) (as : List α) (P : List β → Type) : P (h₁ ▸ as) := sorry
16+
example {α β h} (h₁ : α = β) (as : List α) (P : List β → Type) : P (h ▸ as) := sorry
17+
example (h₁ : α = β) (as : List α) (P : List β → Type) : P (h ▸ as) := sorry
18+
set_option relaxedAutoImplicit false in
19+
example (h₁ : α = β) (as : List α) (P : List β → Type) : P (hi ▸ as) := sorry
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
unknownCannotBeComplex.lean:3:0-3:7: warning: declaration uses 'sorry'
2+
unknownCannotBeComplex.lean:4:10-4:22: error: Function expected at
3+
Maybe
4+
but this term has type
5+
?m
6+
7+
Note: Expected a function because this term is being applied to the argument
8+
String
9+
10+
Hint: The identifier `Maybe` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be a function, and a function is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.
11+
unknownCannotBeComplex.lean:5:10-5:27: error: Function expected at
12+
Result
13+
but this term has type
14+
?m
15+
16+
Note: Expected a function because this term is being applied to the argument
17+
String
18+
19+
Hint: The identifier `Result` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be a function, and a function is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.
20+
unknownCannotBeComplex.lean:6:10-6:29: error: Function expected at
21+
Nonsense
22+
but this term has type
23+
?m
24+
25+
Note: Expected a function because this term is being applied to the argument
26+
String
27+
28+
Hint: The identifier `Nonsense` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be a function, and a function is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.
29+
unknownCannotBeComplex.lean:7:10-7:22: error: Function expected at
30+
MetaM
31+
but this term has type
32+
?m
33+
34+
Note: Expected a function because this term is being applied to the argument
35+
String
36+
37+
Hint: The identifier `MetaM` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be a function, and a function is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.
38+
unknownCannotBeComplex.lean:10:10-10:15: error(lean.unknownIdentifier): Unknown identifier `MetaM`
39+
40+
Note: It is not possible to treat `MetaM` as an implicitly bound variable here because it has multiple characters while the `relaxedAutoImplicit` option is set to `false`.
41+
unknownCannotBeComplex.lean:13:10-13:18: error(lean.unknownIdentifier): Unknown identifier `Nonsense`
42+
43+
Note: It is not possible to treat `Nonsense` as an implicitly bound variable here because it has multiple characters while the `relaxedAutoImplicit` option is set to `false`.
44+
unknownCannotBeComplex.lean:15:0-15:7: warning: declaration uses 'sorry'
45+
unknownCannotBeComplex.lean:16:68-16:74: error: invalid `▸` notation, argument
46+
h
47+
has type
48+
?m
49+
equality expected
50+
unknownCannotBeComplex.lean:17:61-17:67: error: invalid `▸` notation, argument
51+
h
52+
has type
53+
?m
54+
equality expected
55+
56+
Hint: The identifier `h` is unknown, and Lean's `autoImplicit` option causes an unknown identifier to be treated as an implicitly bound variable with an unknown type. However, the unknown type cannot be an equality, and an equality is what Lean expects here. This is often the result of a typo or a missing `import` or `open` statement.
57+
unknownCannotBeComplex.lean:19:60-19:62: error(lean.unknownIdentifier): Unknown identifier `hi`
58+
59+
Note: It is not possible to treat `hi` as an implicitly bound variable here because it has multiple characters while the `relaxedAutoImplicit` option is set to `false`.

0 commit comments

Comments
 (0)