From 045466ae5015e2c75045a2ffbbd30915f8ca63b7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 11 Feb 2022 14:36:51 +0000 Subject: [PATCH 1/6] Switch our string interpolators to use Show/Shown As it was our string interpolators were taking Any values and then trying to pattern match back their classes to try to show them nicely. This inevitably fails for things like the opaque type FlagSet, which interpolates as an indecipherable long number. Now, instead, they take "Shown" arguments, for which there is an implicit conversion in scope, given there is a Show instance for value. I captured some desired results in some new unit test cases. In the process a small handful of bugs were discovered, the only particularly bad one was consuming a Iterator when the "transforms" printer was enabled (accessorDefs), followed by an unintentional eta-expansion of a method with a defaulted argument (showSummary). I also lifted out the Showable and exception fallback function as an extension method, so I could use it more broadly. The use of WrappedResult and its `result` in `showing` was also impacted, because the new expected Shown type was driving `result`'s context lookup. Fortunately I was able to continue to use WrappedResult and `result` as defined by handling this API change inside `showing` alone. I wasn't, however, able to find a solution to the impact the new Shown expected type was having on the `stripModuleClassSuffix` extension method, sadly. JSExportsGen is interpolating a private type, so rather than open its access or giving it a Show instance I changed the string interpolation. SyntaxFormatter was no longer used, since the "hl" interpolator was dropped. --- .../tools/backend/sjs/JSExportsGen.scala | 2 +- .../dotty/tools/dotc/core/Decorators.scala | 44 +++++-- .../dotty/tools/dotc/core/TypeComparer.scala | 19 +-- .../dotty/tools/dotc/parsing/Parsers.scala | 2 +- .../tools/dotc/printing/Formatting.scala | 116 ++++++++++++------ .../dotty/tools/dotc/reporting/messages.scala | 2 +- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- .../tools/dotc/transform/AccessProxies.scala | 2 +- .../dotc/transform/DropOuterAccessors.scala | 2 +- .../dotty/tools/dotc/transform/Erasure.scala | 6 +- .../src/dotty/tools/dotc/typer/Checking.scala | 2 +- .../tools/dotc/printing/PrinterTests.scala | 34 ++++- 12 files changed, 160 insertions(+), 73 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala index bee783af258f..ec74d2c5e896 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.show} / ${overallKind.show})", info.pos) } } diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 08452782fd66..9b974650db5a 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,30 @@ 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): T = { + // either the use of `$result` was driven by the expected type of `Shown` + // which lead 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 show(using Context)(using z: Show[T] = null): String = x match + case _ if z != null => z.show(x).toString + 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) + extension [T](x: T) def assertingErrorsReported(using Context): T = { assert(ctx.reporter.errorsReported) @@ -269,19 +287,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/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 16926ecb72b8..d187e33b7190 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.show}""" 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..0bbbefb1aba0 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -1,47 +1,103 @@ -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 to 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 ShowImplicits1: + 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 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[Atoms] = ShowAny + given Show[Highlight] = ShowAny + given Show[HighlightBuffer] = ShowAny + given Show[CompilationUnit] = ShowAny + given Show[Mode] = ShowAny + given Show[Phases.Phase] = ShowAny + given Show[Signature] = ShowAny + given Show[TyperState] = ShowAny + given Show[config.ScalaVersion] = ShowAny + given Show[io.AbstractFile] = ShowAny + given Show[parsing.Scanners.IndentWidth] = ShowAny + given Show[parsing.Scanners.Scanner] = ShowAny + given Show[util.SourceFile] = ShowAny + given Show[util.Spans.Span] = ShowAny + given Show[dotty.tools.tasty.TastyBuffer.Addr] = ShowAny + given Show[tasty.TreeUnpickler#OwnerTree] = ShowAny + given Show[interactive.Completion] = ShowAny + given Show[transform.sjs.JSSymUtils.JSName] = ShowAny + given Show[org.scalajs.ir.Position] = 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.show - 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 +107,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 +133,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..be30f5adc044 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1759,7 +1759,7 @@ 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 = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix.show}" 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..f294863c25b4 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.show}` 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) } From 382af9417d795c3bcfcaa245b3927606878fe859 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 17 Feb 2022 13:40:01 +0000 Subject: [PATCH 2/6] Define a low-priority instance of Show for Product This removes the need for some of the default instances. More importantly, this reduces the burden and boilerplate that would be needed when introducing a new case class, without defining a Show instance for it or extending Showable. --- .../dotty/tools/dotc/printing/Formatting.scala | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 0bbbefb1aba0..bc1ad6fb163e 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -33,7 +33,10 @@ object Formatting { object ShowAny extends Show[Any]: def show(x: Any): Shown = x - class ShowImplicits1: + class ShowImplicits2: + given Show[Product] = ShowAny + + class ShowImplicits1 extends ShowImplicits2: given Show[ImplicitRef] = ShowAny given Show[Names.Designator] = ShowAny given Show[util.SrcPos] = ShowAny @@ -62,25 +65,15 @@ object Formatting { given Show[Class[?]] = ShowAny given Show[Exception] = ShowAny given Show[StringBuffer] = ShowAny - given Show[Atoms] = ShowAny - given Show[Highlight] = ShowAny - given Show[HighlightBuffer] = ShowAny given Show[CompilationUnit] = ShowAny - given Show[Mode] = ShowAny given Show[Phases.Phase] = ShowAny - given Show[Signature] = ShowAny given Show[TyperState] = ShowAny given Show[config.ScalaVersion] = ShowAny given Show[io.AbstractFile] = ShowAny - given Show[parsing.Scanners.IndentWidth] = ShowAny given Show[parsing.Scanners.Scanner] = ShowAny given Show[util.SourceFile] = ShowAny given Show[util.Spans.Span] = ShowAny - given Show[dotty.tools.tasty.TastyBuffer.Addr] = ShowAny given Show[tasty.TreeUnpickler#OwnerTree] = ShowAny - given Show[interactive.Completion] = ShowAny - given Show[transform.sjs.JSSymUtils.JSName] = ShowAny - given Show[org.scalajs.ir.Position] = ShowAny end Show end ShownDef export ShownDef.{ Show, Shown } From 0ce7c52fc582fecaee988334a3e630e911ddd8b1 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 24 Feb 2022 12:04:38 +0000 Subject: [PATCH 3/6] Drop Show from Any#show extension method I added this at the last minute as a fallback, but I can't remember the use case exactly, and it muddies the waters by mixing concerns. Also other typo fixes and a code tweak. --- compiler/src/dotty/tools/dotc/core/Decorators.scala | 5 ++--- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 2 +- compiler/src/dotty/tools/dotc/reporting/messages.scala | 4 +++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 9b974650db5a..27a4ba075654 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -251,7 +251,7 @@ object Decorators { op: WrappedResult[U] ?=> String, printer: config.Printers.Printer = config.Printers.default)(using c: Conversion[T, U] = null): T = { // either the use of `$result` was driven by the expected type of `Shown` - // which lead to the summoning of `Conversion[T, Shown]` (which we'll invoke) + // 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))) @@ -259,8 +259,7 @@ object Decorators { } /** Instead of `toString` call `show` on `Showable` values, falling back to `toString` if an exception is raised. */ - def show(using Context)(using z: Show[T] = null): String = x match - case _ if z != null => z.show(x).toString + def show(using Context): String = x match case x: Showable => try x.show catch diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index bc1ad6fb163e..b93798c37bf0 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -18,7 +18,7 @@ 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 to a Context to call `Showable#show`. We could + * 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 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index be30f5adc044..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.show}" + 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} From 480eb03b33f7fff7ca0f0e8c450e209e4bd96e06 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 25 Feb 2022 18:27:12 +0000 Subject: [PATCH 4/6] Rename to Any#tryToShow as it's not Show-based It was also being used over TypeComparer.ApproxState's show extension method (for unclear reasons), so let's avoid name confusion too. --- compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala | 2 +- compiler/src/dotty/tools/dotc/core/Decorators.scala | 2 +- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 2 +- compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala index ec74d2c5e896..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.show} / ${overallKind.show})", + 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 27a4ba075654..05286001cd8b 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -259,7 +259,7 @@ object Decorators { } /** Instead of `toString` call `show` on `Showable` values, falling back to `toString` if an exception is raised. */ - def show(using Context): String = x match + def tryToShow(using Context): String = x match case x: Showable => try x.show catch diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d187e33b7190..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.show}""" + |${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 b93798c37bf0..1c8213325ae6 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -88,7 +88,7 @@ object Formatting { * against accidentally treating an interpolated value as a margin. */ class StringFormatter(protected val sc: StringContext) { - protected def showArg(arg: Any)(using Context): String = arg.show + protected def showArg(arg: Any)(using Context): String = arg.tryToShow private def treatArg(arg: Shown, suffix: String)(using Context): (Any, String) = arg match { case arg: Seq[?] if suffix.nonEmpty && suffix.head == '%' => diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index f294863c25b4..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.show}` of unknown class ${elem.getClass}", elem, tree) + cannotHash(what = i"`${elem.tryToShow}` of unknown class ${elem.getClass}", elem, tree) h end iteratorHash From 579e14bf0e0b33893473e6eee38ec6b84f2966d2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 23 Mar 2022 08:52:26 +0000 Subject: [PATCH 5/6] Fix: use the value not the eta-expanded method! Another win for Show/Shown over Any. --- compiler/src/dotty/tools/dotc/interactive/Completion.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) } From e5ed401834e0bbdc6337d5724ebb634d35333c71 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 31 Mar 2022 15:00:07 +0200 Subject: [PATCH 6/6] Fixes now that explicit nulls are on --- compiler/src/dotty/tools/dotc/core/Decorators.scala | 4 ++-- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 05286001cd8b..ce488ef23885 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -249,7 +249,7 @@ object Decorators { extension [T](x: T) def showing[U]( op: WrappedResult[U] ?=> String, - printer: config.Printers.Printer = config.Printers.default)(using c: Conversion[T, U] = null): T = { + 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 @@ -268,7 +268,7 @@ object Decorators { 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) + case _ => String.valueOf(x).nn extension [T](x: T) def assertingErrorsReported(using Context): T = { diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 1c8213325ae6..0cef7fd3e833 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -50,6 +50,9 @@ object Formatting { 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