Skip to content

Commit 2c97437

Browse files
committed
Fix Levenshtein distance calculation
The previous algorithm was simply wrong, which went undiscovered because there were no tests to its correctness. Morale: Just because some code is purely functional does not mean it is clear or correct.
1 parent 1752609 commit 2c97437

File tree

3 files changed

+111
-10
lines changed

3 files changed

+111
-10
lines changed

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -321,23 +321,26 @@ object messages {
321321
// that are of the same type/term kind as the missing member.
322322
def candidates: Set[String] =
323323
for
324-
bc <- site.baseClasses.toSet
324+
bc <- site.widen.baseClasses.toSet
325325
sym <- bc.info.decls.filter(sym =>
326326
sym.isType == name.isTypeName
327327
&& !sym.isConstructor
328328
&& !sym.flagsUNSAFE.isOneOf(Synthetic | Private))
329329
yield sym.name.show
330330

331331
// Calculate Levenshtein distance
332-
def distance(n1: Iterable[?], n2: Iterable[?]) =
333-
n1.foldLeft(List.range(0, n2.size)) { (prev, x) =>
334-
(prev zip prev.tail zip n2).scanLeft(prev.head + 1) {
335-
case (h, ((d, v), y)) => math.min(
336-
math.min(h + 1, v + 1),
337-
if (x == y) d else d + 1
338-
)
339-
}
340-
}.last
332+
def distance(s1: String, s2: String): Int =
333+
val dist = Array.ofDim[Int](s2.length + 1, s1.length + 1)
334+
for
335+
j <- 0 to s2.length
336+
i <- 0 to s1.length
337+
do
338+
dist(j)(i) =
339+
if j == 0 then i
340+
else if i == 0 then j
341+
else if s2(j - 1) == s1(i - 1) then dist(j - 1)(i - 1)
342+
else (dist(j - 1)(i) min dist(j)(i - 1) min dist(j - 1)(i - 1)) + 1
343+
dist(s2.length)(s1.length)
341344

342345
// A list of possible candidate strings with their Levenstein distances
343346
// to the name of the missing member

tests/neg/name-hints.check

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:6:15 ------------------------------------------------------
2+
6 | val x1 = Int.maxvalue // error
3+
| ^^^^^^^^^^^^
4+
| value maxvalue is not a member of object Int - did you mean Int.MaxValue?
5+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:7:15 ------------------------------------------------------
6+
7 | val x2 = Int.MxValue // error
7+
| ^^^^^^^^^^^
8+
| value MxValue is not a member of object Int - did you mean Int.MaxValue?
9+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:8:15 ------------------------------------------------------
10+
8 | val x3 = Int.MaxxValue // error
11+
| ^^^^^^^^^^^^^
12+
| value MaxxValue is not a member of object Int - did you mean Int.MaxValue?
13+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:10:13 -----------------------------------------------------
14+
10 | val d1 = O.abcd // error
15+
| ^^^^^^
16+
| value abcd is not a member of object O - did you mean O.abcde?
17+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:11:13 -----------------------------------------------------
18+
11 | val d2 = O.abc // error
19+
| ^^^^^
20+
| value abc is not a member of object O - did you mean O.abcde?
21+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:12:13 -----------------------------------------------------
22+
12 | val d3 = O.ab // error, no hint since distance = 3 > 2 = length
23+
| ^^^^
24+
| value ab is not a member of object O
25+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:13:13 -----------------------------------------------------
26+
13 | val s1 = O.Abcde // error
27+
| ^^^^^^^
28+
| value Abcde is not a member of object O - did you mean O.abcde?
29+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:14:13 -----------------------------------------------------
30+
14 | val s3 = O.AbCde // error
31+
| ^^^^^^^
32+
| value AbCde is not a member of object O - did you mean O.abcde?
33+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:15:13 -----------------------------------------------------
34+
15 | val s3 = O.AbCdE // error
35+
| ^^^^^^^
36+
| value AbCdE is not a member of object O - did you mean O.abcde?
37+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:16:13 -----------------------------------------------------
38+
16 | val s3 = O.AbCDE // error, no hint
39+
| ^^^^^^^
40+
| value AbCDE is not a member of object O
41+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:17:13 -----------------------------------------------------
42+
17 | val a1 = O.abcde0 // error
43+
| ^^^^^^^^
44+
| value abcde0 is not a member of object O - did you mean O.abcde?
45+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:18:13 -----------------------------------------------------
46+
18 | val a2 = O.abcde00 // error
47+
| ^^^^^^^^^
48+
| value abcde00 is not a member of object O - did you mean O.abcde?
49+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:19:13 -----------------------------------------------------
50+
19 | val a3 = O.abcde000 // error
51+
| ^^^^^^^^^^
52+
| value abcde000 is not a member of object O - did you mean O.abcde?
53+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:20:13 -----------------------------------------------------
54+
20 | val a4 = O.abcde0000 // error, no hint
55+
| ^^^^^^^^^^^
56+
| value abcde0000 is not a member of object O
57+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:22:13 -----------------------------------------------------
58+
22 | val y1 = O.x // error, no hint
59+
| ^^^
60+
| value x is not a member of object O
61+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:23:13 -----------------------------------------------------
62+
23 | val y2 = O.xY // error
63+
| ^^^^
64+
| value xY is not a member of object O - did you mean O.xy?
65+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:24:13 -----------------------------------------------------
66+
24 | val y3 = O.xyz // error
67+
| ^^^^^
68+
| value xyz is not a member of object O - did you mean O.xy?
69+
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:25:13 -----------------------------------------------------
70+
25 | val y2 = O.XY // error, no hint
71+
| ^^^^
72+
| value XY is not a member of object O

tests/neg/name-hints.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
object O with
2+
val abcde: Int = 0
3+
val xy: Int = 1
4+
5+
object Test with
6+
val x1 = Int.maxvalue // error
7+
val x2 = Int.MxValue // error
8+
val x3 = Int.MaxxValue // error
9+
10+
val d1 = O.abcd // error
11+
val d2 = O.abc // error
12+
val d3 = O.ab // error, no hint since distance = 3 > 2 = length
13+
val s1 = O.Abcde // error
14+
val s3 = O.AbCde // error
15+
val s3 = O.AbCdE // error
16+
val s3 = O.AbCDE // error, no hint
17+
val a1 = O.abcde0 // error
18+
val a2 = O.abcde00 // error
19+
val a3 = O.abcde000 // error
20+
val a4 = O.abcde0000 // error, no hint
21+
22+
val y1 = O.x // error, no hint
23+
val y2 = O.xY // error
24+
val y3 = O.xyz // error
25+
val y2 = O.XY // error, no hint
26+

0 commit comments

Comments
 (0)