Skip to content

Commit

Permalink
Suggest possible names in NotAMemberError
Browse files Browse the repository at this point in the history
Fixes scala/bug#10181

With this change, NotAMemberError checks for possible members under target with edit distance of 2 or 1.

```scala
scala> import scala.io.StdIn.{readline, readInt}
              ^
       error: value readline is not a member of object scala.io.StdIn
       did you mean readLine?

scala> import scala.io.stdin.{readLine => line}
                       ^
       error: object stdin is not a member of package io
       did you mean StdIn?

scala> import scala.sth
              ^
       error: object sth is not a member of package scala
       did you mean sys or math?
```
  • Loading branch information
eed3si9n committed Jun 12, 2018
1 parent cee723e commit 08b27c3
Show file tree
Hide file tree
Showing 19 changed files with 199 additions and 28 deletions.
48 changes: 39 additions & 9 deletions src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -339,22 +339,52 @@ trait ContextErrors {
issueNormalTypeError(tree, "ambiguous parent class qualifier")

//typedSelect
def NotAMemberError(sel: Tree, qual: Tree, name: Name) = {
def errMsg = {
def NotAMemberError(sel: Tree, qual: Tree, name: Name, cx: Context) = {
import util.{ EditDistance, StringUtil }
def errMsg: String = {
val owner = qual.tpe.typeSymbol
val target = qual.tpe.widen
def targetKindString = if (owner.isTypeParameterOrSkolem) "type parameter " else ""
def nameString = decodeWithKind(name, owner)
/* Illuminating some common situations and errors a bit further. */
def addendum = {
val companionSymbol: Symbol = {
if (name.isTermName && owner.isPackageClass)
target.member(name.toTypeName)
else NoSymbol
}
val companion = {
if (name.isTermName && owner.isPackageClass) {
target.member(name.toTypeName) match {
case NoSymbol => ""
case sym => "\nNote: %s exists, but it has no companion object.".format(sym)
}
if (companionSymbol == NoSymbol) ""
else s"\nnote: $companionSymbol exists, but it has no companion object."
}
// find out all the names available under target within 2 edit distances
lazy val alternatives: List[String] = {
val editThreshold = 2
val x = name.decode
if ((x.size < 2) || x.endsWith("=")) Nil
else {
target.members.iterator
.filter(sym => (sym.isTerm == name.isTermName) &&
!sym.isConstructor &&
!nme.isLocalName(sym.name) &&
cx.isAccessible(sym, target))
.map(_.name.decode)
.filter(n => (n.length > 2) &&
(math.abs(n.length - x.length) <= editThreshold) &&
(n != x) &&
!n.contains("$") &&
EditDistance.levenshtein(n, x) <= editThreshold)
.distinct.toList
}
else ""
}
val altStr: String = {
val maxSuggestions = 4
if (companionSymbol != NoSymbol) ""
else
alternatives match {
case Nil => ""
case xs => "\ndid you mean " + StringUtil.oxford(xs.sorted.take(maxSuggestions), "or") + "?"
}
}
val semicolon = (
if (linePrecedes(qual, sel))
Expand All @@ -366,7 +396,7 @@ trait ContextErrors {
if (ObjectClass.info.member(name).exists) notAnyRefMessage(target)
else ""
)
companion + notAnyRef + semicolon
companion + altStr + notAnyRef + semicolon
}
def targetStr = targetKindString + target.directObjectString
withAddendum(qual.pos)(
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/typechecker/Namers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ trait Namers extends MethodSynthesis {
if (isValid(from)) {
// for Java code importing Scala objects
if (!nme.isModuleName(from) || isValid(from.dropModule)) {
typer.TyperErrorGen.NotAMemberError(tree, expr, from)
typer.TyperErrorGen.NotAMemberError(tree, expr, from, context.outer)
}
}
// Setting the position at the import means that if there is
Expand Down
10 changes: 8 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4313,11 +4313,17 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (name == nme.ERROR || qual.tpe.widen.isErroneous)
NoSymbol
else lookupInOwner(qual.tpe.typeSymbol, name) orElse {
NotAMemberError(tree, qual, name)
NotAMemberError(tree, qual, name, startingIdentContext)
NoSymbol
}
)

def startingIdentContext = (
// ignore current variable scope in patterns to enforce linearity
if (mode.inNone(PATTERNmode | TYPEPATmode)) context
else context.outer
)

def typedAnnotated(atd: Annotated): Tree = {
val ann = atd.annot
val arg1 = typed(atd.arg, mode, pt)
Expand Down Expand Up @@ -4605,7 +4611,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (sym.isAbstractType || sym.hasAbstractFlag)
IsAbstractError(tree, sym)
else if (isPrimitiveValueClass(sym)) {
NotAMemberError(tpt, TypeTree(tp), nme.CONSTRUCTOR)
NotAMemberError(tpt, TypeTree(tp), nme.CONSTRUCTOR, startingIdentContext)
setError(tpt)
}
else if (!( tp == sym.typeOfThis // when there's no explicit self type -- with (#3612) or without self variable
Expand Down
52 changes: 52 additions & 0 deletions src/compiler/scala/tools/nsc/util/EditDistance.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package scala.tools.nsc
package util

import java.lang.Character.{ toLowerCase => lower }
import scala.collection.immutable

object EditDistance {

/**
* @author Paul Phillips
* Translated from the java version at
* http://www.merriampark.com/ld.htm
* which is declared to be public domain.
*/
def levenshtein(
s: String,
t: String,
insertCost: Int = 1,
deleteCost: Int = 1,
subCost: Int = 1,
matchCost: Int = 0,
caseCost: Int = 1,
transpositions: Boolean = false
): Int = {
val n = s.length
val m = t.length
if (n == 0) return m
if (m == 0) return n

val d = Array.ofDim[Int](n + 1, m + 1)
0 to n foreach (x => d(x)(0) = x)
0 to m foreach (x => d(0)(x) = x)

for (i <- 1 to n; s_i = s(i - 1); j <- 1 to m) {
val t_j = t(j - 1)
val cost = if (s_i == t_j) matchCost else if (lower(s_i) == lower(t_j)) caseCost else subCost

val c1 = d(i - 1)(j) + deleteCost
val c2 = d(i)(j - 1) + insertCost
val c3 = d(i - 1)(j - 1) + cost

d(i)(j) = c1 min c2 min c3

if (transpositions) {
if (i > 1 && j > 1 && s(i - 1) == t(j - 2) && s(i - 2) == t(j - 1))
d(i)(j) = d(i)(j) min (d(i - 2)(j - 2) + cost)
}
}

d(n)(m)
}
}
17 changes: 17 additions & 0 deletions src/compiler/scala/tools/nsc/util/StringUtil.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package scala.tools.nsc
package util

import scala.collection.immutable.Seq

trait StringUtil {
def oxford(vs: Seq[String], conj: String): String = {
vs match {
case Seq() => ""
case Seq(a) => a
case Seq(a, b) => s"$a $conj $b"
case xs => xs.dropRight(1).mkString(", ") + s", $conj " + xs.last
}
}
}

object StringUtil extends StringUtil
1 change: 1 addition & 0 deletions test/files/neg/macro-nontypeablebody.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Macros_Test_2.scala:2: error: value foo2 is not a member of object Impls
did you mean foo?
def foo(x: Any) = macro Impls.foo2
^
one error found
2 changes: 1 addition & 1 deletion test/files/neg/noMember1.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
noMember1.scala:1: error: object IterableOnceOps is not a member of package collection
Note: trait IterableOnceOps exists, but it has no companion object.
note: trait IterableOnceOps exists, but it has no companion object.
import scala.collection.IterableOnceOps._
^
one error found
2 changes: 1 addition & 1 deletion test/files/neg/noMember2.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
noMember2.scala:2: error: object IterableOnceOps is not a member of package collection
Note: trait IterableOnceOps exists, but it has no companion object.
note: trait IterableOnceOps exists, but it has no companion object.
val m = scala.collection.IterableOnceOps(1, 2, 3)
^
one error found
34 changes: 26 additions & 8 deletions test/files/neg/suggest-similar.check
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
suggest-similar.scala:8: error: not found: value flippitx
flippitx = 123
^
suggest-similar.scala:9: error: not found: value identiyt
suggest-similar.scala:10: error: value flippitx is not a member of object example.Weehawken
did you mean flippity?
Weehawken.flippitx = 123
^
suggest-similar.scala:11: error: not found: value identiyt
Nil map identiyt
^
suggest-similar.scala:10: error: not found: type Bingus
new Bingus
^
three errors found
suggest-similar.scala:12: error: type Eeehawken is not a member of package example
did you mean Weehawken?
new example.Eeehawken
^
suggest-similar.scala:16: error: value readline is not a member of object scala.io.StdIn
did you mean readLine?
import scala.io.StdIn.{readline, readInt}
^
suggest-similar.scala:20: error: object stdin is not a member of package io
did you mean StdIn?
import scala.io.stdin.{readLine => line}
^
suggest-similar.scala:37: error: value foo is not a member of object example.Hohokus
did you mean foo1, foo2, foo3, or foo4?
Hohokus.foo
^
suggest-similar.scala:41: error: value bar is not a member of example.Hohokus
did you mean bar2?
new Hohokus().bar // don't suggest bar1
^
7 errors found
41 changes: 36 additions & 5 deletions test/files/neg/suggest-similar.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,42 @@
class Dingus
object Dingus {
package example

class Weehawken
object Weehawken {
var flippity = 1
type Blippitx = Int
}
import Dingus._

class A {
flippitx = 123
Weehawken.flippitx = 123
Nil map identiyt
new Bingus
new example.Eeehawken
}

object B {
import scala.io.StdIn.{readline, readInt}
}

object C {
import scala.io.stdin.{readLine => line}
}

class Hohokus {
protected def bar1
protected[example] def bar2
}
object Hohokus {
def foo1 = 1
def foo2 = 2
def foo3 = 3
def foo4 = 4
def foo5 = 5
def foo6 = 6
}

object D {
Hohokus.foo
}

object E {
new Hohokus().bar // don't suggest bar1
}
2 changes: 1 addition & 1 deletion test/files/neg/t10888.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ t10888.scala:5: error: package scala.collection is not a value
val x = scala.collection // package scala.collection is not a value
^
t10888.scala:7: error: object App is not a member of package scala
Note: trait App exists, but it has no companion object.
note: trait App exists, but it has no companion object.
val z = scala.App // object App is not a member of package scala
^
four errors found
1 change: 1 addition & 0 deletions test/files/neg/t10935.check
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
t10935.scala:4: error: value += is not a member of Int
Expression does not convert to assignment because:
value lengt is not a member of String
did you mean length?
expansion: a.this.size = a.this.size.+(1.+("foo".<lengt: error>))
size += 1 + "foo".lengt
^
Expand Down
1 change: 1 addition & 0 deletions test/files/neg/t3346c.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
t3346c.scala:60: error: value bar is not a member of Either[Int,String]
did you mean map?
eii.bar
^
one error found
9 changes: 9 additions & 0 deletions test/files/neg/t3871b.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ t3871b.scala:77: error: method prot in class A cannot be accessed in E.this.A
a.prot // not allowed, prefix type `A` does not conform to `B`
^
t3871b.scala:79: error: value protT is not a member of E.this.B
did you mean prot or protE?
b.protT // not allowed
^
t3871b.scala:80: error: value protT is not a member of E.this.C
did you mean prot or protE?
c.protT // not allowed
^
t3871b.scala:81: error: value protT is not a member of E.this.A
did you mean protE?
a.protT // not allowed
^
t3871b.scala:91: error: method prot in class A cannot be accessed in E.this.A
Expand All @@ -23,12 +26,15 @@ t3871b.scala:91: error: method prot in class A cannot be accessed in E.this.A
a.prot // not allowed
^
t3871b.scala:93: error: value protT is not a member of E.this.B
did you mean prot or protE?
b.protT // not allowed
^
t3871b.scala:94: error: value protT is not a member of E.this.C
did you mean prot or protE?
c.protT // not allowed
^
t3871b.scala:95: error: value protT is not a member of E.this.A
did you mean protE?
a.protT // not allowed
^
t3871b.scala:102: error: method prot in class A cannot be accessed in E.this.B
Expand All @@ -50,12 +56,15 @@ t3871b.scala:104: error: method prot in class A cannot be accessed in E.this.A
a.prot // not allowed
^
t3871b.scala:109: error: value protT is not a member of E.this.B
did you mean protE?
b.protT // not allowed
^
t3871b.scala:110: error: value protT is not a member of E.this.C
did you mean protE?
c.protT // not allowed
^
t3871b.scala:111: error: value protT is not a member of E.this.A
did you mean protE?
a.protT // not allowed
^
t3871b.scala:120: error: method prot in class A cannot be accessed in Other.this.e.B
Expand Down
1 change: 1 addition & 0 deletions test/files/neg/t4271.check
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ t4271.scala:10: error: value ensuring is not a member of Int
5 ensuring true
^
t4271.scala:11: error: value -> is not a member of Int
did you mean >>>?
3 -> 5
^
three errors found
1 change: 1 addition & 0 deletions test/files/neg/t5801.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
t5801.scala:1: error: object sth is not a member of package scala
did you mean math or sys?
import scala.sth
^
t5801.scala:4: error: not found: value sth
Expand Down
1 change: 1 addition & 0 deletions test/files/run/macro-typecheck-implicitsdisabled.check
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
scala.Predef.ArrowAssoc[Int](1).->[Int](2)
scala.reflect.macros.TypecheckException: value -> is not a member of Int
did you mean >>>?
1 change: 1 addition & 0 deletions test/files/run/repl-no-imports-no-predef.check
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ res6: (Int, Int) = (1,2)
scala> 1 -> 2
^
error: value -> is not a member of Int
did you mean >>>?

scala> 1 → 2
^
Expand Down
1 change: 1 addition & 0 deletions test/files/run/toolbox_typecheck_implicitsdisabled.check
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
scala.Predef.ArrowAssoc[Int](1).->[Int](2)
}
scala.tools.reflect.ToolBoxError: reflective typecheck has failed: value -> is not a member of Int
did you mean >>>?

0 comments on commit 08b27c3

Please sign in to comment.