Skip to content

Commit 8f85da5

Browse files
committed
Fix Formatting.Show for types X | Null.
Once we saw what got changed we got priority errors like this: ```scala [warn] -- Warning: /Users/odersky/workspace/dotty/compiler/src/dotty/tools/dotc/reporting/messages.scala:2145:15 [warn] 2145 | | ${Magenta("case a @ C()")} => 2 [warn] | ^ [warn] |Change in given search preference for dotty.tools.dotc.printing.Formatting.ShownDef.Show[Product | Null] between alternatives (dotty.tools.dotc.printing.Formatting.ShownDef.Show.given_Show_Product : [warn] | dotty.tools.dotc.printing.Formatting.ShownDef.Show[Product]) and (dotty.tools.dotc.printing.Formatting.ShownDef.Show.oldShowNull : [warn] | [X] [warn] | (implicit evidence$1: dotty.tools.dotc.printing.Formatting.ShownDef.Show[X]) [warn] | : dotty.tools.dotc.printing.Formatting.ShownDef.Show.oldShowNull[X] [warn] |) [warn] |Previous choice : the first alternative [warn] |New choice from Scala 3.6: the second alternative ``` I believe there is some funky thing going on with nulls. Probably unsafe nulls somewhere. Anyway what seems to happen is that before Product's given was selected for an argument of `Product | Null`, which seems to indicate that the two types were seen as equivalent. Anyway, if we compare `Show[Product]` with `Show[X | Null]` it seems that Show[Product] won the type score and lost the owner score, so it was a draw. But then the second criterion kicked in which rules that alternatives without any implicit arguments take priority over alternatives with explicit arguments. The given for Product was unconditional but the given for X | Null has a context bound. So Show[Product] won. But once we switch to more general, we have that Show[Product] loses both type and owner score, so Show[X | Null] won. And that seems to have caused the classcast exceptions. I am not sure why, the logic for Shown was too hard for me to follow. The fix is to move Show[X | Null] to have lowest priority.
1 parent 1ea5485 commit 8f85da5

File tree

2 files changed

+10
-11
lines changed

2 files changed

+10
-11
lines changed

compiler/src/dotty/tools/dotc/printing/Formatting.scala

+9-11
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package dotty.tools
22
package dotc
33
package printing
44

5-
import scala.language.unsafeNulls
6-
75
import scala.collection.mutable
86

97
import core.*
@@ -50,9 +48,14 @@ object Formatting {
5048

5149
/** The base implementation, passing the argument to StringFormatter which will try to `.show` it. */
5250
object ShowAny extends Show[Any]:
53-
def show(x: Any): Shown = x
51+
def show(x: Any): Shown =
52+
if x == null then "null" else x // defensive action in case `showNull` does not trigger because of unsafeNulls
53+
54+
class ShowImplicit4:
55+
given showNull[X: Show]: Show[X | Null] with
56+
def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x.nn))
5457

55-
class ShowImplicits3:
58+
class ShowImplicits3 extends ShowImplicit4:
5659
given Show[Product] = ShowAny
5760

5861
class ShowImplicits2 extends ShowImplicits3:
@@ -77,15 +80,10 @@ object Formatting {
7780
given [K: Show, V: Show]: Show[Map[K, V]] with
7881
def show(x: Map[K, V]) =
7982
CtxShow(x.map((k, v) => s"${toStr(k)} => ${toStr(v)}"))
80-
end given
8183

8284
given [H: Show, T <: Tuple: Show]: Show[H *: T] with
8385
def show(x: H *: T) =
8486
CtxShow(toStr(x.head) *: toShown(x.tail).asInstanceOf[Tuple])
85-
end given
86-
87-
given [X: Show]: Show[X | Null] with
88-
def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x.nn))
8987

9088
given Show[FlagSet] with
9189
def show(x: FlagSet) = x.flagsString
@@ -148,8 +146,8 @@ object Formatting {
148146
private def treatArg(arg: Shown, suffix: String)(using Context): (String, String) = arg.runCtxShow match {
149147
case arg: Seq[?] if suffix.indexOf('%') == 0 && suffix.indexOf('%', 1) != -1 =>
150148
val end = suffix.indexOf('%', 1)
151-
val sep = StringContext.processEscapes(suffix.substring(1, end))
152-
(arg.mkString(sep), suffix.substring(end + 1))
149+
val sep = StringContext.processEscapes(suffix.substring(1, end).nn)
150+
(arg.mkString(sep), suffix.substring(end + 1).nn)
153151
case arg: Seq[?] =>
154152
(arg.map(showArg).mkString("[", ", ", "]"), suffix)
155153
case arg =>

compiler/test/dotty/tools/dotc/StringFormatterTest.scala

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class StringFormatterTest extends AbstractStringFormatterTest:
2323
@Test def flagsTup = check("(<static>,final)", i"${(JavaStatic, Final)}")
2424
@Test def seqOfTup2 = check("(final,given), (private,lazy)", i"${Seq((Final, Given), (Private, Lazy))}%, %")
2525
@Test def seqOfTup3 = check("(Foo,given, (right is approximated))", i"${Seq((Foo, Given, TypeComparer.ApproxState.None.addHigh))}%, %")
26+
@Test def tupleNull = check("(1,null)", i"${(1, null: String | Null)}")
2627

2728
class StorePrinter extends Printer:
2829
var string: String = "<never set>"

0 commit comments

Comments
 (0)