Skip to content

Commit

Permalink
Add Levenshtein distance for member values and types
Browse files Browse the repository at this point in the history
  • Loading branch information
felixmulder committed Oct 10, 2016
1 parent b9e03b8 commit d490f7d
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 4 deletions.
71 changes: 68 additions & 3 deletions src/dotty/tools/dotc/reporting/diagnostic/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,78 @@ object messages {
case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "", implicitFailure: String = "")(implicit ctx: Context)
extends Message(7) {
val kind = "Type Mismatch"
private val (where, printCtx) = Formatting.disambiguateTypes(found, expected)
private val (fnd, exp) = Formatting.typeDiff(found, expected)(printCtx)
val msg =
val msg = {
val (where, printCtx) = Formatting.disambiguateTypes(found, expected)
val (fnd, exp) = Formatting.typeDiff(found, expected)(printCtx)
s"""|found: $fnd
|required: $exp
|
|$where""".stripMargin + whyNoMatch + implicitFailure
}

val explanation = ""
}

case class NotAMember(site: Type, name: Name, selected: String)(implicit ctx: Context)
extends Message(8) {
val kind = "Member Not Found"

val msg = {
import core.Flags._
val maxDist = 3
val decls = site.decls.flatMap { sym =>
if (sym.is(Synthetic | PrivateOrLocal) || sym.isConstructor) Nil
else List((sym.name.show, sym))
}

// 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`
val closest =
decls
.map { case (n, sym) => (n, distance(n, name.show), sym) }
.collect { case (n, dist, sym) if dist <= maxDist => (n, dist, sym) }
.groupBy(_._2).toList
.sortBy(_._1)
.headOption.map(_._2).getOrElse(Nil)
.map(incorrectChars).toList
.sortBy(_._3)
.take(1).map { case (n, sym, _) => (n, sym) }

val siteName = site match {
case site: NamedType => site.name.show
case site => i"$site"
}

val closeMember = closest match {
case (n, sym) :: Nil => hl""" - did you mean `${s"$siteName.$n"}`?"""
case Nil => ""
case _ => assert(
false,
"Could not single out one distinct member to match on input with"
)
}

ex"$selected `$name` is not a member of $site$closeMember"
}

val explanation = ""
}
Expand Down
4 changes: 3 additions & 1 deletion src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import config.Printers.typr
import ast.Trees._
import NameOps._
import collection.mutable
import reporting.diagnostic.Message
import reporting.diagnostic.messages._

trait TypeAssigner {
import tpd._
Expand Down Expand Up @@ -220,7 +222,7 @@ trait TypeAssigner {
else ""
ctx.error(
if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor"
else ex"$kind $name is not a member of $site$addendum",
else NotAMember(site, name, kind),
pos)
}
ErrorType
Expand Down
5 changes: 5 additions & 0 deletions tests/repl/errmsgs.check
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,9 @@ scala> abstract class C {
|
| where: T is a type in the initalizer of value s which is an alias of String
| T' is a type in method f which is an alias of Int
scala> class Foo() { def bar: Int = 1 }; val foo = new Foo(); foo.barr
-- [E008] Member Not Found Error: <console> ----------------------------------------------------------------------------
4 |class Foo() { def bar: Int = 1 }; val foo = new Foo(); foo.barr
| ^^^^^^^^
| value `barr` is not a member of Foo(foo) - did you mean `foo.bar`?
scala> :quit

0 comments on commit d490f7d

Please sign in to comment.