Skip to content

Commit 72ddc47

Browse files
authored
fix: modify @[suggest_for] to work with the Prelude (#11529)
This PR fixes a syntax-pattern-matching issue from #11367 that prevented the addition of suggestions in Init prior to Lean.Parser being introduced, which was a significant shortcoming. It preserves the ability to have multiple suggestions for one annotation later in the process. Additionally, tweaks a (not-yet-user-visible) error message and modifies the attribute declaration to store a wrongIdentifier -> correctIdentifier mapping instead of a correctIdentifier -> wrongIdentifier mapping.
1 parent c5e0417 commit 72ddc47

File tree

3 files changed

+69
-47
lines changed

3 files changed

+69
-47
lines changed

src/Lean/Elab/App.lean

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,15 +1420,26 @@ where
14201420
-- Possible alternatives provided with `@[suggest_for]` annotations
14211421
let suggestions := (← Lean.getSuggestions fullName).filter (·.getPrefix = fullName.getPrefix)
14221422
let suggestForHint ←
1423-
if suggestions.size = 0 then
1423+
if h : suggestions.size = 0 then
14241424
pure .nil
1425+
else if suggestions.size = 1 then
1426+
MessageData.hint (ref? := ref)
1427+
m!"Perhaps you meant `{.ofConstName suggestions[0]}` in place of `{fullName}`:"
1428+
(suggestions.map fun suggestion => {
1429+
preInfo? := .some s!"{e}.",
1430+
suggestion := suggestion.getString!,
1431+
toCodeActionTitle? := .some (s!"Suggested replacement: {e}.{·}"),
1432+
diffGranularity := .all,
1433+
})
14251434
else
1426-
m!"Perhaps you meant one of these in place of `{fullName}`:".hint (suggestions.map fun suggestion => {
1427-
suggestion := suggestion.getString!,
1428-
toCodeActionTitle? := .some (s!"Suggested replacement: {e}.{·}"),
1429-
diffGranularity := .all,
1430-
messageData? := .some m!"`{.ofConstName suggestion}`: {e}.{suggestion.getString!}",
1431-
}) ref
1435+
MessageData.hint (ref? := ref)
1436+
m!"Perhaps you meant one of these in place of `{fullName}`:"
1437+
(suggestions.map fun suggestion => {
1438+
suggestion := suggestion.getString!,
1439+
toCodeActionTitle? := .some (s!"Suggested replacement: {e}.{·}"),
1440+
diffGranularity := .all,
1441+
messageData? := .some m!"`{.ofConstName suggestion}`: {e}.{suggestion.getString!}",
1442+
})
14321443

14331444
-- By using `mkUnknownIdentifierMessage`, the tag `Lean.unknownIdentifierMessageTag` is
14341445
-- incorporated within the message, as required for the "import unknown identifier" code action.

src/Lean/IdentifierSuggestion.lean

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,46 @@ namespace Lean
1717

1818
set_option doc.verso true
1919

20-
builtin_initialize identifierSuggestionForAttr : ParametricAttribute (Name × Array Name) ←
21-
registerParametricAttribute {
20+
21+
builtin_initialize identifierSuggestionForAttr : PersistentEnvExtension
22+
(Name × Array Name) /- *Store* mappings from incorrect names to corrections (identifiers that exist) -/
23+
(Name × Array Name) /- *Add* mappings from existing functions to possible incorrect namings -/
24+
(NameMap NameSet) /- *Use* mapping from incorrect names to corrections (identifiers that exist) -/
25+
let ext ← registerPersistentEnvExtension {
26+
mkInitial := pure {},
27+
addImportedFn := fun modules => pure <|
28+
modules.foldl (init := {}) fun accum entries =>
29+
entries.foldl (init := accum) fun accum (altName, trueNames) =>
30+
accum.alter altName (fun old => trueNames.foldl (β := NameSet) (init := old.getD ∅) fun accum trueName =>
31+
accum.insert trueName),
32+
addEntryFn := fun table (trueName, altNames) =>
33+
altNames.foldl (init := table) fun accum alt =>
34+
accum.alter alt (·.getD {} |>.insert trueName)
35+
exportEntriesFn table := table.toArray.map fun (altName, trueNames) =>
36+
(altName, trueNames.toArray)
37+
}
38+
39+
registerBuiltinAttribute {
2240
name := `suggest_for,
2341
descr := "suggest other (incorrect, not-existing) identifiers that someone might use when they actually want this definition",
24-
getParam := fun trueDeclName stx => do
25-
let `(attr| suggest_for $altNames:ident*) := stx
26-
| throwError "Invalid `[suggest_for]` attribute syntax"
27-
let ns := trueDeclName.getPrefix
28-
return (trueDeclName, altNames.map (·.getId))
29-
}
42+
add (decl : Name) (stx : Syntax) (kind : AttributeKind) := do
43+
unless kind == AttributeKind.global do throwAttrMustBeGlobal `suggest_for kind
44+
let altSyntaxIds : Array Syntax ← match stx with
45+
| -- Attributes parsed _after_ the suggest_for notation is added
46+
.node _ ``suggest_for #[.atom _ "suggest_for", .node _ `null ids] => pure ids
47+
| -- Attributes parsed _before the suggest_for notation is added
48+
.node _ ``Parser.Attr.simple #[.ident _ _ `suggest_for [], .node _ `null #[id]] => pure #[id]
49+
| _ => throwError "Invalid `[suggest_for]` attribute syntax {repr stx}"
50+
modifyEnv (ext.addEntry · (decl, altSyntaxIds.map (·.getId)))
51+
}
52+
53+
return ext
3054

3155
public def getSuggestions [Monad m] [MonadEnv m] (fullName : Name) : m (Array Name) := do
32-
let mut possibleReplacements := #[]
33-
let (_, allSuggestions) := identifierSuggestionForAttr.ext.getState (← getEnv)
34-
for (_, trueName, suggestions) in allSuggestions do
35-
for suggestion in suggestions do
36-
if fullName = suggestion then
37-
possibleReplacements := possibleReplacements.push trueName
38-
return possibleReplacements.qsort (lt := lt)
39-
where
40-
-- Ensure the result of getSuggestions is stable (if arbitrary)
41-
lt : Name -> Name -> Bool
42-
| .anonymous, _ => false
43-
| .str _ _, .anonymous => true
44-
| .num _ _, .anonymous => true
45-
| .str _ _, .num _ _ => true
46-
| .num _ _, .str _ _ => false
47-
| .num a n, .num b m => n < m || n == m && lt a b
48-
| .str a s1, .str b s2 => s1 < s2 || s1 == s2 && lt a b
56+
return identifierSuggestionForAttr.getState (← getEnv)
57+
|>.find? fullName
58+
|>.getD {}
59+
|>.toArray
4960

5061
/--
5162
Throw an unknown constant error message, potentially suggesting alternatives using

tests/lean/run/identifierSuggestions.lean

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ error: Invalid field `test0`: The environment does not contain `String.test0`, s
2222
"abc"
2323
of type `String`
2424
25-
Hint: Perhaps you meant one of these in place of `String.test0`:
26-
[apply] `String.foo`: "abc".foo
25+
Hint: Perhaps you meant `String.foo` in place of `String.test0`:
26+
"abc".t̵e̵s̵t̵0̵f̲o̲o̲
2727
-/
2828
#guard_msgs in
2929
#check "abc".test0
@@ -44,8 +44,8 @@ error: Invalid field `test1`: The environment does not contain `String.test1`, s
4444
of type `String`
4545
4646
Hint: Perhaps you meant one of these in place of `String.test1`:
47-
[apply] `String.baz`: "abc".baz
4847
[apply] `String.foo`: "abc".foo
48+
[apply] `String.baz`: "abc".baz
4949
-/
5050
#guard_msgs in
5151
#check "abc".test1
@@ -54,8 +54,8 @@ Hint: Perhaps you meant one of these in place of `String.test1`:
5454
error: Unknown constant `String.test1`
5555
5656
Hint: Perhaps you meant one of these in place of `String.test1`:
57-
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵1̵S̲t̲r̲i̲n̲g̲.̲b̲a̲z̲
5857
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵1̵S̲t̲r̲i̲n̲g̲.̲f̲o̲o̲
58+
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵1̵S̲t̲r̲i̲n̲g̲.̲b̲a̲z̲
5959
-/
6060
#guard_msgs in
6161
#check String.test1
@@ -67,9 +67,9 @@ error: Invalid field `test2`: The environment does not contain `String.test2`, s
6767
of type `String`
6868
6969
Hint: Perhaps you meant one of these in place of `String.test2`:
70-
[apply] `String.bar`: "abc".bar
71-
[apply] `String.baz`: "abc".baz
7270
[apply] `String.foo`: "abc".foo
71+
[apply] `String.baz`: "abc".baz
72+
[apply] `String.bar`: "abc".bar
7373
-/
7474
#guard_msgs in
7575
#check "abc".test2
@@ -79,11 +79,11 @@ Hint: Perhaps you meant one of these in place of `String.test2`:
7979
error: Unknown constant `String.test2`
8080
8181
Hint: Perhaps you meant one of these in place of `String.test2`:
82-
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵2̵S̲t̲r̲i̲n̲g̲.̲b̲a̲r̲
83-
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵2̵S̲t̲r̲i̲n̲g̲.̲b̲a̲z̲
84-
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵2̵S̲t̲r̲i̲n̲g̲.̲f̲o̲o̲
8582
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵2̵o̲t̲h̲e̲r̲B̲a̲z̲
83+
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵2̵S̲t̲r̲i̲n̲g̲.̲f̲o̲o̲
8684
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵2̵o̲t̲h̲e̲r̲F̲o̲o̲
85+
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵2̵S̲t̲r̲i̲n̲g̲.̲b̲a̲z̲
86+
• S̵t̵r̵i̵n̵g̵.̵t̵e̵s̵t̵2̵S̲t̲r̲i̲n̲g̲.̲b̲a̲r̲
8787
-/
8888
#guard_msgs in
8989
#check String.test2
@@ -118,8 +118,8 @@ error: Invalid field `toNum`: The environment does not contain `Foo.Bar.toNum`,
118118
Foo.Bar.three
119119
of type `Foo.Bar`
120120
121-
Hint: Perhaps you meant one of these in place of `Foo.Bar.toNum`:
122-
[apply] `Foo.Bar.toNat`: Foo.Bar.three.toNat
121+
Hint: Perhaps you meant `Foo.Bar.toNat` in place of `Foo.Bar.toNum`:
122+
Foo.Bar.three.t̵o̵N̵u̵m̵t̲o̲N̲a̲t̲
123123
-/
124124
#guard_msgs in
125125
#eval Foo.Bar.three.toNum
@@ -129,8 +129,8 @@ error: Invalid field `toStr`: The environment does not contain `Foo.Bar.toStr`,
129129
Foo.Bar.two
130130
of type `Foo.Bar`
131131
132-
Hint: Perhaps you meant one of these in place of `Foo.Bar.toStr`:
133-
[apply] `Foo.Bar.toString`: Foo.Bar.two.toString
132+
Hint: Perhaps you meant `Foo.Bar.toString` in place of `Foo.Bar.toStr`:
133+
Foo.Bar.two.t̵o̵S̵t̵r̵t̲o̲S̲t̲r̲i̲n̲g̲
134134
-/
135135
#guard_msgs in
136136
#eval Foo.Bar.two.toStr
@@ -273,8 +273,8 @@ error: Invalid field `not`: The environment does not contain `MyBool.not`, so it
273273
MyBool.tt
274274
of type `MyBool`
275275
276-
Hint: Perhaps you meant one of these in place of `MyBool.not`:
277-
[apply] `MyBool.swap`: MyBool.tt.swap
276+
Hint: Perhaps you meant `MyBool.swap` in place of `MyBool.not`:
277+
MyBool.tt.n̵o̵t̵s̲w̲a̲p̲
278278
-/
279279
#guard_msgs in
280280
example := MyBool.tt.not

0 commit comments

Comments
 (0)