diff --git a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala index bee783af258f..c9dbdaf68812 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala @@ -119,7 +119,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { if (kind != overallKind) { bad = true report.error( - em"export overload conflicts with export of $firstSym: they are of different types ($kind / $overallKind)", + em"export overload conflicts with export of $firstSym: they are of different types (${kind.tryToShow} / ${overallKind.tryToShow})", info.pos) } } diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 08452782fd66..ce488ef23885 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -2,12 +2,13 @@ package dotty.tools package dotc package core -import annotation.tailrec -import Symbols._ -import Contexts._, Names._, Phases._, printing.Texts._ -import collection.mutable.ListBuffer -import dotty.tools.dotc.transform.MegaPhase -import printing.Formatting._ +import scala.annotation.tailrec +import scala.collection.mutable.ListBuffer +import scala.util.control.NonFatal + +import Contexts._, Names._, Phases._, Symbols._ +import printing.{ Printer, Showable }, printing.Formatting._, printing.Texts._ +import transform.MegaPhase /** This object provides useful implicit decorators for types defined elsewhere */ object Decorators { @@ -246,13 +247,29 @@ object Decorators { } extension [T](x: T) - def showing( - op: WrappedResult[T] ?=> String, - printer: config.Printers.Printer = config.Printers.default): T = { - printer.println(op(using WrappedResult(x))) + def showing[U]( + op: WrappedResult[U] ?=> String, + printer: config.Printers.Printer = config.Printers.default)(using c: Conversion[T, U] | Null = null): T = { + // either the use of `$result` was driven by the expected type of `Shown` + // which led to the summoning of `Conversion[T, Shown]` (which we'll invoke) + // or no such conversion was found so we'll consume the result as it is instead + val obj = if c == null then x.asInstanceOf[U] else c(x) + printer.println(op(using WrappedResult(obj))) x } + /** Instead of `toString` call `show` on `Showable` values, falling back to `toString` if an exception is raised. */ + def tryToShow(using Context): String = x match + case x: Showable => + try x.show + catch + case ex: CyclicReference => "... (caught cyclic reference) ..." + case NonFatal(ex) + if !ctx.mode.is(Mode.PrintShowExceptions) && !ctx.settings.YshowPrintErrors.value => + val msg = ex match { case te: TypeError => te.toMessage case _ => ex.getMessage } + s"[cannot display due to $msg, raw string = $x]" + case _ => String.valueOf(x).nn + extension [T](x: T) def assertingErrorsReported(using Context): T = { assert(ctx.reporter.errorsReported) @@ -269,19 +286,19 @@ object Decorators { extension (sc: StringContext) /** General purpose string formatting */ - def i(args: Any*)(using Context): String = + def i(args: Shown*)(using Context): String = new StringFormatter(sc).assemble(args) /** 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*)(using Context): String = + def em(args: Shown*)(using Context): String = new ErrorMessageFormatter(sc).assemble(args) /** Formatting with added explanations: Like `em`, but add explanations to * give more info about type variables and to disambiguate where needed. */ - def ex(args: Any*)(using Context): String = + def ex(args: Shown*)(using Context): String = explained(em(args: _*)) extension [T <: AnyRef](arr: Array[T]) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 67cfb12d67be..19c5f3dda4ae 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2713,15 +2713,16 @@ object TypeComparer { */ val Fresh: Repr = 4 - extension (approx: Repr) - def low: Boolean = (approx & LoApprox) != 0 - def high: Boolean = (approx & HiApprox) != 0 - def addLow: Repr = approx | LoApprox - def addHigh: Repr = approx | HiApprox - def show: String = - val lo = if low then " (left is approximated)" else "" - val hi = if high then " (right is approximated)" else "" - lo ++ hi + object Repr: + extension (approx: Repr) + def low: Boolean = (approx & LoApprox) != 0 + def high: Boolean = (approx & HiApprox) != 0 + def addLow: Repr = approx | LoApprox + def addHigh: Repr = approx | HiApprox + def show: String = + val lo = if low then " (left is approximated)" else "" + val hi = if high then " (right is approximated)" else "" + lo ++ hi end ApproxState type ApproxState = ApproxState.Repr diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 15628c3b83e8..c97653725a92 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -161,7 +161,7 @@ object Completion { | prefix = ${completer.prefix}, | term = ${completer.mode.is(Mode.Term)}, | type = ${completer.mode.is(Mode.Type)} - | results = $backtickCompletions%, %""") + | results = $backtickedCompletions%, %""") (offset, backtickedCompletions) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 16926ecb72b8..fab132a36035 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -606,7 +606,7 @@ object Parsers { if startIndentWidth <= nextIndentWidth then i"""Line is indented too far to the right, or a `{` is missing before: | - |$t""" + |${t.tryToShow}""" else in.spaceTabMismatchMsg(startIndentWidth, nextIndentWidth), in.next.offset diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 50e18d5587b9..0cef7fd3e833 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -1,47 +1,99 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package printing import scala.language.unsafeNulls +import scala.collection.mutable + import core._ import Texts._, Types._, Flags._, Symbols._, Contexts._ -import collection.mutable import Decorators._ -import scala.util.control.NonFatal import reporting.Message import util.DiffUtil import Highlighting._ object Formatting { + object ShownDef: + /** Represents a value that has been "shown" and can be consumed by StringFormatter. + * Not just a string because it may be a Seq that StringFormatter will intersperse with the trailing separator. + * Also, it's not a `String | Seq[String]` because then we'd need a Context to call `Showable#show`. We could + * make Context a requirement for a Show instance but then we'd have lots of instances instead of just one ShowAny + * instance. We could also try to make `Show#show` require the Context, but then that breaks the Conversion. */ + opaque type Shown = Any + object Shown: + given [A: Show]: Conversion[A, Shown] = Show[A].show(_) + + sealed abstract class Show[-T]: + /** Show a value T by returning a "shown" result. */ + def show(x: T): Shown + + /** The base implementation, passing the argument to StringFormatter which will try to `.show` it. */ + object ShowAny extends Show[Any]: + def show(x: Any): Shown = x + + class ShowImplicits2: + given Show[Product] = ShowAny + + class ShowImplicits1 extends ShowImplicits2: + given Show[ImplicitRef] = ShowAny + given Show[Names.Designator] = ShowAny + given Show[util.SrcPos] = ShowAny + + object Show extends ShowImplicits1: + inline def apply[A](using inline z: Show[A]): Show[A] = z + + given [X: Show]: Show[Seq[X]] with + def show(x: Seq[X]) = x.map(Show[X].show) + + given [A: Show, B: Show]: Show[(A, B)] with + def show(x: (A, B)) = (Show[A].show(x._1), Show[B].show(x._2)) + + given [X: Show]: Show[X | Null] with + def show(x: X | Null) = if x == null then "null" else Show[X].show(x.nn) + + given Show[FlagSet] with + def show(x: FlagSet) = x.flagsString + + given Show[TypeComparer.ApproxState] with + def show(x: TypeComparer.ApproxState) = TypeComparer.ApproxState.Repr.show(x) + + given Show[Showable] = ShowAny + given Show[Shown] = ShowAny + given Show[Int] = ShowAny + given Show[Char] = ShowAny + given Show[Boolean] = ShowAny + given Show[String] = ShowAny + given Show[Class[?]] = ShowAny + given Show[Exception] = ShowAny + given Show[StringBuffer] = ShowAny + given Show[CompilationUnit] = ShowAny + given Show[Phases.Phase] = ShowAny + given Show[TyperState] = ShowAny + given Show[config.ScalaVersion] = ShowAny + given Show[io.AbstractFile] = ShowAny + given Show[parsing.Scanners.Scanner] = ShowAny + given Show[util.SourceFile] = ShowAny + given Show[util.Spans.Span] = ShowAny + given Show[tasty.TreeUnpickler#OwnerTree] = ShowAny + end Show + end ShownDef + export ShownDef.{ Show, Shown } + /** 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, + * 1. Invokes the `show` extension method on the interpolated arguments. + * 2. 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 + * 3. Safe handling of multi-line margins. Left margins are stripped on 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)(using Context): String = arg match { - case arg: Showable => - try arg.show - catch { - case ex: CyclicReference => "... (caught cyclic reference) ..." - case NonFatal(ex) - if !ctx.mode.is(Mode.PrintShowExceptions) && - !ctx.settings.YshowPrintErrors.value => - val msg = ex match - case te: TypeError => te.toMessage - case _ => ex.getMessage - s"[cannot display due to $msg, raw string = ${arg.toString}]" - } - case _ => String.valueOf(arg) - } + protected def showArg(arg: Any)(using Context): String = arg.tryToShow - private def treatArg(arg: Any, suffix: String)(using Context): (Any, String) = arg match { + private def treatArg(arg: Shown, suffix: String)(using Context): (Any, String) = arg match { case arg: Seq[?] if suffix.nonEmpty && suffix.head == '%' => val (rawsep, rest) = suffix.tail.span(_ != '%') val sep = StringContext.processEscapes(rawsep) @@ -51,7 +103,7 @@ object Formatting { (showArg(arg), suffix) } - def assemble(args: Seq[Any])(using Context): String = { + def assemble(args: Seq[Shown])(using 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)) @@ -77,18 +129,6 @@ object Formatting { override protected def showArg(arg: Any)(using Context): String = wrapNonSensical(arg, super.showArg(arg)(using errorMessageCtx)) - class SyntaxFormatter(sc: StringContext) extends StringFormatter(sc) { - override protected def showArg(arg: Any)(using Context): String = - arg match { - case hl: Highlight => - hl.show - case hb: HighlightBuffer => - hb.toString - case _ => - SyntaxHighlighting.highlight(super.showArg(arg)) - } - } - private def wrapNonSensical(arg: Any, str: String)(using Context): String = { import Message._ def isSensical(arg: Any): Boolean = arg match { diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f8fec07e9f9f..18e2d234452d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1759,7 +1759,9 @@ import transform.SymUtils._ class ClassAndCompanionNameClash(cls: Symbol, other: Symbol)(using Context) extends NamingMsg(ClassAndCompanionNameClashID) { - def msg = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix}" + def msg = + val name = cls.name.stripModuleClassSuffix + em"Name clash: both ${cls.owner} and its companion object defines $name" def explain = em"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name: | - ${cls.owner} defines ${cls} diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 1120b279b2e2..c22172b3e10f 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -776,7 +776,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { case n: Name => h = nameHash(n, h) case elem => - cannotHash(what = i"`$elem` of unknown class ${elem.getClass}", elem, tree) + cannotHash(what = i"`${elem.tryToShow}` of unknown class ${elem.getClass}", elem, tree) h end iteratorHash diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index 15c6d30c3b19..a59ce4d79a48 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -58,7 +58,7 @@ abstract class AccessProxies { /** Add all needed accessors to the `body` of class `cls` */ def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] = { - val accDefs = accessorDefs(cls) + val accDefs = accessorDefs(cls).toList transforms.println(i"add accessors for $cls: $accDefs%, %") if (accDefs.isEmpty) body else body ++ accDefs } diff --git a/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala b/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala index ca6a056fd6d5..a363ccaeb0d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala @@ -80,7 +80,7 @@ class DropOuterAccessors extends MiniPhase with IdentityDenotTransformer: cpy.Block(rhs)(inits.filterNot(dropOuterInit), expr) }) assert(droppedParamAccessors.isEmpty, - i"""Failed to eliminate: $droppedParamAccessors + i"""Failed to eliminate: ${droppedParamAccessors.toList} when dropping outer accessors for ${ctx.owner} with $impl""") cpy.Template(impl)(constr = constr1, body = body1) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index ab0c8d449d00..123f2a7aaeb8 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -266,7 +266,7 @@ object Erasure { def constant(tree: Tree, const: Tree)(using Context): Tree = (if (isPureExpr(tree)) const else Block(tree :: Nil, const)).withSpan(tree.span) - final def box(tree: Tree, target: => String = "")(using Context): Tree = trace(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") { + final def box(tree: Tree, target: => String = "")(using Context): Tree = trace(i"boxing ${tree.showSummary()}: ${tree.tpe} into $target") { tree.tpe.widen match { case ErasedValueType(tycon, _) => New(tycon, cast(tree, underlyingOfValueClass(tycon.symbol.asClass)) :: Nil) // todo: use adaptToType? @@ -286,7 +286,7 @@ object Erasure { } } - def unbox(tree: Tree, pt: Type)(using Context): Tree = trace(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") { + def unbox(tree: Tree, pt: Type)(using Context): Tree = trace(i"unboxing ${tree.showSummary()}: ${tree.tpe} as a $pt") { pt match { case ErasedValueType(tycon, underlying) => def unboxedTree(t: Tree) = @@ -1031,7 +1031,7 @@ object Erasure { } override def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree = - trace(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) { + trace(i"adapting ${tree.showSummary()}: ${tree.tpe} to $pt", show = true) { if ctx.phase != erasurePhase && ctx.phase != erasurePhase.next then // this can happen when reading annotations loaded during erasure, // since these are loaded at phase typer. diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 0525ce805d2e..f41eba593791 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1197,7 +1197,7 @@ trait Checking { case _: TypeTree => case _ => if tree.tpe.typeParams.nonEmpty then - val what = if tree.symbol.exists then tree.symbol else i"type $tree" + val what = if tree.symbol.exists then tree.symbol.show else i"type $tree" report.error(em"$what takes type parameters", tree.srcPos) /** Check that we are in an inline context (inside an inline method or in inline code) */ diff --git a/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala b/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala index e6b17fade86f..23054cdf0dfb 100644 --- a/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala +++ b/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala @@ -1,9 +1,11 @@ -package dotty.tools.dotc.printing +package dotty.tools +package dotc +package printing -import dotty.tools.DottyTest -import dotty.tools.dotc.ast.{Trees,tpd} -import dotty.tools.dotc.core.Names._ -import dotty.tools.dotc.core.Symbols._ +import ast.{ Trees, tpd } +import core.Names._ +import core.Symbols._ +import core.Decorators._ import dotty.tools.dotc.core.Contexts.Context import org.junit.Assert.assertEquals @@ -49,4 +51,26 @@ class PrinterTests extends DottyTest { assertEquals("Int & (Boolean | String)", bar.tpt.show) } } + + @Test def string: Unit = assertEquals("foo", i"${"foo"}") + + import core.Flags._ + @Test def flagsSingle: Unit = assertEquals("final", i"$Final") + @Test def flagsSeq: Unit = assertEquals(", final", i"${Seq(JavaStatic, Final)}%, %") + @Test def flagsTuple: Unit = assertEquals("(,final)", i"${(JavaStatic, Final)}") + @Test def flagsSeqOfTuple: Unit = assertEquals("(final,given), (private,lazy)", i"${Seq((Final, Given), (Private, Lazy))}%, %") + + class StorePrinter extends config.Printers.Printer: + var string: String = "" + override def println(msg: => String) = string = msg + + @Test def testShowing: Unit = + val store = StorePrinter() + (JavaStatic | Final).showing(i"flags=$result", store) + assertEquals("flags=final ", store.string) + + @Test def TestShowingWithOriginalType: Unit = + val store = StorePrinter() + (JavaStatic | Final).showing(i"flags=${if result.is(Private) then result &~ Private else result | Private}", store) + assertEquals("flags=private final ", store.string) }