diff options
author | Felix Mulder <felix.mulder@gmail.com> | 2016-10-08 00:22:08 +0200 |
---|---|---|
committer | Felix Mulder <felix.mulder@gmail.com> | 2016-10-10 15:46:50 +0200 |
commit | d490f7d9ebe072b5ade305265100c7bc433885a9 (patch) | |
tree | 08cb576bed46272011f5ff4bf8199d000872f926 | |
parent | b9e03b8d9246134afeacc925651139c079275479 (diff) | |
download | dotty-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.scala | 71 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/TypeAssigner.scala | 4 | ||||
-rw-r--r-- | tests/repl/errmsgs.check | 5 |
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 |