Skip to content

Fix #1430: Better error messages for type errors involving type variables #1434

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 11 commits into from
Aug 17, 2016
Merged
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/ast/NavigateAST.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object NavigateAST {
Error(i"""no untyped tree for $tree, pos = ${tree.pos}, envelope = ${tree.envelope}
|best matching path =\n$loosePath%\n====\n%
|path positions = ${loosePath.map(_.pos)}
|path envelopes = ${loosePath.map(_.envelope)}""".stripMargin)
|path envelopes = ${loosePath.map(_.envelope)}""")
}

/** The reverse path of untyped trees starting with a tree that closest matches
Expand Down
4 changes: 2 additions & 2 deletions src/dotty/tools/dotc/config/CompilerCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object CompilerCommand extends DotClass {
/** The name of the command */
def cmdName = "scalac"

private def explainAdvanced = "\n" + """
private def explainAdvanced = """
|-- Notes on option parsing --
|Boolean settings are always false unless set.
|Where multiple values are accepted, they should be comma-separated.
Expand All @@ -26,7 +26,7 @@ object CompilerCommand extends DotClass {
| example: -Ylog:erasure+ logs the erasure phase and the phase after the erasure phase.
| This is useful because during the tree transform of phase X, we often
| already are in phase X + 1.
""".stripMargin.trim + "\n"
"""

def shortUsage = s"Usage: $cmdName <options> <source files>"

Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Constraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ abstract class Constraint extends Showable {
def contains(tvar: TypeVar): Boolean

/** The constraint entry for given type parameter `param`, or NoType if `param` is not part of
* the constraint domain.
* the constraint domain. Note: Low level, implementation dependent.
*/
def entry(param: PolyParam): Type

Expand Down
74 changes: 13 additions & 61 deletions src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import util.Positions.Position, util.SourcePosition
import collection.mutable.ListBuffer
import dotty.tools.dotc.transform.TreeTransforms._
import scala.language.implicitConversions
import printing.Formatting._

/** This object provides useful implicit decorators for types defined elsewhere */
object Decorators {
Expand Down Expand Up @@ -150,72 +151,23 @@ object Decorators {
implicit def sourcePos(pos: Position)(implicit ctx: Context): SourcePosition =
ctx.source.atPos(pos)

/** The i"..." string interpolator adds two features to the s interpolator:
* 1) On all Showables, `show` is called instead of `toString`
* 2) Lists can be formatted using the desired separator between two `%` signs,
* eg `i"myList = (${myList}%, %)"`
*/
implicit class StringInterpolators(val sc: StringContext) extends AnyVal {

def i(args: Any*)(implicit ctx: Context): String = {

def treatArg(arg: Any, suffix: String): (Any, String) = arg match {
case arg: Seq[_] if suffix.nonEmpty && suffix.head == '%' =>
val (rawsep, rest) = suffix.tail.span(_ != '%')
val sep = StringContext.treatEscapes(rawsep)
if (rest.nonEmpty) (arg map treatSingleArg mkString sep, rest.tail)
else (arg, suffix)
case _ =>
(treatSingleArg(arg), suffix)
}

def treatSingleArg(arg: Any) : Any =
try
arg match {
case arg: Showable => arg.show(ctx.addMode(Mode.FutureDefsOK))
case _ => arg
}
catch {
case ex: Exception => throw ex // s"(missing due to $ex)"
}
/** General purpose string formatting */
def i(args: Any*)(implicit ctx: Context): String =
new StringFormatter(sc).assemble(args)

val prefix :: suffixes = sc.parts.toList
val (args1, suffixes1) = (args, suffixes).zipped.map(treatArg(_, _)).unzip
new StringContext(prefix :: suffixes1.toList: _*).s(args1: _*)
}
/** Formatting for error messages: Like `i` but suppress follow-on
* error messages after the first one if some of their arguments are "non-sensical".
*/
def em(args: Any*)(implicit ctx: Context): String =
new ErrorMessageFormatter(sc).assemble(args)

/** Lifted from scala.reflect.internal.util
* A safe combination of [[scala.collection.immutable.StringLike#stripMargin]]
* and [[scala.StringContext#raw]].
*
* The margin of each line is defined by whitespace leading up to a '|' character.
* This margin is stripped '''before''' the arguments are interpolated into to string.
*
* String escape sequences are '''not''' processed; this interpolater is designed to
* be used with triple quoted Strings.
*
* {{{
* scala> val foo = "f|o|o"
* foo: String = f|o|o
* scala> sm"""|${foo}
* |"""
* res0: String =
* "f|o|o
* "
* }}}
/** Formatting with added explanations: Like `em`, but add explanations to
* give more info about type variables and to disambiguate where needed.
*/
final def sm(args: Any*): String = {
def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak
def stripTrailingPart(s: String) = {
val (pre, post) = s.span(c => !isLineBreak(c))
pre + post.stripMargin
}
val stripped: List[String] = sc.parts.toList match {
case head :: tail => head.stripMargin :: (tail map stripTrailingPart)
case Nil => Nil
}
new StringContext(stripped: _*).raw(args: _*)
}
def ex(args: Any*)(implicit ctx: Context): String =
explained2(implicit ctx => em(args: _*))
}
}

2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,7 @@ object Denotations {
| $sym2: ${sym2.info};
|they are both defined in ${sym1.owner} but have matching signatures
| ${denot1.info} and
| ${denot2.info}$fromWhere""".stripMargin,
| ${denot2.info}$fromWhere""",
denot2.info, denot2.info)
}

Expand Down
16 changes: 8 additions & 8 deletions src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -669,19 +669,19 @@ object SymDenotations {
val cls = owner.enclosingSubClass
if (!cls.exists)
fail(
s""" Access to protected $this not permitted because
i""" Access to protected $this not permitted because
| enclosing ${ctx.owner.enclosingClass.showLocated} is not a subclass of
| ${owner.showLocated} where target is defined""".stripMargin)
| ${owner.showLocated} where target is defined""")
else if (
!( isType // allow accesses to types from arbitrary subclasses fixes #4737
|| pre.baseTypeRef(cls).exists // ??? why not use derivesFrom ???
|| isConstructor
|| (owner is ModuleClass) // don't perform this check for static members
))
fail(
s""" Access to protected ${symbol.show} not permitted because
i""" Access to protected ${symbol.show} not permitted because
| prefix type ${pre.widen.show} does not conform to
| ${cls.showLocated} where the access takes place""".stripMargin)
| ${cls.showLocated} where the access takes place""")
else true
}

Expand Down Expand Up @@ -1933,10 +1933,10 @@ object SymDenotations {
else ("", "the signature")
val name = ctx.fresh.setSetting(ctx.settings.debugNames, true).nameString(denot.name)
ctx.error(
s"""|bad symbolic reference. A signature$location
|refers to $name in ${denot.owner.showKind} ${denot.owner.showFullName} which is not available.
|It may be completely missing from the current classpath, or the version on
|the classpath might be incompatible with the version used when compiling $src.""".stripMargin)
i"""bad symbolic reference. A signature$location
|refers to $name in ${denot.owner.showKind} ${denot.owner.showFullName} which is not available.
|It may be completely missing from the current classpath, or the version on
|the classpath might be incompatible with the version used when compiling $src.""")
if (ctx.debug) throw new Error()
initializeToDefaults(denot)
}
Expand Down
4 changes: 2 additions & 2 deletions src/dotty/tools/dotc/core/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ class SymbolLoaders {
// require yjp.jar at runtime. See SI-2089.
if (ctx.settings.termConflict.isDefault)
throw new TypeError(
sm"""$owner contains object and package with same name: $pname
|one of them needs to be removed from classpath""")
i"""$owner contains object and package with same name: $pname
|one of them needs to be removed from classpath""")
else if (ctx.settings.termConflict.value == "package") {
ctx.warning(
s"Resolving package/object name conflict in favor of package ${preExisting.fullName}. The object will be inaccessible.")
Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ object Symbols {
def toText(printer: Printer): Text = printer.toText(this)

def showLocated(implicit ctx: Context): String = ctx.locatedText(this).show
def showExtendedLocation(implicit ctx: Context): String = ctx.extendedLocationText(this).show
def showDcl(implicit ctx: Context): String = ctx.dclText(this).show
def showKind(implicit ctx: Context): String = ctx.kindString(this)
def showName(implicit ctx: Context): String = ctx.nameString(this)
Expand Down
3 changes: 1 addition & 2 deletions src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import Types._, Contexts._, Symbols._, Flags._, Names._, NameOps._, Denotations.
import Decorators._
import StdNames.{nme, tpnme}
import collection.mutable
import printing.Disambiguation.disambiguated
import util.{Stats, DotClass, SimpleMap}
import config.Config
import config.Printers._
Expand Down Expand Up @@ -1469,7 +1468,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {

/** Show subtype goal that led to an assertion failure */
def showGoal(tp1: Type, tp2: Type)(implicit ctx: Context) = {
println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint"))
println(ex"assertion failure for $tp1 <:< $tp2, frozen = $frozenConstraint")
def explainPoly(tp: Type) = tp match {
case tp: PolyParam => ctx.echo(s"polyparam ${tp.show} found in ${tp.binder.show}")
case tp: TypeRef if tp.symbol.exists => ctx.echo(s"typeref ${tp.show} found in ${tp.symbol.owner.show}")
Expand Down
8 changes: 4 additions & 4 deletions src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1493,11 +1493,11 @@ object Types {
(sym.owner.derivesFrom(lastSymbol.owner) ||
selfTypeOf(sym).derivesFrom(lastSymbol.owner) ||
selfTypeOf(lastSymbol).derivesFrom(sym.owner))),
s"""data race? overwriting symbol of type ${this.show},
|long form = $this of class ${this.getClass},
i"""data race? overwriting symbol of type $this,
|long form = $toString of class $getClass,
|last sym id = ${lastSymbol.id}, new sym id = ${sym.id},
|last owner = ${lastSymbol.owner}, new owner = ${sym.owner},
|period = ${ctx.phase} at run ${ctx.runId}""".stripMargin)
|period = ${ctx.phase} at run ${ctx.runId}""")
}

protected def sig: Signature = Signature.NotAMethod
Expand Down Expand Up @@ -3799,7 +3799,7 @@ object Types {

class MissingType(pre: Type, name: Name)(implicit ctx: Context) extends TypeError(
i"""cannot resolve reference to type $pre.$name
|the classfile defining the type might be missing from the classpath${otherReason(pre)}""".stripMargin) {
|the classfile defining the type might be missing from the classpath${otherReason(pre)}""") {
if (ctx.debug) printStackTrace()
}

Expand Down
27 changes: 11 additions & 16 deletions src/dotty/tools/dotc/core/classfile/ClassfileParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,21 @@ class ClassfileParser(
case e: RuntimeException =>
if (ctx.debug) e.printStackTrace()
throw new IOException(
sm"""class file $classfile is broken, reading aborted with ${e.getClass}
|${Option(e.getMessage).getOrElse("")}""")
i"""class file $classfile is broken, reading aborted with ${e.getClass}
|${Option(e.getMessage).getOrElse("")}""")
}

private def parseHeader(): Unit = {
val magic = in.nextInt
if (magic != JAVA_MAGIC)
throw new IOException("class file '" + in.file + "' "
+ "has wrong magic number 0x" + toHexString(magic)
+ ", should be 0x" + toHexString(JAVA_MAGIC))
throw new IOException(s"class file '${in.file}' has wrong magic number 0x${toHexString(magic)}, should be 0x${toHexString(JAVA_MAGIC)}")
val minorVersion = in.nextChar.toInt
val majorVersion = in.nextChar.toInt
if ((majorVersion < JAVA_MAJOR_VERSION) ||
((majorVersion == JAVA_MAJOR_VERSION) &&
(minorVersion < JAVA_MINOR_VERSION)))
throw new IOException("class file '" + in.file + "' "
+ "has unknown version "
+ majorVersion + "." + minorVersion
+ ", should be at least "
+ JAVA_MAJOR_VERSION + "." + JAVA_MINOR_VERSION)
throw new IOException(
s"class file '${in.file}' has unknown version $majorVersion.$minorVersion, should be at least $JAVA_MAJOR_VERSION.$JAVA_MINOR_VERSION")
}

/** Return the class symbol of the given name. */
Expand Down Expand Up @@ -817,12 +812,12 @@ class ClassfileParser(
getMember(owner, innerName.toTypeName)
}
assert(result ne NoSymbol,
sm"""failure to resolve inner class:
|externalName = $externalName,
|outerName = $outerName,
|innerName = $innerName
|owner.fullName = ${owner.showFullName}
|while parsing ${classfile}""")
i"""failure to resolve inner class:
|externalName = $externalName,
|outerName = $outerName,
|innerName = $innerName
|owner.fullName = ${owner.showFullName}
|while parsing ${classfile}""")
result

case None =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas

protected def errorBadSignature(msg: String, original: Option[RuntimeException] = None)(implicit ctx: Context) = {
val ex = new BadSignature(
sm"""error reading Scala signature of $classRoot from $source:
|error occurred at position $readIndex: $msg""")
i"""error reading Scala signature of $classRoot from $source:
|error occurred at position $readIndex: $msg""")
if (ctx.debug || true) original.getOrElse(ex).printStackTrace() // temporarily enable printing of original failure signature to debug failing builds
throw ex
}
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ object Parsers {
case Typed(Ident(name), tpt) =>
makeParameter(name.asTermName, tpt, mods) withPos tree.pos
case _ =>
syntaxError(s"not a legal $expected (${tree.getClass})", tree.pos)
syntaxError(s"not a legal $expected", tree.pos)
makeParameter(nme.ERROR, tree, mods)
}

Expand Down
86 changes: 0 additions & 86 deletions src/dotty/tools/dotc/printing/Disambiguation.scala

This file was deleted.

Loading