From a57b4a3ec5b139711ad7e729040165e58751a49c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Aug 2016 14:34:48 +0200 Subject: [PATCH 01/11] Fix #1430: Avoid constrained polyparams in error message When issuing a type mismatch error, avoid mentioning polyparams in the current constraint set and their associated typevars. Mention instead the bound that caused the constrained to become unsatisfiable (if that bound is unique, i.e. the parameter appears co- or contravariantly in the type). --- src/dotty/tools/dotc/typer/ErrorReporting.scala | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index d3303628ef69..6f7d427cb194 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -113,9 +113,23 @@ object ErrorReporting { case tp: TypeRef => s"with info ${tp.info} / ${tp.prefix.toString} / ${tp.prefix.dealias.toString}" case _ => "" } + // replace constrained polyparams and their typevars by their bounds where possible + val reported = new TypeMap { + def apply(tp: Type): Type = tp match { + case tp: PolyParam => + val e = ctx.typerState.constraint.entry(tp) + if (e.exists) + if (variance > 0) e.bounds.hi + else if (variance < 0) e.bounds.lo + else tp + else tp + case tp: TypeVar => apply(tp.stripTypeVar) + case _ => mapOver(tp) + } + } d"""type mismatch: | found : $found - | required: $expected""".stripMargin + whyNoMatchStr(found, expected) + | required: ${reported(expected)}""".stripMargin + whyNoMatchStr(found, expected) } /** Format `raw` implicitNotFound argument, replacing all From d2120ca8accb7d6b8438431931c84870e835512c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Aug 2016 14:37:57 +0200 Subject: [PATCH 02/11] Suspend interpolating typevars when there are unreported errors. Interpolating typevars that appear co- or contra-variantly in a type is a cleanup measure - it helps keep the constraint set small. However, if there are uneported errors, some of these errors might report on unsatisfiable constraints for these type variables. In that case, instantiating the type variables risks being confusing. --- src/dotty/tools/dotc/typer/Inferencing.scala | 38 ++++++++++++++++---- tests/neg/i1430.scala | 8 +++++ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 tests/neg/i1430.scala diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala index c60f4c1f2500..7c61f8c2348a 100644 --- a/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/src/dotty/tools/dotc/typer/Inferencing.scala @@ -17,6 +17,7 @@ import Decorators._ import Uniques._ import config.Printers._ import annotation.tailrec +import reporting._ import collection.mutable object Inferencing { @@ -222,13 +223,38 @@ object Inferencing { val vs = variances(tp, qualifies) var changed = false - vs foreachBinding { (tvar, v) => - if (v != 0) { - typr.println(s"interpolate ${if (v == 1) "co" else "contra"}variant ${tvar.show} in ${tp.show}") - tvar.instantiate(fromBelow = v == 1) - changed = true - } + val hasUnreportedErrors = ctx.typerState.reporter match { + case r: StoreReporter if r.hasErrors => true + case _ => false } + // Avoid interpolating variables if typerstate has unreported errors. + // Reason: The errors might reflect unsatisfiable constraints. In that + // case interpolating without taking account the constraints risks producing + // nonsensical types that then in turn produce incomprehensible errors. + // An example is in neg/i1240.scala. Without the condition in the next code line + // we get for + // + // val y: List[List[String]] = List(List(1)) + // + // i1430.scala:5: error: type mismatch: + // found : Int(1) + // required: Nothing + // val y: List[List[String]] = List(List(1)) + // ^ + // With the condition, we get the much more sensical: + // + // i1430.scala:5: error: type mismatch: + // found : Int(1) + // required: String + // val y: List[List[String]] = List(List(1)) + if (!hasUnreportedErrors) + vs foreachBinding { (tvar, v) => + if (v != 0) { + typr.println(s"interpolate ${if (v == 1) "co" else "contra"}variant ${tvar.show} in ${tp.show}") + tvar.instantiate(fromBelow = v == 1) + changed = true + } + } if (changed) // instantiations might have uncovered new typevars to interpolate interpolateUndetVars(tree, ownedBy) else diff --git a/tests/neg/i1430.scala b/tests/neg/i1430.scala new file mode 100644 index 000000000000..870780695c90 --- /dev/null +++ b/tests/neg/i1430.scala @@ -0,0 +1,8 @@ +object Test { + + val x: List[String] = List(1) // error: found Int(1), expected: String + + val y: List[List[String]] = List(List(1)) // error: found Int(1), expected: String + + val z: (List[String], List[Int]) = (List(1), List("a")) // error: found Int(1), expected: String // error: found String(a), expected: Int +} From d5f42680803e40f9b3698404848450d088fca07a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Aug 2016 14:57:52 +0200 Subject: [PATCH 03/11] Error message for illegal self type (#1424) Remove debug info from error message. --- src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- tests/neg/i1424.scala | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i1424.scala diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index edc9d3f66ee9..600707cbfc41 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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) } diff --git a/tests/neg/i1424.scala b/tests/neg/i1424.scala new file mode 100644 index 000000000000..3586260c19a6 --- /dev/null +++ b/tests/neg/i1424.scala @@ -0,0 +1,3 @@ +class Test { + (x: Int) => x // error: not a legal self type clause // error: package x is not a value // error: package x is not a value +} From 8d4d9a363d90cc24bd79b18ea2ef7cba6a746bef Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 7 Aug 2016 17:29:24 +0200 Subject: [PATCH 04/11] New string infterpolators Roll `sm` and `i` into one interpolator (also called `i`) Evolve `d` to `em` interpolator (for error messages) New interpolator `ex` with more explanations, replaces disambiguation. --- src/dotty/tools/dotc/ast/NavigateAST.scala | 2 +- .../tools/dotc/config/CompilerCommand.scala | 4 +- src/dotty/tools/dotc/core/Constraint.scala | 2 +- src/dotty/tools/dotc/core/Decorators.scala | 74 ++------ src/dotty/tools/dotc/core/Denotations.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 16 +- src/dotty/tools/dotc/core/SymbolLoaders.scala | 4 +- src/dotty/tools/dotc/core/Symbols.scala | 1 + src/dotty/tools/dotc/core/TypeComparer.scala | 3 +- src/dotty/tools/dotc/core/Types.scala | 8 +- .../dotc/core/classfile/ClassfileParser.scala | 27 ++- .../core/unpickleScala2/Scala2Unpickler.scala | 4 +- .../tools/dotc/printing/Disambiguation.scala | 86 --------- .../tools/dotc/printing/Formatting.scala | 175 ++++++++++++++++++ .../tools/dotc/printing/PlainPrinter.scala | 40 +++- src/dotty/tools/dotc/printing/Printer.scala | 3 + .../tools/dotc/printing/RefinedPrinter.scala | 4 +- src/dotty/tools/dotc/reporting/Reporter.scala | 10 +- .../tools/dotc/transform/CheckReentrant.scala | 2 +- .../dotc/transform/ExtensionMethods.scala | 26 +-- .../tools/dotc/transform/PatternMatcher.scala | 5 +- src/dotty/tools/dotc/transform/Pickler.scala | 4 +- src/dotty/tools/dotc/typer/Applications.scala | 10 +- src/dotty/tools/dotc/typer/Checking.scala | 38 ++-- .../tools/dotc/typer/ErrorReporting.scala | 85 ++++----- src/dotty/tools/dotc/typer/Implicits.scala | 20 +- src/dotty/tools/dotc/typer/Namer.scala | 8 +- src/dotty/tools/dotc/typer/RefChecks.scala | 6 +- src/dotty/tools/dotc/typer/TypeAssigner.scala | 22 ++- src/dotty/tools/dotc/typer/Typer.scala | 52 +++--- tests/neg/i1430.scala | 26 +++ 31 files changed, 420 insertions(+), 349 deletions(-) delete mode 100644 src/dotty/tools/dotc/printing/Disambiguation.scala create mode 100644 src/dotty/tools/dotc/printing/Formatting.scala diff --git a/src/dotty/tools/dotc/ast/NavigateAST.scala b/src/dotty/tools/dotc/ast/NavigateAST.scala index 782866bad992..2b11f81f3cd6 100644 --- a/src/dotty/tools/dotc/ast/NavigateAST.scala +++ b/src/dotty/tools/dotc/ast/NavigateAST.scala @@ -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 diff --git a/src/dotty/tools/dotc/config/CompilerCommand.scala b/src/dotty/tools/dotc/config/CompilerCommand.scala index 2fe32b4d339b..19ede3cece9b 100644 --- a/src/dotty/tools/dotc/config/CompilerCommand.scala +++ b/src/dotty/tools/dotc/config/CompilerCommand.scala @@ -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. @@ -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 " diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index e10523753e1a..436b035dcc3f 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -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 diff --git a/src/dotty/tools/dotc/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index 7d108a45923f..387e7e466d2c 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -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 { @@ -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: _*)) } } diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 80daa96810fa..4f01c43cfb63 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -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) } diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 9d96f2b15f6f..47ec541ab84c 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -669,9 +669,9 @@ 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 ??? @@ -679,9 +679,9 @@ object SymDenotations { || (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 } @@ -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) } diff --git a/src/dotty/tools/dotc/core/SymbolLoaders.scala b/src/dotty/tools/dotc/core/SymbolLoaders.scala index a62a88dfbf1f..3f801bda57f2 100644 --- a/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -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.") diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index d46ea6b0fd12..38b2c8bd6cd5 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -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) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index d6ada7244b3b..091999412fa8 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -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._ @@ -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}") diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 3e04e9c77af5..1bfd6eaee727 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -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 @@ -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() } diff --git a/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index a6d381693783..4ea98f7c377d 100644 --- a/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -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. */ @@ -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 => diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 8ea4cecde6e9..0d91e8cd64eb 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -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 } diff --git a/src/dotty/tools/dotc/printing/Disambiguation.scala b/src/dotty/tools/dotc/printing/Disambiguation.scala deleted file mode 100644 index aa3fae2dee88..000000000000 --- a/src/dotty/tools/dotc/printing/Disambiguation.scala +++ /dev/null @@ -1,86 +0,0 @@ -package dotty.tools.dotc -package printing - -import core._ -import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Contexts._ -import collection.mutable -import scala.annotation.switch - -object Disambiguation { - - private class State { - var hasConflicts = false - val symString = new mutable.HashMap[Symbol, String] - val variants = new mutable.HashMap[String, mutable.ListBuffer[Symbol]] - } - - def newPrinter: Context => RefinedPrinter = { - val state = new State - new Printer(state)(_) - } - - private class Printer(state: State)(_ctx: Context) extends RefinedPrinter(_ctx) { - import state._ - - override def simpleNameString(sym: Symbol): String = { - if ((sym is ModuleClass) && sym.sourceModule.exists) simpleNameString(sym.sourceModule) - else symString.getOrElse(sym, recordedNameString(sym)) - } - - private def rawNameString(sym: Symbol) = super.simpleNameString(sym) - - private def recordedNameString(sym: Symbol): String = { - val str = rawNameString(sym) - val existing = variants.getOrElse(str, new mutable.ListBuffer[Symbol]) - // Dotty deviation: without a type parameter on ListBuffer, inference - // will compute ListBuffer[Symbol] | ListBuffer[Nothing] as the type of "existing" - // and then the assignment to variants below will fail. - // We need to find a way to avoid such useless inferred types. - if (!(existing contains sym)) { - hasConflicts |= existing.nonEmpty - variants(str) = (existing += sym) - } - str - } - - def disambiguated(): Boolean = { - val res = hasConflicts - while (hasConflicts) disambiguate() - res - } - - private def qualifiers: Stream[String] = - Stream("", "(some other)", "(some 3rd)") ++ (Stream.from(4) map (n => s"(some ${n}th)")) - - private def disambiguate(): Unit = { - def update(sym: Symbol, str: String) = if (!(symString contains sym)) symString(sym) = str - def disambiguated(sym: Symbol, owner: Symbol) = s"${rawNameString(sym)}(in ${simpleNameString(owner)})" - hasConflicts = false - for ((name, vs) <- variants.toList) - if (vs.tail.nonEmpty) { - for ((owner, syms) <- vs.groupBy(_.effectiveOwner)) { - if (syms.tail.isEmpty) update(syms.head, disambiguated(syms.head, owner)) - else - for { - (kind, syms1) <- syms.groupBy(kindString) - (sym, qual) <- syms1 zip qualifiers - } { - update(sym, s"$qual$kind ${disambiguated(sym, owner)}") - } - } - } - } - } - - def disambiguated(op: Context => String)(implicit ctx: Context): String = { - val dctx = ctx.printer match { - case dp: Printer => ctx - case _ => ctx.fresh.setPrinterFn(newPrinter) - } - val res = op(dctx) - dctx.printer match { - case dp: Printer if dp.disambiguated() => op(dctx) - case _ => res - } - } -} diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala new file mode 100644 index 000000000000..174d801d10e3 --- /dev/null +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -0,0 +1,175 @@ +package dotty.tools.dotc +package printing + +import core._ +import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Contexts._ +import collection.mutable +import collection.Map +import Decorators._ +import scala.annotation.switch +import scala.util.control.NonFatal +import reporting.Diagnostic + +object Formatting { + + /** General purpose string formatter, with the following features: + * + * 1) On all Showables, `show` is called instead of `toString` + * 2) Exceptions raised by a `show` are handled by falling back to `toString`. + * 3) Sequences can be formatted using the desired separator between two `%` signs, + * eg `i"myList = (${myList}%, %)"` + * 4) Safe handling of multi-line margins. Left margins are skipped om the parts + * of the string context *before* inserting the arguments. That way, we guard + * against accidentally treating an interpolated value as a margin. + */ + class StringFormatter(protected val sc: StringContext) { + + protected def showArg(arg: Any)(implicit ctx: Context): String = arg match { + case arg: Showable => + try arg.show(ctx.addMode(Mode.FutureDefsOK)) + catch { + case NonFatal(ex) => s"(missing due to $ex)" + } + case _ => arg.toString + } + + private def treatArg(arg: Any, suffix: String)(implicit ctx: Context): (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(showArg).mkString(sep), rest.tail) + else (arg, suffix) + case _ => + (showArg(arg), suffix) + } + + def assemble(args: Seq[Any])(implicit ctx: Context): 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 (prefix, suffixes) = sc.parts.toList match { + case head :: tail => (head.stripMargin, tail map stripTrailingPart) + case Nil => ("", Nil) + } + val (args1, suffixes1) = (args, suffixes).zipped.map(treatArg(_, _)).unzip + new StringContext(prefix :: suffixes1.toList: _*).s(args1: _*) + } + } + + /** The d string interpolator works like the i string interpolator, but marks nonsensical errors + * using `...` tags. + * Note: Instead of these tags, it would be nicer to return a data structure containing the message string + * and a boolean indicating whether the message is sensical, but then we cannot use string operations + * like concatenation, stripMargin etc on the values returned by d"...", and in the current error + * message composition methods, this is crucial. + */ + class ErrorMessageFormatter(sc: StringContext) extends StringFormatter(sc) { + override protected def showArg(arg: Any)(implicit ctx: Context): String = { + def isSensical(arg: Any): Boolean = arg match { + case tpe: Type => + tpe.exists && !tpe.isErroneous + case sym: Symbol if sym.isCompleted => + sym.info != ErrorType && sym.info != TypeAlias(ErrorType) && sym.info.exists + case _ => true + } + val str = super.showArg(arg) + if (isSensical(arg)) str else Diagnostic.nonSensicalStartTag + str + Diagnostic.nonSensicalEndTag + } + } + + private type Recorded = AnyRef /*Symbol | PolyParam*/ + + private class Seen extends mutable.HashMap[String, List[Recorded]] { + + override def default(key: String) = Nil + + def record(str: String, entry: Recorded): String = { + var alts = apply(str).dropWhile(entry ne _) + if (alts.isEmpty) { + alts = entry :: apply(str) + update(str, alts) + } + str + "'" * (alts.length - 1) + } + } + + private class ExplainingPrinter(seen: Seen)(_ctx: Context) extends RefinedPrinter(_ctx) { + override def simpleNameString(sym: Symbol): String = + if ((sym is ModuleClass) && sym.sourceModule.exists) simpleNameString(sym.sourceModule) + else seen.record(super.simpleNameString(sym), sym) + + override def polyParamNameString(param: PolyParam): String = + seen.record(super.polyParamNameString(param), param) + } + + def explained2(op: Context => String)(implicit ctx: Context): String = { + val seen = new Seen + val explainCtx = ctx.printer match { + case dp: ExplainingPrinter => ctx // re-use outer printer and defer explanation to it + case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx)) + } + + def explanation(entry: Recorded): String = { + def boundStr(bound: Type, default: ClassSymbol, cmp: String) = + if (bound.isRef(default)) "" else i"$cmp $bound" + + def boundsStr(bounds: TypeBounds): String = { + val lo = boundStr(bounds.lo, defn.NothingClass, ">:") + val hi = boundStr(bounds.hi, defn.AnyClass, "<:") + if (lo.isEmpty) hi + else if (hi.isEmpty) lo + else s"$lo and $hi" + } + + def addendum(cat: String, info: Type)(implicit ctx: Context): String = info match { + case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty => + if (lo eq hi) i" which is an alias of $lo" + else i" with $cat ${boundsStr(bounds)}" + case _ => + "" + } + + entry match { + case param: PolyParam => + s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}" + case sym: Symbol => + val ownerStr = + if (!sym.exists) "" + else { + var owner = sym.effectiveOwner + if (owner.isLocalDummy) i" locally defined in ${owner.owner}" + else i" in $owner" + } + s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}" + } + } + + def explanations(seen: Seen)(implicit ctx: Context): String = { + def needsExplanation(entry: Recorded) = entry match { + case param: PolyParam => ctx.typerState.constraint.contains(param) + case _ => false + } + val toExplain: List[(String, Recorded)] = seen.toList.flatMap { + case (str, entry :: Nil) => + if (needsExplanation(entry)) (str, entry) :: Nil else Nil + case (str, entries) => + entries.map(alt => (seen.record(str, alt), alt)) + }.sortBy(_._1) + val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } + val explainLines = columnar(explainParts, " ") + if (explainLines.isEmpty) "" else i"\n\nwhere $explainLines%\n %\n" + } + + op(explainCtx) ++ explanations(seen) + } + + def columnar(parts: List[(String, String)], sep: String): List[String] = { + lazy val maxLen = parts.map(_._1.length).max + parts.map { + case (leader, trailer) => + s"$leader${" " * (maxLen - leader.length)}$sep$trailer" + } + } +} diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index acf4514ea4d1..a92095d9bd15 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -188,22 +188,22 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: TypeLambda => typeLambdaText(tp.paramNames.map(_.toString), tp.variances, tp.paramBounds, tp.resultType) case tp: PolyType => - def paramText(name: TypeName, bounds: TypeBounds) = - toText(polyParamName(name)) ~ polyHash(tp) ~ toText(bounds) + def paramText(name: TypeName, bounds: TypeBounds): Text = + polyParamNameString(name) ~ polyHash(tp) ~ toText(bounds) changePrec(GlobalPrec) { "[" ~ Text((tp.paramNames, tp.paramBounds).zipped map paramText, ", ") ~ "]" ~ toText(tp.resultType) } - case PolyParam(pt, n) => - toText(polyParamName(pt.paramNames(n))) ~ polyHash(pt) + case tp: PolyParam => + polyParamNameString(tp) ~ polyHash(tp.binder) case AnnotatedType(tpe, annot) => toTextLocal(tpe) ~ " " ~ toText(annot) case HKApply(tycon, args) => toTextLocal(tycon) ~ "[" ~ Text(args.map(argText), ", ") ~ "]" case tp: TypeVar => if (tp.isInstantiated) - toTextLocal(tp.instanceOpt) ~ "'" // debug for now, so that we can see where the TypeVars are. + toTextLocal(tp.instanceOpt) ~ "^" // debug for now, so that we can see where the TypeVars are. else { val constr = ctx.typerState.constraint val bounds = @@ -219,7 +219,9 @@ class PlainPrinter(_ctx: Context) extends Printer { } }.close - protected def polyParamName(name: TypeName): TypeName = name + protected def polyParamNameString(name: TypeName): String = name.toString + + protected def polyParamNameString(param: PolyParam): String = polyParamNameString(param.binder.paramNames(param.paramNum)) /** The name of the symbol without a unique id. Under refined printing, * the decoded original name. @@ -416,13 +418,33 @@ class PlainPrinter(_ctx: Context) extends Printer { def locationText(sym: Symbol): Text = if (!sym.exists) "" else { - val owns = sym.effectiveOwner - if (owns.isClass && !isEmptyPrefix(owns)) " in " ~ toText(owns) else Text() - } + val ownr = sym.effectiveOwner + if (ownr.isClass && !isEmptyPrefix(ownr)) " in " ~ toText(ownr) else Text() + } def locatedText(sym: Symbol): Text = (toText(sym) ~ locationText(sym)).close + def extendedLocationText(sym: Symbol): Text = + if (!sym.exists) "" + else { + def recur(ownr: Symbol, innerLocation: String): Text = { + def nextOuter(innerKind: String): Text = + recur(ownr.effectiveOwner, + if (!innerLocation.isEmpty) innerLocation + else s" in an anonymous $innerKind") + def showLocation(ownr: Symbol, where: String): Text = + innerLocation ~ " " ~ where ~ " " ~ toText(ownr) + if (ownr.isAnonymousClass) nextOuter("class") + else if (ownr.isAnonymousFunction) nextOuter("function") + else if (isEmptyPrefix(ownr)) "" + else if (ownr.isLocalDummy) showLocation(ownr.owner, "locally defined in") + else if (ownr.isTerm && !ownr.is(Module | Method)) showLocation(ownr, "in the initalizer of") + else showLocation(ownr, "in") + } + recur(sym.owner, "") + } + def toText(denot: Denotation): Text = toText(denot.symbol) ~ "/D" @switch private def escapedChar(ch: Char): String = ch match { diff --git a/src/dotty/tools/dotc/printing/Printer.scala b/src/dotty/tools/dotc/printing/Printer.scala index 360874522ce1..14b63012eb67 100644 --- a/src/dotty/tools/dotc/printing/Printer.scala +++ b/src/dotty/tools/dotc/printing/Printer.scala @@ -68,6 +68,9 @@ abstract class Printer { /** Textual representation of symbol and its location */ def locatedText(sym: Symbol): Text + /** A description of sym's location */ + def extendedLocationText(sym: Symbol): Text + /** Textual representation of denotation */ def toText(denot: Denotation): Text diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index ce063f06a221..83f61c97663c 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -532,8 +532,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def optText[T >: Untyped](tree: List[Tree[T]])(encl: Text => Text): Text = if (tree.exists(!_.isEmpty)) encl(blockText(tree)) else "" - override protected def polyParamName(name: TypeName): TypeName = - name.unexpandedName + override protected def polyParamNameString(name: TypeName): String = + name.unexpandedName.toString override protected def treatAsTypeParam(sym: Symbol): Boolean = sym is TypeParam diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index bddfd2f680ba..b3d173a423d5 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -79,11 +79,11 @@ trait Reporting { this: Context => if (reporter.isReportedFeatureUseSite(featureUseSite)) "" else { reporter.reportNewFeatureUseSite(featureUseSite) - s"""| - |This can be achieved by adding the import clause 'import $fqname' - |or by setting the compiler option -language:$feature. - |See the Scala docs for value $fqname for a discussion - |why the feature $req be explicitly enabled.""".stripMargin + s""" + |This can be achieved by adding the import clause 'import $fqname' + |or by setting the compiler option -language:$feature. + |See the Scala docs for value $fqname for a discussion + |why the feature $req be explicitly enabled.""" } } diff --git a/src/dotty/tools/dotc/transform/CheckReentrant.scala b/src/dotty/tools/dotc/transform/CheckReentrant.scala index 7e0e368b577c..c9eefb22f75e 100644 --- a/src/dotty/tools/dotc/transform/CheckReentrant.scala +++ b/src/dotty/tools/dotc/transform/CheckReentrant.scala @@ -74,7 +74,7 @@ class CheckReentrant extends MiniPhaseTransform { thisTransformer => if (sym.is(Mutable)) { ctx.error( i"""possible data race involving globally reachable ${sym.showLocated}: ${sym.info} - | use -Ylog:checkReentrant+ to find out more about why the variable is reachable.""".stripMargin) + | use -Ylog:checkReentrant+ to find out more about why the variable is reachable.""") shared += sym } else if (!sym.is(Method) || sym.is(Accessor | ParamAccessor)) { scanning(sym) { diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 8d61cef42173..62a21198d0d7 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -215,19 +215,19 @@ object ExtensionMethods { val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists) val matching = candidates filter (c => FullParameterization.memberSignature(c.info) == imeth.signature) assert(matching.nonEmpty, - sm"""|no extension method found for: - | - | $imeth:${imeth.info.show} with signature ${imeth.signature} - | - | Candidates: - | - | ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")} - | - | Candidates (signatures normalized): - | - | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + FullParameterization.memberSignature(c.info)).mkString("\n")} - | - | Eligible Names: ${extensionNames(imeth).mkString(",")}""") + i"""no extension method found for: + | + | $imeth:${imeth.info.show} with signature ${imeth.signature} + | + | Candidates: + | + | ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")} + | + | Candidates (signatures normalized): + | + | ${candidates.map(c => c.name + ":" + c.info.signature + ":" + FullParameterization.memberSignature(c.info)).mkString("\n")} + | + | Eligible Names: ${extensionNames(imeth).mkString(",")}""") matching.head.asTerm } } diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 92d638be9913..839189948b05 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -1169,9 +1169,8 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans private def translatedAlts(alts: List[Tree]) = alts map (alt => rebindTo(alt).translate()) private def noStep() = step()() - private def unsupportedPatternMsg = sm""" - |unsupported pattern: ${tree.show} / $this (this is a scalac bug.) - |""".trim + private def unsupportedPatternMsg = + i"unsupported pattern: ${tree.show} / $this (this is a scalac bug.)" // example check: List[Int] <:< ::[Int] private def extractorStep(): TranslationStep = { diff --git a/src/dotty/tools/dotc/transform/Pickler.scala b/src/dotty/tools/dotc/transform/Pickler.scala index e8d6d03bfd9c..4bcc90a41a0f 100644 --- a/src/dotty/tools/dotc/transform/Pickler.scala +++ b/src/dotty/tools/dotc/transform/Pickler.scala @@ -89,8 +89,8 @@ class Pickler extends Phase { if (previous != unpickled) { output("before-pickling.txt", previous) output("after-pickling.txt", unpickled) - ctx.error(s"""pickling difference for ${cls.fullName} in ${cls.sourceFile}, for details: + ctx.error(i"""pickling difference for ${cls.fullName} in ${cls.sourceFile}, for details: | - | diff before-pickling.txt after-pickling.txt""".stripMargin) + | diff before-pickling.txt after-pickling.txt""") } } diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index f0a514e8cd84..c8f41b7fab60 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -777,9 +777,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => maximizeType(unapplyArgType) match { case Some(tvar) => def msg = - d"""There is no best instantiation of pattern type $unapplyArgType - |that makes it a subtype of selector type $selType. - |Non-variant type variable ${tvar.origin} cannot be uniquely instantiated.""".stripMargin + ex"""There is no best instantiation of pattern type $unapplyArgType + |that makes it a subtype of selector type $selType. + |Non-variant type variable ${tvar.origin} cannot be uniquely instantiated.""" if (fromScala2x) { // We can't issue an error here, because in Scala 2, ::[B] is invariant // whereas List[+T] is covariant. According to the strict rule, a pattern @@ -801,7 +801,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => unapp.println("Neither sub nor super") unapp.println(TypeComparer.explained(implicit ctx => unapplyArgType <:< selType)) errorType( - d"Pattern type $unapplyArgType is neither a subtype nor a supertype of selector type $selType", + ex"Pattern type $unapplyArgType is neither a subtype nor a supertype of selector type $selType", tree.pos) } @@ -822,7 +822,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case _ => args } if (argTypes.length != bunchedArgs.length) { - ctx.error(d"wrong number of argument patterns for $qual; expected: ($argTypes%, %)", tree.pos) + ctx.error(em"wrong number of argument patterns for $qual; expected: ($argTypes%, %)", tree.pos) argTypes = argTypes.take(args.length) ++ List.fill(argTypes.length - args.length)(WildcardType) } diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index b1cceea887c1..d77520c778cb 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -25,7 +25,7 @@ import util.common._ import transform.SymUtils._ import Decorators._ import Uniques._ -import ErrorReporting.{err, errorType, DiagnosticString} +import ErrorReporting.{err, errorType} import config.Printers._ import collection.mutable import SymDenotations.NoCompleter @@ -40,11 +40,11 @@ object Checking { def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context) = { (args, boundss).zipped.foreach { (arg, bound) => if (!bound.isHK && arg.tpe.isHK) - ctx.error(d"missing type parameter(s) for $arg", arg.pos) + ctx.error(ex"missing type parameter(s) for $arg", arg.pos) } for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate)) ctx.error( - d"Type argument ${arg.tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(arg.tpe, bound)}", + ex"Type argument ${arg.tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(arg.tpe, bound)}", arg.pos) } @@ -65,7 +65,7 @@ object Checking { tycon match { case tycon: TypeLambda => ctx.errorOrMigrationWarning( - d"unreducible application of higher-kinded type $tycon to wildcard arguments", + ex"unreducible application of higher-kinded type $tycon to wildcard arguments", pos) case _ => checkWildcardHKApply(tp.superType, pos) @@ -117,14 +117,14 @@ object Checking { case tref: TypeRef => val cls = tref.symbol if (cls.is(AbstractOrTrait)) - ctx.error(d"$cls is abstract; cannot be instantiated", pos) + ctx.error(em"$cls is abstract; cannot be instantiated", pos) if (!cls.is(Module)) { // Create a synthetic singleton type instance, and check whether // it conforms to the self type of the class as seen from that instance. val stp = SkolemType(tp) val selfType = tref.givenSelfType.asSeenFrom(stp, cls) if (selfType.exists && !(stp <:< selfType)) - ctx.error(d"$tp does not conform to its self type $selfType; cannot be instantiated") + ctx.error(ex"$tp does not conform to its self type $selfType; cannot be instantiated") } case _ => } @@ -133,7 +133,7 @@ object Checking { def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { val rstatus = realizability(tp) if (rstatus ne Realizable) { - def msg = d"$tp is not a legal path\n since it${rstatus.msg}" + def msg = em"$tp is not a legal path\n since it${rstatus.msg}" if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) } } @@ -378,7 +378,7 @@ object Checking { var tp1 = if (tp.symbol.is(Private) && !accessBoundary(sym).isContainedIn(tp.symbol.owner)) { - errors = (d"non-private $sym refers to private ${tp.symbol}\n in its type signature ${sym.info}", + errors = (em"non-private $sym refers to private ${tp.symbol}\n in its type signature ${sym.info}", pos) :: errors tp } @@ -422,20 +422,20 @@ trait Checking { val sym = tree.tpe.termSymbol // The check is avoided inside Java compilation units because it always fails // on the singleton type Module.type. - if ((sym is Package) || ((sym is JavaModule) && !ctx.compilationUnit.isJava)) ctx.error(d"$sym is not a value", tree.pos) + if ((sym is Package) || ((sym is JavaModule) && !ctx.compilationUnit.isJava)) ctx.error(em"$sym is not a value", tree.pos) } tree } /** Check that type `tp` is stable. */ def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = - if (!tp.isStable) ctx.error(d"$tp is not stable", pos) + if (!tp.isStable) ctx.error(ex"$tp is not stable", pos) /** Check that all type members of `tp` have realizable bounds */ def checkRealizableBounds(tp: Type, pos: Position)(implicit ctx: Context): Unit = { val rstatus = boundsRealizability(tp) if (rstatus ne Realizable) - ctx.error(i"$tp cannot be instantiated since it${rstatus.msg}", pos) + ctx.error(ex"$tp cannot be instantiated since it${rstatus.msg}", pos) } /** Check that `tp` is a class type. @@ -447,11 +447,11 @@ trait Checking { def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp.underlyingClassRef(refinementOK = false) match { case tref: TypeRef => - if (traitReq && !(tref.symbol is Trait)) ctx.error(d"$tref is not a trait", pos) + if (traitReq && !(tref.symbol is Trait)) ctx.error(ex"$tref is not a trait", pos) if (stablePrefixReq && ctx.phase <= ctx.refchecksPhase) checkStable(tref.prefix, pos) tp case _ => - ctx.error(d"$tp is not a class type", pos) + ctx.error(ex"$tp is not a class type", pos) defn.ObjectType } @@ -475,7 +475,7 @@ trait Checking { case tp: RecType => tp.rebind(tp.parent) case tp @ TypeBounds(lo, hi) if !(lo <:< hi) => - ctx.error(d"no type exists between low bound $lo and high bound $hi$where", pos) + ctx.error(ex"no type exists between low bound $lo and high bound $hi$where", pos) TypeAlias(hi) case _ => tp @@ -493,17 +493,17 @@ trait Checking { typr.println(i"conflict? $decl $other") if (decl.matches(other)) { def doubleDefError(decl: Symbol, other: Symbol): Unit = { - def ofType = if (decl.isType) "" else d": ${other.info}" + def ofType = if (decl.isType) "" else em": ${other.info}" def explanation = if (!decl.isRealMethod) "" else "\n (the definitions have matching type signatures)" - ctx.error(d"$decl is already defined as $other$ofType$explanation", decl.pos) + ctx.error(em"$decl is already defined as $other$ofType$explanation", decl.pos) } if (decl is Synthetic) doubleDefError(other, decl) else doubleDefError(decl, other) } if ((decl is HasDefaultParams) && (other is HasDefaultParams)) { - ctx.error(d"two or more overloaded variants of $decl have default arguments") + ctx.error(em"two or more overloaded variants of $decl have default arguments") decl resetFlag HasDefaultParams } } @@ -524,7 +524,7 @@ trait Checking { ctx.error(i"$caller may not call constructor of $called", call.pos) else if (called.is(Trait) && !caller.mixins.contains(called)) ctx.error(i"""$called is already implemented by super${caller.superClass}, - |its constructor cannot be called again""".stripMargin, call.pos) + |its constructor cannot be called again""", call.pos) } /** Check that `tpt` does not define a higher-kinded type */ @@ -532,7 +532,7 @@ trait Checking { if (tpt.tpe.isHK && !ctx.compilationUnit.isJava) { // be more lenient with missing type params in Java, // needed to make pos/java-interop/t1196 work. - errorTree(tpt, d"missing type parameter for ${tpt.tpe}") + errorTree(tpt, ex"missing type parameter for ${tpt.tpe}") } else tpt } diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index 6f7d427cb194..ad84ff583dd2 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -9,8 +9,8 @@ import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._ import Applications._, Implicits._, Flags._ import util.Positions._ import reporting.Diagnostic -import printing.Showable -import printing.Disambiguation.disambiguated +import printing.{Showable, RefinedPrinter} +import scala.collection.mutable import java.util.regex.Matcher.quoteReplacement object ErrorReporting { @@ -38,7 +38,7 @@ object ErrorReporting { val treeSym = ctx.symOfContextTree(tree) if (treeSym.exists && treeSym.name == cycleSym.name && treeSym.owner == cycleSym.owner) { val result = if (cycleSym is Method) " result" else "" - d"overloaded or recursive $cycleSym needs$result type" + em"overloaded or recursive $cycleSym needs$result type" } else errorMsg(msg, cx.outer) case _ => @@ -48,6 +48,9 @@ object ErrorReporting { errorMsg(ex.show, ctx) } + def wrongNumberOfArgs(fntpe: Type, kind: String, expected: Int, pos: Position)(implicit ctx: Context) = + errorType(em"wrong number of ${kind}arguments for $fntpe, expected: $expected", pos) + class Errors(implicit ctx: Context) { /** An explanatory note to be added to error messages @@ -59,15 +62,15 @@ object ErrorReporting { def expectedTypeStr(tp: Type): String = tp match { case tp: PolyProto => - d"type arguments [${tp.targs}%, %] and ${expectedTypeStr(tp.resultType)}" + em"type arguments [${tp.targs}%, %] and ${expectedTypeStr(tp.resultType)}" case tp: FunProto => val result = tp.resultType match { case _: WildcardType | _: IgnoredProto => "" - case tp => d" and expected result type $tp" + case tp => em" and expected result type $tp" } - d"arguments (${tp.typedArgs.tpes}%, %)$result" + em"arguments (${tp.typedArgs.tpes}%, %)$result" case _ => - d"expected type $tp" + em"expected type $tp" } def anonymousTypeMemberStr(tpe: Type) = { @@ -76,12 +79,12 @@ object ErrorReporting { case _: PolyType | _: MethodType => "method" case _ => "value of type" } - d"$kind $tpe" + em"$kind $tpe" } def overloadedAltsStr(alts: List[SingleDenotation]) = - d"overloaded alternatives of ${denotStr(alts.head)} with types\n" + - d" ${alts map (_.info)}%\n %" + em"overloaded alternatives of ${denotStr(alts.head)} with types\n" + + em" ${alts map (_.info)}%\n %" def denotStr(denot: Denotation): String = if (denot.isOverloaded) overloadedAltsStr(denot.alternatives) @@ -97,9 +100,8 @@ object ErrorReporting { def patternConstrStr(tree: Tree): String = ??? - def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = { + def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = errorTree(tree, typeMismatchStr(normalize(tree.tpe, pt), pt) + implicitFailure.postscript) - } /** A subtype log explaining why `found` does not conform to `expected` */ def whyNoMatchStr(found: Type, expected: Type) = @@ -108,28 +110,31 @@ object ErrorReporting { else "" - def typeMismatchStr(found: Type, expected: Type) = disambiguated { implicit ctx => - def infoStr = found match { // DEBUG - case tp: TypeRef => s"with info ${tp.info} / ${tp.prefix.toString} / ${tp.prefix.dealias.toString}" - case _ => "" - } + def typeMismatchStr(found: Type, expected: Type) = { // replace constrained polyparams and their typevars by their bounds where possible - val reported = new TypeMap { + object reported extends TypeMap { + def setVariance(v: Int) = variance = v + val constraint = ctx.typerState.constraint def apply(tp: Type): Type = tp match { case tp: PolyParam => - val e = ctx.typerState.constraint.entry(tp) - if (e.exists) - if (variance > 0) e.bounds.hi - else if (variance < 0) e.bounds.lo - else tp - else tp + constraint.entry(tp) match { + case bounds: TypeBounds => + if (variance < 0) apply(constraint.fullUpperBound(tp)) + else if (variance > 0) apply(constraint.fullLowerBound(tp)) + else tp + case NoType => tp + case instType => apply(instType) + } case tp: TypeVar => apply(tp.stripTypeVar) case _ => mapOver(tp) } } - d"""type mismatch: - | found : $found - | required: ${reported(expected)}""".stripMargin + whyNoMatchStr(found, expected) + val found1 = reported(found) + reported.setVariance(-1) + val expected1 = reported(expected) + ex"""type mismatch: + | found : $found1 + | required: $expected1""" + whyNoMatchStr(found, expected) } /** Format `raw` implicitNotFound argument, replacing all @@ -139,35 +144,11 @@ object ErrorReporting { def implicitNotFoundString(raw: String, paramNames: List[String], args: List[Type]): String = { def translate(name: String): Option[String] = { val idx = paramNames.indexOf(name) - if (idx >= 0) Some(quoteReplacement(args(idx).show)) else None + if (idx >= 0) Some(quoteReplacement(ex"${args(idx)}")) else None } """\$\{\w*\}""".r.replaceSomeIn(raw, m => translate(m.matched.drop(2).init)) } } def err(implicit ctx: Context): Errors = new Errors - - /** The d string interpolator works like the i string interpolator, but marks nonsensical errors - * using `...` tags. - * Note: Instead of these tags, it would be nicer to return a data structure containing the message string - * and a boolean indicating whether the message is sensical, but then we cannot use string operations - * like concatenation, stripMargin etc on the values returned by d"...", and in the current error - * message composition methods, this is crucial. - */ - implicit class DiagnosticString(val sc: StringContext) extends AnyVal { - def d(args: Any*)(implicit ctx: Context): String = { - def isSensical(arg: Any): Boolean = arg match { - case l: Seq[_] => l.forall(isSensical(_)) - case tpe: Type if tpe.isErroneous => false - case NoType => false - case sym: Symbol if sym.isCompleted => - sym.info != ErrorType && sym.info != TypeAlias(ErrorType) && sym.info != NoType - case _ => true - } - - val s = new StringInterpolators(sc).i(args : _*) - if (args.forall(isSensical(_))) s - else Diagnostic.nonSensicalStartTag + s + Diagnostic.nonSensicalEndTag - } - } } diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 1eba64e2e401..0a3307140d2e 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -217,8 +217,8 @@ object Implicits { protected def pt: Type protected def argument: tpd.Tree protected def qualify(implicit ctx: Context) = - if (argument.isEmpty) d"match type $pt" - else d"convert from ${argument.tpe} to $pt" + if (argument.isEmpty) em"match type $pt" + else em"convert from ${argument.tpe} to $pt" /** An explanation of the cause of the failure as a string */ def explanation(implicit ctx: Context): String @@ -227,7 +227,7 @@ object Implicits { /** An ambiguous implicits failure */ class AmbiguousImplicits(alt1: TermRef, alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { def explanation(implicit ctx: Context): String = - d"both ${err.refStr(alt1)} and ${err.refStr(alt2)} $qualify" + em"both ${err.refStr(alt1)} and ${err.refStr(alt2)} $qualify" override def postscript(implicit ctx: Context) = "\nNote that implicit conversions cannot be applied because they are ambiguous;" + "\n " + explanation @@ -235,17 +235,17 @@ object Implicits { class NonMatchingImplicit(ref: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { def explanation(implicit ctx: Context): String = - d"${err.refStr(ref)} does not $qualify" + em"${err.refStr(ref)} does not $qualify" } class ShadowedImplicit(ref: TermRef, shadowing: Type, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { def explanation(implicit ctx: Context): String = - d"${err.refStr(ref)} does $qualify but is shadowed by ${err.refStr(shadowing)}" + em"${err.refStr(ref)} does $qualify but is shadowed by ${err.refStr(shadowing)}" } class DivergingImplicit(ref: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { def explanation(implicit ctx: Context): String = - d"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" + em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" } class FailedImplicit(failures: List[ExplainedSearchFailure], val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { @@ -253,7 +253,9 @@ object Implicits { if (failures.isEmpty) s" No implicit candidates were found that $qualify" else " " + (failures map (_.explanation) mkString "\n ") override def postscript(implicit ctx: Context): String = - "\nImplicit search failure summary:\n" + explanation + i""" + |Implicit search failure summary: + |$explanation""" } } @@ -456,7 +458,7 @@ trait Implicits { self: Typer => if (!arg.isEmpty) arg else { var msgFn = (where: String) => - d"no implicit argument of type $formal found for $where" + failure.postscript + em"no implicit argument of type $formal found for $where" + failure.postscript for { notFound <- formal.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot) Trees.Literal(Constant(raw: String)) <- notFound.argument(0) @@ -568,7 +570,7 @@ trait Implicits { self: Typer => // Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though. assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], - d"found: $argument: ${argument.tpe}, expected: $pt") + em"found: $argument: ${argument.tpe}, expected: $pt") /** The expected type for the searched implicit */ lazy val fullProto = implicitProto(pt, identity) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index f917c233f3e6..698f7e9a9d26 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -646,7 +646,7 @@ class Namer { typer: Typer => val pname = paramAccessor.name def illegal(how: String): Unit = { - ctx.error(d"Illegal override of public type parameter $pname in $parent$how", paramAccessor.pos) + ctx.error(em"Illegal override of public type parameter $pname in $parent$how", paramAccessor.pos) ok = false } @@ -659,7 +659,7 @@ class Namer { typer: Typer => case TypeRef(pre, name1) if name1 == pname && (pre =:= cls.thisType) => // OK, parameter is passed on directly case _ => - illegal(d".\nParameter is both redeclared and instantiated with $alias.") + illegal(em".\nParameter is both redeclared and instantiated with $alias.") } case _ => // OK, argument is not fully defined } @@ -832,7 +832,7 @@ class Namer { typer: Typer => // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") def isInline = sym.is(Final, butNot = Method | Mutable) - + // Widen rhs type and approximate `|' but keep ConstantTypes if // definition is inline (i.e. final in Scala2). def widenRhs(tp: Type): Type = tp.widenTermRefExpr match { @@ -856,7 +856,7 @@ class Namer { typer: Typer => else { if (sym is Implicit) { val resStr = if (mdef.isInstanceOf[DefDef]) "result " else "" - ctx.error(d"${resStr}type of implicit definition needs to be given explicitly", mdef.pos) + ctx.error(s"${resStr}type of implicit definition needs to be given explicitly", mdef.pos) sym.resetFlag(Implicit) } lhsType orElse WildcardType diff --git a/src/dotty/tools/dotc/typer/RefChecks.scala b/src/dotty/tools/dotc/typer/RefChecks.scala index a654bb08f734..2838866fd55c 100644 --- a/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/src/dotty/tools/dotc/typer/RefChecks.scala @@ -81,14 +81,14 @@ object RefChecks { def checkSelfConforms(other: TypeRef, category: String, relation: String) = { val otherSelf = other.givenSelfType.asSeenFrom(cls.thisType, other.classSymbol) if (otherSelf.exists && !(cinfo.selfType <:< otherSelf)) - ctx.error(d"$category: self type ${cinfo.selfType} of $cls does not conform to self type $otherSelf of $relation ${other.classSymbol}", cls.pos) + ctx.error(ex"$category: self type ${cinfo.selfType} of $cls does not conform to self type $otherSelf of $relation ${other.classSymbol}", cls.pos) } for (parent <- cinfo.classParents) { val pclazz = parent.classSymbol if (pclazz.is(Final)) - ctx.error(d"cannot extend final $pclazz", cls.pos) + ctx.error(em"cannot extend final $pclazz", cls.pos) if (pclazz.is(Sealed) && pclazz.associatedFile != cls.associatedFile) - ctx.error(d"cannot extend sealed $pclazz in different compilation unit", cls.pos) + ctx.error(em"cannot extend sealed $pclazz in different compilation unit", cls.pos) checkSelfConforms(parent, "illegal inheritance", "parent") } for (reqd <- cinfo.givenSelfType.classSymbols) diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index c2b7b7101d00..ab151fb1d0d0 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -171,13 +171,13 @@ trait TypeAssigner { case sym :: Nil => if (sym.owner == pre.typeSymbol) sym.show else sym.showLocated case _ => - d"none of the overloaded alternatives named $name" + em"none of the overloaded alternatives named $name" } val where = if (ctx.owner.exists) s" from ${ctx.owner.enclosingClass}" else "" val whyNot = new StringBuffer alts foreach (_.isAccessibleFrom(pre, superAccess, whyNot)) if (!tpe.isError) - ctx.error(d"$what cannot be accessed as a member of $pre$where.$whyNot", pos) + ctx.error(ex"$what cannot be accessed as a member of $pre$where.$whyNot", pos) ErrorType } } @@ -205,10 +205,12 @@ trait TypeAssigner { if (!site.isErroneous) { def notAMember = d"${if (name.isTypeName) "type" else "value"} $name is not a member of $site" ctx.error( - if (name == nme.CONSTRUCTOR) d"$site does not have a constructor" - else if (site.derivesFrom(defn.DynamicClass)) s"$notAMember\npossible cause: maybe a wrong Dynamic method signature?" - else notAMember, - pos) + if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" + else if (site.derivesFrom(defn.DynamicClass)) { + ex"$name is not a member of $site\n" + + "possible cause: maybe a wrong Dynamic method signature?" + } + else ex"$name is not a member of $site", pos) } ErrorType } @@ -283,7 +285,7 @@ trait TypeAssigner { case p :: Nil => p case Nil => - errorType(d"$mix does not name a parent class of $cls", tree.pos) + errorType(em"$mix does not name a parent class of $cls", tree.pos) case p :: q :: _ => errorType("ambiguous parent class qualifier", tree.pos) } @@ -302,7 +304,7 @@ trait TypeAssigner { val ownType = fn.tpe.widen match { case fntpe @ MethodType(_, ptypes) => if (sameLength(ptypes, args) || ctx.phase.prev.relaxedTyping) fntpe.instantiate(args.tpes) - else errorType(i"wrong number of parameters for ${fn.tpe}; expected: ${ptypes.length}", tree.pos) + else wrongNumberOfArgs(fn.tpe, "", ptypes.length, tree.pos) case t => errorType(i"${err.exprStr(fn)} does not take parameters", tree.pos) } @@ -348,7 +350,7 @@ trait TypeAssigner { else { val argTypes = args.tpes if (sameLength(argTypes, paramNames)|| ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) - else errorType(d"wrong number of type parameters for ${fn.tpe}; expected: ${pt.paramNames.length}", tree.pos) + else wrongNumberOfArgs(fn.tpe, "type ", pt.paramNames.length, tree.pos) } case _ => errorType(i"${err.exprStr(fn)} does not take type parameters", tree.pos) @@ -429,7 +431,7 @@ trait TypeAssigner { val ownType = if (hasNamedArg(args)) (tycon.tpe /: args)(refineNamed) else if (sameLength(tparams, args)) tycon.tpe.appliedTo(args.tpes) - else errorType(d"wrong number of type arguments for ${tycon.tpe}, should be ${tparams.length}", tree.pos) + else wrongNumberOfArgs(tycon.tpe, "type ", tparams.length, tree.pos) tree.withType(ownType) } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index e982f9aa9c43..2b690ef51164 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -134,8 +134,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit * or defined in */ def bindingString(prec: Int, whereFound: Context, qualifier: String = "") = - if (prec == wildImport || prec == namedImport) d"imported$qualifier by ${whereFound.importInfo}" - else d"defined$qualifier in ${whereFound.owner}" + if (prec == wildImport || prec == namedImport) ex"imported$qualifier by ${whereFound.importInfo}" + else ex"defined$qualifier in ${whereFound.owner}" /** Check that any previously found result from an inner context * does properly shadow the new one from an outer context. @@ -152,9 +152,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit else { if (!previous.isError && !found.isError) { error( - d"""reference to $name is ambiguous; - |it is both ${bindingString(newPrec, ctx, "")} - |and ${bindingString(prevPrec, prevCtx, " subsequently")}""".stripMargin, + ex"""reference to $name is ambiguous; + |it is both ${bindingString(newPrec, ctx, "")} + |and ${bindingString(prevPrec, prevCtx, " subsequently")}""", tree.pos) } previous @@ -167,7 +167,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def checkUnambiguous(found: Type) = { val other = namedImportRef(site, selectors.tail) if (other.exists && found.exists && (found != other)) - error(d"reference to $name is ambiguous; it is imported twice in ${ctx.tree}", + error(em"reference to $name is ambiguous; it is imported twice in ${ctx.tree}", tree.pos) found } @@ -275,7 +275,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (rawType.exists) ensureAccessible(rawType, superAccess = false, tree.pos) else { - error(d"not found: $kind$name", tree.pos) + error(em"not found: $kind$name", tree.pos) ErrorType } @@ -304,10 +304,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree match { case tree @ Select(qual, _) if !qual.tpe.isStable => val alt = typedSelect(tree, pt, Typed(qual, TypeTree(SkolemType(qual.tpe.widen)))) - typr.println(d"healed type: ${tree.tpe} --> $alt") + typr.println(i"healed type: ${tree.tpe} --> $alt") alt.asInstanceOf[T] case _ => - ctx.error(d"unsafe instantiation of type ${tree.tpe}", tree.pos) + ctx.error(ex"unsafe instantiation of type ${tree.tpe}", tree.pos) tree } else tree @@ -342,7 +342,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit convertToSelectFromType(tree.qualifier, tree.name) match { case Some(sftt) => typedSelectFromTypeTree(sftt, pt) - case _ => ctx.error(d"Could not convert $tree to a SelectFromTypeTree"); EmptyTree + case _ => ctx.error(em"Could not convert $tree to a SelectFromTypeTree"); EmptyTree } } @@ -569,7 +569,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit ensureNoLocalRefs(tree1, pt, localSyms, forcedDefined = true) } else errorTree(tree, - d"local definition of ${leaks.head.name} escapes as part of expression's type ${tree.tpe}"/*; full type: ${result.tpe.toString}"*/) + em"local definition of ${leaks.head.name} escapes as part of expression's type ${tree.tpe}"/*; full type: ${result.tpe.toString}"*/) } def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = track("typedIf") { @@ -723,7 +723,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit pt match { case SAMType(meth) if !defn.isFunctionType(pt) && mt <:< meth.info => if (!isFullyDefined(pt, ForceDegree.all)) - ctx.error(d"result type of closure is an underspecified SAM type $pt", tree.pos) + ctx.error(ex"result type of closure is an underspecified SAM type $pt", tree.pos) TypeTree(pt) case _ => if (!mt.isDependent) EmptyTree @@ -802,7 +802,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit super.transform(tree.withType(elimWildcardSym(tree.tpe))) match { case b: Bind => if (ctx.scope.lookup(b.name) == NoSymbol) ctx.enter(b.symbol) - else ctx.error(d"duplicate pattern variable: ${b.name}", b.pos) + else ctx.error(em"duplicate pattern variable: ${b.name}", b.pos) b.symbol.info = elimWildcardSym(b.symbol.info) b case t => t @@ -854,7 +854,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val proto = returnProto(owner, cx.scope) (from, proto) } - else (EmptyTree, errorType(d"$owner has return statement; needs result type", tree.pos)) + else (EmptyTree, errorType(em"$owner has return statement; needs result type", tree.pos)) } else enclMethInfo(cx.outer) } @@ -973,7 +973,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val tpt1 = typed(tree.tpt, AnyTypeConstructorProto)(ctx.retractMode(Mode.Pattern)) val tparams = tpt1.tpe.typeParams if (tparams.isEmpty) { - ctx.error(d"${tpt1.tpe} does not take type parameters", tree.pos) + ctx.error(ex"${tpt1.tpe} does not take type parameters", tree.pos) tpt1 } else { @@ -982,7 +982,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (hasNamedArg(args)) typedNamedArgs(args) else { if (args.length != tparams.length) { - ctx.error(d"wrong number of type arguments for ${tpt1.tpe}, should be ${tparams.length}", tree.pos) + wrongNumberOfArgs(tpt1.tpe, "type ", tparams.length, tree.pos) args = args.take(tparams.length) } def typedArg(arg: untpd.Tree, tparam: TypeParamInfo) = { @@ -1207,7 +1207,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case _ => // add synthetic class type val first :: _ = ensureFirstIsClass(parents.tpes) - TypeTree(checkFeasible(first, pos, d"\n in inferred parent $first")).withPos(pos) :: parents + TypeTree(checkFeasible(first, pos, em"\n in inferred parent $first")).withPos(pos) :: parents } /** If this is a real class, make sure its first parent is a @@ -1239,7 +1239,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val packageContext = if (pkg is Package) ctx.fresh.setOwner(pkg.moduleClass).setTree(tree) else { - ctx.error(d"$pkg is already defined, cannot be a package", tree.pos) + ctx.error(em"$pkg is already defined, cannot be a package", tree.pos) ctx } val stats1 = typedStats(tree.stats, pkg.moduleClass)(packageContext) @@ -1522,8 +1522,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def methodStr = err.refStr(methPart(tree).tpe) def missingArgs = errorTree(tree, - d"""missing arguments for $methodStr - |follow this method with `_' if you want to treat it as a partially applied function""".stripMargin) + em"""missing arguments for $methodStr + |follow this method with `_' if you want to treat it as a partially applied function""") def adaptOverloaded(ref: TermRef) = { val altDenots = ref.denot.alternatives @@ -1537,8 +1537,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Nil => def noMatches = errorTree(tree, - d"""none of the ${err.overloadedAltsStr(altDenots)} - |match $expectedStr""".stripMargin) + em"""none of the ${err.overloadedAltsStr(altDenots)} + |match $expectedStr""") def hasEmptyParams(denot: SingleDenotation) = denot.info.paramTypess == ListOfNil pt match { case pt: FunProto => @@ -1553,8 +1553,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) def all = if (remainingDenots.length == 2) "both" else "all" errorTree(tree, - d"""Ambiguous overload. The ${err.overloadedAltsStr(remainingDenots)} - |$all match $expectedStr""".stripMargin) + em"""Ambiguous overload. The ${err.overloadedAltsStr(remainingDenots)} + |$all match $expectedStr""") } } @@ -1581,7 +1581,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Apply(_, _) => " more" case _ => "" } - (_, _) => errorTree(tree, d"$methodStr does not take$more parameters") + (_, _) => errorTree(tree, em"$methodStr does not take$more parameters") } } @@ -1630,7 +1630,7 @@ 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(d"parameter $pname of $methodStr")) + errors += (() => msg(em"parameter $pname of $methodStr")) inferImplicitArg(formal, implicitArgError, tree.pos.endPos) } if (errors.nonEmpty) { diff --git a/tests/neg/i1430.scala b/tests/neg/i1430.scala index 870780695c90..d988ba3f2e89 100644 --- a/tests/neg/i1430.scala +++ b/tests/neg/i1430.scala @@ -1,3 +1,4 @@ +class Inv[T](x: T) object Test { val x: List[String] = List(1) // error: found Int(1), expected: String @@ -5,4 +6,29 @@ object Test { val y: List[List[String]] = List(List(1)) // error: found Int(1), expected: String val z: (List[String], List[Int]) = (List(1), List("a")) // error: found Int(1), expected: String // error: found String(a), expected: Int + + val a: Inv[String] = new Inv(new Inv(1)) // error: found Inv[T], expected: String ... where T ... + + val b: Inv[String] = new Inv(1) // error: found Int, expected: String + + abstract class C { + type T + val x: T + val s: Unit = { + type T = String + var y: T = x // error + locally { def f() = { + new { + type T = Int + val z: T = y // error + } + Nil map { _ => + type T = Int + val z: T = y // error + } + } + f() + } + } + } } From b3d4fd94f24b961715dce15d7d46fcdd32f7dd69 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 7 Aug 2016 18:12:13 +0200 Subject: [PATCH 05/11] Fix readLine in TestREPL to align with Ammonite reader Needs to read several input lines at once. Enables repl test of new error messages. --- src/dotty/tools/dotc/repl/REPL.scala | 2 +- test/test/TestREPL.scala | 14 ++++-- tests/repl/errmsgs.check | 74 ++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 tests/repl/errmsgs.check diff --git a/src/dotty/tools/dotc/repl/REPL.scala b/src/dotty/tools/dotc/repl/REPL.scala index dd20852a4f69..211e3c93196a 100644 --- a/src/dotty/tools/dotc/repl/REPL.scala +++ b/src/dotty/tools/dotc/repl/REPL.scala @@ -50,7 +50,7 @@ class REPL extends Driver { object REPL { class Config { val prompt = "scala> " - val continuationPrompt = " | " + val continuationPrompt = " " val version = ".next (pre-alpha)" def context(ctx: Context): Context = ctx diff --git a/test/test/TestREPL.scala b/test/test/TestREPL.scala index 4899607ce61b..1ba456ce2f47 100644 --- a/test/test/TestREPL.scala +++ b/test/test/TestREPL.scala @@ -24,12 +24,20 @@ class TestREPL(script: String) extends REPL { ctx.fresh.setSetting(ctx.settings.color, "never") override def input(in: Interpreter)(implicit ctx: Context) = new InteractiveReader { - val lines = script.lines + val lines = script.lines.buffered def readLine(prompt: String): String = { val line = lines.next - if (line.startsWith(prompt) || line.startsWith(continuationPrompt)) { + val buf = new StringBuilder + if (line.startsWith(prompt)) { output.println(line) - line.drop(prompt.length) + buf append line.drop(prompt.length) + while (lines.hasNext && lines.head.startsWith(continuationPrompt)) { + val continued = lines.next + output.println(continued) + buf append "\n" + buf append continued.drop(continuationPrompt.length) + } + buf.toString } else readLine(prompt) } diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check new file mode 100644 index 000000000000..d8e863a28612 --- /dev/null +++ b/tests/repl/errmsgs.check @@ -0,0 +1,74 @@ +scala> class Inv[T](x: T) +defined class Inv +scala> val x: List[String] = List(1) +:4: error: type mismatch: + found : Int(1) + required: String +val x: List[String] = List(1) + ^ +scala> val y: List[List[String]] = List(List(1)) +:4: error: type mismatch: + found : Int(1) + required: String +val y: List[List[String]] = List(List(1)) + ^ +scala> val z: (List[String], List[Int]) = (List(1), List("a")) +:4: error: type mismatch: + found : Int(1) + required: String +val z: (List[String], List[Int]) = (List(1), List("a")) + ^ +:4: error: type mismatch: + found : String("a") + required: Int +val z: (List[String], List[Int]) = (List(1), List("a")) + ^ +scala> val a: Inv[String] = new Inv(new Inv(1)) +:5: error: type mismatch: + found : Inv[T] + required: String + +where T is a type variable with constraint >: Int(1) + +val a: Inv[String] = new Inv(new Inv(1)) + ^ +scala> val b: Inv[String] = new Inv(1) +:5: error: type mismatch: + found : Int(1) + required: String +val b: Inv[String] = new Inv(1) + ^ +scala> abstract class C { + type T + val x: T + val s: Unit = { + type T = String + var y: T = x + locally { + def f() = { + type T = Int + val z: T = y + } + f() + } + } + } +:9: error: type mismatch: + found : C.this.T(C.this.x) + required: T' + +where T is a type in class C + T' is a type in the initalizer of value s which is an alias of String + + var y: T = x + ^ +:13: error: type mismatch: + found : T(y) + required: T' + +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 + + val z: T = y + ^ +scala> :quit From 4a0858fab5547c896870d269968aff9674ab2ee6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 13 Aug 2016 13:28:33 +0200 Subject: [PATCH 06/11] Catch exceptions only in i-interpolator. Normal show will propagate the excpetions. Previously, exceptions were filtered in both cases, which was redundant. Also, it's good to have a way to show things that does not mask exceptions, if only to debug problems in show itself. --- src/dotty/tools/dotc/printing/Formatting.scala | 2 +- src/dotty/tools/dotc/printing/Showable.scala | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 174d801d10e3..8d713683937c 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -28,7 +28,7 @@ object Formatting { case arg: Showable => try arg.show(ctx.addMode(Mode.FutureDefsOK)) catch { - case NonFatal(ex) => s"(missing due to $ex)" + case NonFatal(ex) => s"[cannot display due to $ex, raw string = $toString]" } case _ => arg.toString } diff --git a/src/dotty/tools/dotc/printing/Showable.scala b/src/dotty/tools/dotc/printing/Showable.scala index 37de053cb5ad..efddb26f7f13 100644 --- a/src/dotty/tools/dotc/printing/Showable.scala +++ b/src/dotty/tools/dotc/printing/Showable.scala @@ -21,11 +21,7 @@ trait Showable extends Any { def fallbackToText(printer: Printer): Text = toString /** The string representation of this showable element. */ - def show(implicit ctx: Context): String = - try toText(ctx.printer).show - catch { - case NonFatal(ex) => s"[cannot display due to $ex, raw string = $toString]" - } + def show(implicit ctx: Context): String = toText(ctx.printer).show /** The summarized string representation of this showable element. * Recursion depth is limited to some smallish value. Default is From 71070854e4385198a9895a434a2623d313ca94dc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 13 Aug 2016 13:32:17 +0200 Subject: [PATCH 07/11] Don't disambiguate aliases Don't disambiguate in situations like Predef.String vs java.lang.String where one Symbol is an alias of another with the same name. Also, fix reviewer comments wrt comments and unused defs. --- .../tools/dotc/printing/Formatting.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 8d713683937c..8921c56a332a 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -58,11 +58,11 @@ object Formatting { } } - /** The d string interpolator works like the i string interpolator, but marks nonsensical errors + /** The `em` string interpolator works like the `i` string interpolator, but marks nonsensical errors * using `...` tags. * Note: Instead of these tags, it would be nicer to return a data structure containing the message string * and a boolean indicating whether the message is sensical, but then we cannot use string operations - * like concatenation, stripMargin etc on the values returned by d"...", and in the current error + * like concatenation, stripMargin etc on the values returned by em"...", and in the current error * message composition methods, this is crucial. */ class ErrorMessageFormatter(sc: StringContext) extends StringFormatter(sc) { @@ -85,8 +85,15 @@ object Formatting { override def default(key: String) = Nil - def record(str: String, entry: Recorded): String = { - var alts = apply(str).dropWhile(entry ne _) + def record(str: String, entry: Recorded)(implicit ctx: Context): String = { + def followAlias(e1: Recorded): Recorded = e1 match { + case e1: Symbol if e1.isAliasType => + val underlying = e1.typeRef.underlyingClassRef(refinementOK = false).typeSymbol + if (underlying.name == e1.name) underlying else e1 + case _ => e1 + } + lazy val dealiased = followAlias(entry) + var alts = apply(str).dropWhile(alt => dealiased ne followAlias(alt)) if (alts.isEmpty) { alts = entry :: apply(str) update(str, alts) @@ -135,13 +142,6 @@ object Formatting { case param: PolyParam => s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}" case sym: Symbol => - val ownerStr = - if (!sym.exists) "" - else { - var owner = sym.effectiveOwner - if (owner.isLocalDummy) i" locally defined in ${owner.owner}" - else i" in $owner" - } s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}" } } From 416aca9825d14e073bb38364054fefa65ccaaa4c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 13 Aug 2016 13:33:01 +0200 Subject: [PATCH 08/11] Don't print REPL prefixes. This was already disabled when printing types. Now is also disabled when printing fully qualified names. --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 83f61c97663c..dd247ead2490 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -53,6 +53,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { nameString(if (sym is ExpandedTypeParam) name.asTypeName.unexpandedName else name) } + override def fullNameString(sym: Symbol): String = + if (isOmittablePrefix(sym.maybeOwner)) nameString(sym) + else super.fullNameString(sym) + override protected def fullNameOwner(sym: Symbol) = { val owner = super.fullNameOwner(sym) if (owner is ModuleClass) owner.sourceModule else owner From 3b710030df3603384b2434dc8b6570aadfaa74f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 13 Aug 2016 13:52:43 +0200 Subject: [PATCH 09/11] Don't print $ suffixes of module classes ... when printing using RefinedPrinter. PlainPrinter will still show them. --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index dd247ead2490..090825f8e1bf 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -554,7 +554,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => } } - super.toText(sym) + if (sym.is(ModuleClass)) + kindString(sym) ~~ (nameString(sym.name.stripModuleClassSuffix) + idString(sym)) + else + super.toText(sym) } override def kindString(sym: Symbol) = { From 273b184f77950624f6bd2a9e810d78ae390a5487 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 13 Aug 2016 14:11:05 +0200 Subject: [PATCH 10/11] Don't omit scala. from fullNameString. The previous fix was too drastic, as it would also have omitted scala, Prefef and other "unqualified owner types" from full names. We now omit only "empty prefixes", i.e. roots, anonymous classes and repl qualifiers. --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 090825f8e1bf..7e2a2893e690 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -54,7 +54,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } override def fullNameString(sym: Symbol): String = - if (isOmittablePrefix(sym.maybeOwner)) nameString(sym) + if (isEmptyPrefix(sym.maybeOwner)) nameString(sym) else super.fullNameString(sym) override protected def fullNameOwner(sym: Symbol) = { From 401b421430592b213220b74ae1c394ba7f8bd479 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 16 Aug 2016 17:38:03 +0200 Subject: [PATCH 11/11] Fix merge conflict --- src/dotty/tools/dotc/typer/TypeAssigner.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index ab151fb1d0d0..22339d470236 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -203,14 +203,14 @@ trait TypeAssigner { TryDynamicCallType } else { if (!site.isErroneous) { - def notAMember = d"${if (name.isTypeName) "type" else "value"} $name is not a member of $site" + def kind = if (name.isTypeName) "type" else "value" + def addendum = + if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" + else "" ctx.error( if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" - else if (site.derivesFrom(defn.DynamicClass)) { - ex"$name is not a member of $site\n" + - "possible cause: maybe a wrong Dynamic method signature?" - } - else ex"$name is not a member of $site", pos) + else ex"$kind $name is not a member of $site$addendum", + pos) } ErrorType }