aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Mulder <felix.mulder@gmail.com>2016-10-08 00:22:08 +0200
committerFelix Mulder <felix.mulder@gmail.com>2016-10-10 15:46:50 +0200
commitd490f7d9ebe072b5ade305265100c7bc433885a9 (patch)
tree08cb576bed46272011f5ff4bf8199d000872f926
parentb9e03b8d9246134afeacc925651139c079275479 (diff)
downloaddotty-d490f7d9ebe072b5ade305265100c7bc433885a9.tar.gz
dotty-d490f7d9ebe072b5ade305265100c7bc433885a9.tar.bz2
dotty-d490f7d9ebe072b5ade305265100c7bc433885a9.zip
Add Levenshtein distance for member values and types
-rw-r--r--src/dotty/tools/dotc/reporting/diagnostic/messages.scala71
-rw-r--r--src/dotty/tools/dotc/typer/TypeAssigner.scala4
-rw-r--r--tests/repl/errmsgs.check5
3 files changed, 76 insertions, 4 deletions
diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
index cc062ff92..9cfac4801 100644
--- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
+++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
@@ -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 = ""
}
diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala
index 0c55d977e..262d3f731 100644
--- a/src/dotty/tools/dotc/typer/TypeAssigner.scala
+++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala
@@ -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._
@@ -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
diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check
index 066d98d0f..2bcb40eb0 100644
--- a/tests/repl/errmsgs.check
+++ b/tests/repl/errmsgs.check
@@ -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