Skip to content

Fix "did you mean" hinting #7895

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 34 additions & 44 deletions compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -314,56 +314,46 @@ object messages {

val msg: String = {
import core.Flags._
val maxDist = 3
val decls = site.decls.toList
.filter(_.isType == name.isTypeName)
.flatMap { sym =>
if (sym.flagsUNSAFE.isOneOf(Synthetic | PrivateLocal) || sym.isConstructor) Nil
else List((sym.name.show, sym))
}
val maxDist = 3 // maximal number of differences to be considered for a hint
val missing = name.show

// The names of all non-synthetic, non-private members of `site`
// that are of the same type/term kind as the missing member.
def candidates: Set[String] =
for
bc <- site.widen.baseClasses.toSet
sym <- bc.info.decls.filter(sym =>
sym.isType == name.isTypeName
&& !sym.isConstructor
&& !sym.flagsUNSAFE.isOneOf(Synthetic | Private))
yield sym.name.show

// Calculate Levenshtein distance
def distance(n1: Iterable[?], n2: Iterable[?]) =
n1.foldLeft(List.range(0, n2.size)) { (prev, x) =>
(prev zip prev.tail zip n2).scanLeft(prev.head + 1) {
case (h, ((d, v), y)) => math.min(
math.min(h + 1, v + 1),
if (x == y) d else d + 1
)
}
}.last

// Count number of wrong characters
def incorrectChars(x: (String, Int, Symbol)): (String, Symbol, Int) = {
val (currName, _, sym) = x
val matching = name.show.zip(currName).foldLeft(0) {
case (acc, (x,y)) => if (x != y) acc + 1 else acc
}
(currName, sym, matching)
}

// Get closest match in `site`
def closest: List[String] =
decls
.map { (n, sym) => (n, distance(n, name.show), sym) }
.collect {
case (n, dist, sym)
if dist <= maxDist && dist < (name.toString.length min n.length) =>
(n, dist, sym)
}
.groupBy(_._2).toList
.sortBy(_._1)
.headOption.map(_._2).getOrElse(Nil)
.map(incorrectChars).toList
.sortBy(_._3)
.map(_._1)
// [Martin] Note: I have no idea what this does. This shows the
// pitfalls of not naming things, functional or not.
def distance(s1: String, s2: String): Int =
val dist = Array.ofDim[Int](s2.length + 1, s1.length + 1)
for
j <- 0 to s2.length
i <- 0 to s1.length
do
dist(j)(i) =
if j == 0 then i
else if i == 0 then j
else if s2(j - 1) == s1(i - 1) then dist(j - 1)(i - 1)
else (dist(j - 1)(i) min dist(j)(i - 1) min dist(j - 1)(i - 1)) + 1
dist(s2.length)(s1.length)

// A list of possible candidate strings with their Levenstein distances
// to the name of the missing member
def closest: List[(Int, String)] = candidates
.toList
.map(n => (distance(n.show, missing), n))
.filter((d, n) => d <= maxDist && d < missing.length && d < n.length)
.sorted // sort by distance first, alphabetically second

val finalAddendum =
if addendum.nonEmpty then addendum
else closest match {
case n :: _ =>
case (d, n) :: _ =>
val siteName = site match
case site: NamedType => site.name.show
case site => i"$site"
Expand Down
72 changes: 72 additions & 0 deletions tests/neg/name-hints.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:6:15 ------------------------------------------------------
6 | val x1 = Int.maxvalue // error
| ^^^^^^^^^^^^
| value maxvalue is not a member of object Int - did you mean Int.MaxValue?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:7:15 ------------------------------------------------------
7 | val x2 = Int.MxValue // error
| ^^^^^^^^^^^
| value MxValue is not a member of object Int - did you mean Int.MaxValue?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:8:15 ------------------------------------------------------
8 | val x3 = Int.MaxxValue // error
| ^^^^^^^^^^^^^
| value MaxxValue is not a member of object Int - did you mean Int.MaxValue?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:10:13 -----------------------------------------------------
10 | val d1 = O.abcd // error
| ^^^^^^
| value abcd is not a member of object O - did you mean O.abcde?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:11:13 -----------------------------------------------------
11 | val d2 = O.abc // error
| ^^^^^
| value abc is not a member of object O - did you mean O.abcde?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:12:13 -----------------------------------------------------
12 | val d3 = O.ab // error, no hint since distance = 3 > 2 = length
| ^^^^
| value ab is not a member of object O
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:13:13 -----------------------------------------------------
13 | val s1 = O.Abcde // error
| ^^^^^^^
| value Abcde is not a member of object O - did you mean O.abcde?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:14:13 -----------------------------------------------------
14 | val s3 = O.AbCde // error
| ^^^^^^^
| value AbCde is not a member of object O - did you mean O.abcde?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:15:13 -----------------------------------------------------
15 | val s3 = O.AbCdE // error
| ^^^^^^^
| value AbCdE is not a member of object O - did you mean O.abcde?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:16:13 -----------------------------------------------------
16 | val s3 = O.AbCDE // error, no hint
| ^^^^^^^
| value AbCDE is not a member of object O
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:17:13 -----------------------------------------------------
17 | val a1 = O.abcde0 // error
| ^^^^^^^^
| value abcde0 is not a member of object O - did you mean O.abcde?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:18:13 -----------------------------------------------------
18 | val a2 = O.abcde00 // error
| ^^^^^^^^^
| value abcde00 is not a member of object O - did you mean O.abcde?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:19:13 -----------------------------------------------------
19 | val a3 = O.abcde000 // error
| ^^^^^^^^^^
| value abcde000 is not a member of object O - did you mean O.abcde?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:20:13 -----------------------------------------------------
20 | val a4 = O.abcde0000 // error, no hint
| ^^^^^^^^^^^
| value abcde0000 is not a member of object O
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:22:13 -----------------------------------------------------
22 | val y1 = O.x // error, no hint
| ^^^
| value x is not a member of object O
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:23:13 -----------------------------------------------------
23 | val y2 = O.xY // error
| ^^^^
| value xY is not a member of object O - did you mean O.xy?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:24:13 -----------------------------------------------------
24 | val y3 = O.xyz // error
| ^^^^^
| value xyz is not a member of object O - did you mean O.xy?
-- [E008] Member Not Found Error: tests/neg/name-hints.scala:25:13 -----------------------------------------------------
25 | val y2 = O.XY // error, no hint
| ^^^^
| value XY is not a member of object O
26 changes: 26 additions & 0 deletions tests/neg/name-hints.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
object O with
val abcde: Int = 0
val xy: Int = 1

object Test with
val x1 = Int.maxvalue // error
val x2 = Int.MxValue // error
val x3 = Int.MaxxValue // error

val d1 = O.abcd // error
val d2 = O.abc // error
val d3 = O.ab // error, no hint since distance = 3 > 2 = length
val s1 = O.Abcde // error
val s3 = O.AbCde // error
val s3 = O.AbCdE // error
val s3 = O.AbCDE // error, no hint
val a1 = O.abcde0 // error
val a2 = O.abcde00 // error
val a3 = O.abcde000 // error
val a4 = O.abcde0000 // error, no hint

val y1 = O.x // error, no hint
val y2 = O.xY // error
val y3 = O.xyz // error
val y2 = O.XY // error, no hint