Skip to content

Fix #1907: Improve error message #1921

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 10 commits into from
Feb 8, 2017
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
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ trait ConstraintHandling {
if (Config.failOnInstantiationToNothing) assert(false, msg)
else ctx.log(msg)
}
constr.println(i"adding $description")
constr.println(i"adding $description in ${ctx.typerState.hashesStr}")
val lower = constraint.lower(param)
val res =
addOneBound(param, bound, isUpper = true) &&
lower.forall(addOneBound(_, bound, isUpper = true))
constr.println(i"added $description = $res")
constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}")
res
}

Expand All @@ -95,7 +95,7 @@ trait ConstraintHandling {
val res =
addOneBound(param, bound, isUpper = false) &&
upper.forall(addOneBound(_, bound, isUpper = false))
constr.println(i"added $description = $res")
constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}")
res
}

Expand All @@ -108,12 +108,12 @@ trait ConstraintHandling {
val up2 = p2 :: constraint.exclusiveUpper(p2, p1)
val lo1 = constraint.nonParamBounds(p1).lo
val hi2 = constraint.nonParamBounds(p2).hi
constr.println(i"adding $description down1 = $down1, up2 = $up2")
constr.println(i"adding $description down1 = $down1, up2 = $up2 ${ctx.typerState.hashesStr}")
constraint = constraint.addLess(p1, p2)
down1.forall(addOneBound(_, hi2, isUpper = true)) &&
up2.forall(addOneBound(_, lo1, isUpper = false))
}
constr.println(i"added $description = $res")
constr.println(i"added $description = $res ${ctx.typerState.hashesStr}")
res
}

Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ class TyperState(r: Reporter) extends DotClass with Showable {
def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = unsupported("tryWithFallBack")

override def toText(printer: Printer): Text = "ImmutableTyperState"

/** A string showing the hashes of all nested mutable typerstates */
def hashesStr: String = ""
}

class MutableTyperState(previous: TyperState, r: Reporter, override val isCommittable: Boolean)
Expand Down Expand Up @@ -207,4 +210,7 @@ extends TyperState(r) {
}

override def toText(printer: Printer): Text = constraint.toText(printer)

override def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr

}
8 changes: 2 additions & 6 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1765,12 +1765,8 @@ object Parsers {
TypeTree() // XX-METHOD-INFER
} else {
accept(COLON)
if (in.token == ARROW) {
if (owner.isTypeName && !(mods is Local))
syntaxError(s"${if (mods is Mutable) "`var'" else "`val'"} parameters may not be call-by-name")
else if (imods.hasFlags)
syntaxError("implicit parameters may not be call-by-name")
}
if (in.token == ARROW && owner.isTypeName && !(mods is Local))
syntaxError(s"${if (mods is Mutable) "`var'" else "`val'"} parameters may not be call-by-name")
paramType()
}
val default =
Expand Down
18 changes: 18 additions & 0 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._, De
import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation
import StdNames.{nme, tpnme}
import ast.Trees._, ast._
import typer.Implicits._
import config.Config
import java.lang.Integer.toOctalString
import config.Config.summarizeDepth
Expand Down Expand Up @@ -484,6 +485,23 @@ class PlainPrinter(_ctx: Context) extends Printer {
}
}.close // todo: override in refined printer

def toText(result: SearchResult): Text = result match {
case result: SearchSuccess =>
"SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree)
case result: NonMatchingImplicit =>
"NoImplicitMatches"
case result: DivergingImplicit =>
"Diverging Implicit"
case result: ShadowedImplicit =>
"Shadowed Implicit"
case result: FailedImplicit =>
"Failed Implicit"
case result: AmbiguousImplicits =>
"Ambiguous Implicit: " ~ toText(result.alt1) ~ " and " ~ toText(result.alt2)
case _ =>
"?Unknown Implicit Result?"
}

private var maxSummarized = Int.MaxValue

def summarized[T](depth: Int)(op: => T): T = {
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/Printer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import core._
import Texts._, ast.Trees._
import Types.Type, Symbols.Symbol, Contexts.Context, Scopes.Scope, Constants.Constant,
Names.Name, Denotations._, Annotations.Annotation
import typer.Implicits.SearchResult

/** The base class of all printers
*/
Expand Down Expand Up @@ -94,7 +95,10 @@ abstract class Printer {
/** Textual representation of tree */
def toText[T >: Untyped](tree: Tree[T]): Text

/** Perform string or text-producing operation `op` so that only a
/** Textual representation of implicit search result */
def toText(result: SearchResult): Text

/** Perform string or text-producing operation `op` so that only a
* summarized text with given recursion depth is shown
*/
def summarized[T](depth: Int)(op: => T): T
Expand Down
71 changes: 43 additions & 28 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import core._
import ast.{Trees, untpd, tpd, TreeInfo}
import util.Positions._
import util.Stats.{track, record, monitored}
import printing.Showable
import printing.{Showable, Printer}
import printing.Texts._
import Contexts._
import Types._
import Flags._
Expand Down Expand Up @@ -219,14 +220,16 @@ object Implicits {
}

/** The result of an implicit search */
abstract class SearchResult
abstract class SearchResult extends Showable {
def toText(printer: Printer): Text = printer.toText(this)
}

/** A successful search
* @param ref The implicit reference that succeeded
* @param tree The typed tree that needs to be inserted
* @param ctx The context after the implicit search
*/
case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult {
case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult with Showable {
override def toString = s"SearchSuccess($tree, $ref, $level)"
}

Expand Down Expand Up @@ -256,7 +259,7 @@ object Implicits {
}

/** An ambiguous implicits failure */
class AmbiguousImplicits(alt1: TermRef, alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure {
class AmbiguousImplicits(val alt1: TermRef, val alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure {
def explanation(implicit ctx: Context): String =
em"both ${err.refStr(alt1)} and ${err.refStr(alt2)} $qualify"
override def postscript(implicit ctx: Context) =
Expand Down Expand Up @@ -380,7 +383,9 @@ trait ImplicitRunInfo { self: RunInfo =>
EmptyTermRefSet // on the other hand, the refs of `tp` are now not accurate, so `tp` is marked incomplete.
} else {
seen += t
iscope(t).companionRefs
val is = iscope(t)
if (!implicitScopeCache.contains(t)) incomplete += tp
is.companionRefs
}
}

Expand Down Expand Up @@ -436,10 +441,8 @@ trait ImplicitRunInfo { self: RunInfo =>
if (ctx.typerState.ephemeral)
record("ephemeral cache miss: implicitScope")
else if (canCache &&
((tp eq rootTp) || // first type traversed is always cached
!incomplete.contains(tp) && // other types are cached if they are not incomplete
result.companionRefs.forall( // and all their companion refs are cached
implicitScopeCache.contains)))
((tp eq rootTp) || // first type traversed is always cached
!incomplete.contains(tp))) // other types are cached if they are not incomplete
implicitScopeCache(tp) = result
result
}
Expand Down Expand Up @@ -504,7 +507,36 @@ trait Implicits { self: Typer =>
* which is itself parameterized by another string,
* indicating where the implicit parameter is needed
*/
def inferImplicitArg(formal: Type, error: (String => String) => Unit, pos: Position)(implicit ctx: Context): Tree =
def inferImplicitArg(formal: Type, error: (String => String) => Unit, pos: Position)(implicit ctx: Context): Tree = {

/** If `formal` is of the form ClassTag[T], where `T` is a class type,
* synthesize a class tag for `T`.
*/
def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = {
if (formal.isRef(defn.ClassTagClass))
formal.argTypes match {
case arg :: Nil =>
fullyDefinedType(arg, "ClassTag argument", pos) match {
case defn.ArrayOf(elemTp) =>
val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos)
if (etag.isEmpty) etag else etag.select(nme.wrap)
case tp if hasStableErasure(tp) =>
if (defn.isBottomClass(tp.typeSymbol))
error(where => i"attempt to take ClassTag of undetermined type for $where")
ref(defn.ClassTagModule)
.select(nme.apply)
.appliedToType(tp)
.appliedTo(clsOf(erasure(tp)))
.withPos(pos)
case tp =>
EmptyTree
}
case _ =>
EmptyTree
}
else EmptyTree
}

inferImplicit(formal, EmptyTree, pos) match {
case SearchSuccess(arg, _, _, _) =>
arg
Expand All @@ -531,24 +563,6 @@ trait Implicits { self: Typer =>
EmptyTree
}
}

/** If `formal` is of the form ClassTag[T], where `T` is a class type,
* synthesize a class tag for `T`.
*/
def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = {
if (formal.isRef(defn.ClassTagClass))
formal.argTypes match {
case arg :: Nil =>
val tp = fullyDefinedType(arg, "ClassTag argument", pos)
if (hasStableErasure(tp))
return ref(defn.ClassTagModule)
.select(nme.apply)
.appliedToType(tp)
.appliedTo(clsOf(erasure(tp)))
.withPos(pos)
case _ =>
}
EmptyTree
}

private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = {
Expand Down Expand Up @@ -604,6 +618,7 @@ trait Implicits { self: Typer =>
result match {
case result: SearchSuccess =>
result.tstate.commit()
implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} ${ctx.typerState.hashesStr}")
result
case result: AmbiguousImplicits =>
val deepPt = pt.deepenProto
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1880,7 +1880,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
val args = (wtp.paramNames, wtp.paramTypes).zipped map { (pname, formal) =>
def implicitArgError(msg: String => String) =
errors += (() => msg(em"parameter $pname of $methodStr"))
inferImplicitArg(formal, implicitArgError, tree.pos.endPos)
if (errors.nonEmpty) EmptyTree
else inferImplicitArg(formal.widenExpr, implicitArgError, tree.pos.endPos)
}
if (errors.nonEmpty) {
// If there are several arguments, some arguments might already
Expand Down
33 changes: 19 additions & 14 deletions compiler/test/dotty/tools/dotc/CompilerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -397,20 +397,25 @@ abstract class CompilerTest {
/** Gives an error message for one line where the expected number of errors and
* the number of compiler errors differ. */
def compareLines(fileName: String, expectedLines: List[(Int, Int)], foundLines: List[(Int, Int)]) = {
expectedLines.foreach({ case (line, expNr) =>
foundLines.find(_._1 == line) match {
case Some((_, `expNr`)) => // this line is ok
case Some((_, foundNr)) => errorMsg(fileName, Some(line), expNr, foundNr)
case None => errorMsg(fileName, Some(line), expNr, 0)
}
})
foundLines.foreach({ case (line, foundNr) =>
expectedLines.find(_._1 == line) match {
case Some((_, `foundNr`)) => // this line is ok
case Some((_, expNr)) => errorMsg(fileName, Some(line), expNr, foundNr)
case None => errorMsg(fileName, Some(line), 0, foundNr)
}
})
expectedLines foreach{
case (line, expNr) =>
foundLines.find(_._1 == line) match {
case Some((_, `expNr`)) => // this line is ok
case Some((_, foundNr)) => errorMsg(fileName, Some(line), expNr, foundNr)
case None =>
println(s"expected lines = $expectedLines%, %")
println(s"found lines = $foundLines%, %")
errorMsg(fileName, Some(line), expNr, 0)
}
}
foundLines foreach {
case (line, foundNr) =>
expectedLines.find(_._1 == line) match {
case Some((_, `foundNr`)) => // this line is ok
case Some((_, expNr)) => errorMsg(fileName, Some(line), expNr, foundNr)
case None => errorMsg(fileName, Some(line), 0, foundNr)
}
}
}

// ========== PARTEST HELPERS =============
Expand Down
3 changes: 0 additions & 3 deletions library/src/dotty/DottyPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import scala.collection.Seq
object DottyPredef {
implicit def typeTag[T]: TypeTag[T] = ???

implicit def arrayTag[T](implicit ctag: ClassTag[T]): ClassTag[Array[T]] =
ctag.wrap

/** A fall-back implicit to compare values of any types.
* The compiler will restrict implicit instances of `eqAny`. An instance
* `eqAny[T, U]` is _valid_ if `T <: U` or `U <: T` or both `T` and `U` are
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i1802.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ object Exception {
def apply(x: Throwable): T = f(downcast(x).get)
}

def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f)
def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f) // error: undetermined ClassTag

implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) = // error: cyclic reference
implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) =
mkCatcher(pf.isDefinedAt _, pf.apply _)
}
7 changes: 7 additions & 0 deletions tests/neg/i1907.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.io.File

object Test {
Some(new File("."))
.map(_.listFiles).getOrElse(Array.empty) // error: undetermined ClassTag
.map(_.listFiles)
}
27 changes: 27 additions & 0 deletions tests/neg/undet-classtag.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import scala.reflect.ClassTag

object Test {
def f[T: reflect.ClassTag](x: T) = ???

f(???) // error: undetermined ClassTag
}

// SI 9754
object Program {
def test[T: ClassTag](x: T) = {
val arr = new Array[T](1)
println(arr.getClass)
println(x.getClass)
arr(0) = x
}

def main(args: Array[String]): Unit = {
test(new Array[Nothing](0)) // error: undetermined ClassTag
}
}

// SI 5353
object t5353 {
if (false) Array("qwe") else Array() // error: undetermined ClassTag
}

2 changes: 1 addition & 1 deletion tests/pos/t3859.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Test {
def foo: Unit = bar(Array(): _*)
def foo: Unit = bar(Array[AnyRef](): _*)
def bar(values: AnyRef*): Unit = ()
}
4 changes: 2 additions & 2 deletions tests/pos/t5859.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ class A {
f(List[AnyRef](): _*)
f(List(): _*)
f(Nil: _*)
f(Array(): _*)
// f(Array(): _*) // undetermined ClassTag
f(Array[AnyRef](): _*)
f(List(1))
f(List(1), Nil: _*)
f(List(1), Array(): _*)
// f(List(1), Array(): _*) // undetermined ClassTag
}
4 changes: 2 additions & 2 deletions tests/run/array-addition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ object Test {
def main(args: Array[String]): Unit = {
prettyPrintArray(Array(1,2,3) :+ 4)
prettyPrintArray(1 +: Array(2,3,4))
prettyPrintArray(Array() :+ 1)
prettyPrintArray(1 +: Array())
prettyPrintArray(Array[Int]() :+ 1)
prettyPrintArray(1 +: Array[Int]())
}
}

Loading