diff options
Diffstat (limited to 'src/dotty/tools')
30 files changed, 394 insertions, 349 deletions
diff --git a/src/dotty/tools/dotc/ast/NavigateAST.scala b/src/dotty/tools/dotc/ast/NavigateAST.scala index 782866bad..2b11f81f3 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 2fe32b4d3..19ede3cec 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 <options> <source files>" diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index e10523753..436b035dc 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 7d108a459..387e7e466 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 80daa9681..4f01c43cf 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 9d96f2b15..47ec541ab 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 a62a88dfb..3f801bda5 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 d46ea6b0f..38b2c8bd6 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 d6ada7244..091999412 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 3e04e9c77..1bfd6eaee 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 a6d381693..4ea98f7c3 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 8ea4cecde..0d91e8cd6 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 aa3fae2de..000000000 --- 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 000000000..174d801d1 --- /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 `<nonsensical>...</nonsensical>` 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 acf4514ea..a92095d9b 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 360874522..14b63012e 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 ce063f06a..83f61c976 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 bddfd2f68..b3d173a42 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 7e0e368b5..c9eefb22f 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 8d61cef42..62a21198d 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 92d638be9..839189948 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 e8d6d03bf..4bcc90a41 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 f0a514e8c..c8f41b7fa 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 b1cceea88..d77520c77 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 6f7d427cb..ad84ff583 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 `<nonsensical>...</nonsensical>` 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 1eba64e2e..0a3307140 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 f917c233f..698f7e9a9 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 a654bb08f..2838866fd 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 c2b7b7101..ab151fb1d 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 e982f9aa9..2b690ef51 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 <symbol> */ 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) { |