Skip to content

Commit f19de96

Browse files
committed
Cut the Gordian Knot: Don't widen unions to transparent
The idea is that some unions usually make more sense than others. For instance, if `Apply` and `Ident` are case classes that extend `Tree`, it makes sense to widen `Apply | Ident` to `Tree`. But it makes less sense to widen `String | Int` to `Matchable`. Making sense means: (1) Matches our intuitive understanding, and (2) choosing not to widen would usually not cause errors. To explain (2): In the `Tree` case it might well be that we define an implicits on `Inv[Tree]` for invariant class `Inv`, and then we would not find the implicit for `Inv[Apply | Ident]`. But it's much less likely that we are looking for an implicit of type `Inv[Any]`. This commit does two things: - add logic not to widen a union if the result is a product of only transparent traits or classes. - treat `Any`, `AnyVal`, `Object`, and `Matchable` as transparent.
1 parent 275cfa8 commit f19de96

File tree

8 files changed

+41
-15
lines changed

8 files changed

+41
-15
lines changed

compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,10 @@ trait ConstraintHandling {
550550
inst
551551
end approximation
552552

553+
private def isTransparent(tp: Type)(using Context): Boolean = tp match
554+
case AndType(tp1, tp2) => isTransparent(tp1) && isTransparent(tp2)
555+
case _ => tp.typeSymbol.isTransparentTrait && !tp.isLambdaSub
556+
553557
/** If `tp` is an intersection such that some operands are transparent trait instances
554558
* and others are not, replace as many transparent trait instances as possible with Any
555559
* as long as the result is still a subtype of `bound`. But fall back to the
@@ -563,7 +567,7 @@ trait ConstraintHandling {
563567

564568
def dropOneTransparentTrait(tp: Type): Type =
565569
val tpd = tp.dealias
566-
if tpd.typeSymbol.isTransparentTrait && !tpd.isLambdaSub && !kept.contains(tpd) then
570+
if isTransparent(tpd) && !kept.contains(tpd) then
567571
dropped = tpd :: dropped
568572
defn.AnyType
569573
else tpd match
@@ -648,7 +652,16 @@ trait ConstraintHandling {
648652

649653
val wideInst =
650654
if isSingleton(bound) then inst
651-
else dropTransparentTraits(widenIrreducible(widenOr(widenSingle(inst))), bound)
655+
else
656+
val widenedFromSingle = widenSingle(inst)
657+
val widenedFromUnion = widenOr(widenedFromSingle)
658+
val widened =
659+
if (widenedFromUnion ne widenedFromSingle) && isTransparent(widenedFromUnion) then
660+
widenedFromSingle
661+
else
662+
dropTransparentTraits(widenedFromUnion, bound)
663+
widenIrreducible(widened)
664+
652665
wideInst match
653666
case wideInst: TypeRef if wideInst.symbol.is(Module) =>
654667
TermRef(wideInst.prefix, wideInst.symbol.sourceModule)

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1839,7 +1839,11 @@ class Definitions {
18391839
requiredClass("scala.collection.generic.IsMap"),
18401840
requiredClass("scala.collection.generic.IsSeq"),
18411841
requiredClass("scala.collection.generic.Subtractable"),
1842-
requiredClass("scala.collection.immutable.StrictOptimizedSeqOps")
1842+
requiredClass("scala.collection.immutable.StrictOptimizedSeqOps"),
1843+
AnyClass,
1844+
AnyValClass,
1845+
ObjectClass,
1846+
MatchableClass
18431847
)
18441848

18451849
// ----- primitive value class machinery ------------------------------------------

tests/neg/harmonize.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ object Test {
7979
val a4 = ArrayBuffer(1.0f, 1L)
8080
val b4: ArrayBuffer[Double] = a4 // error: no widening
8181
val a5 = ArrayBuffer(1.0f, 1L, f())
82-
val b5: ArrayBuffer[AnyVal] = a5
82+
val b5: ArrayBuffer[Float | Long | Int] = a5
8383
val a6 = ArrayBuffer(1.0f, 1234567890)
84-
val b6: ArrayBuffer[AnyVal] = a6
84+
val b6: ArrayBuffer[Float | Int] = a6
8585

8686
def totalDuration(results: List[Long], cond: Boolean): Long =
8787
results.map(r => if (cond) r else 0).sum

tests/neg/supertraits.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ class C extends A, S
66
val x = if ??? then B() else C()
77
val x1: S = x // error
88

9-
case object a
10-
case object b
9+
class Top
10+
case object a extends Top
11+
case object b extends Top
1112
val y = if ??? then a else b
1213
val y1: Product = y // error
1314
val y2: Serializable = y // error
1415

15-
enum Color {
16+
enum Color extends Top {
1617
case Red, Green, Blue
1718
}
1819

19-
enum Nucleobase {
20+
enum Nucleobase extends Top {
2021
case A, C, G, T
2122
}
2223

2324
val z = if ??? then Color.Red else Nucleobase.G
24-
val z1: reflect.Enum = z // error: Found: (z : Object) Required: reflect.Enum
25+
val z1: reflect.Enum = z // error: Found: (z : Top) Required: reflect.Enum

tests/neg/union.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ object Test {
1111
}
1212

1313
object O {
14-
class A
15-
class B
14+
class Top
15+
class A extends Top
16+
class B extends Top
1617
def f[T](x: T, y: T): T = x
1718

1819
val x: A = f(new A { }, new A)

tests/pos/unions.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Test:
2+
3+
def test =
4+
val x = if ??? then "" else 1
5+
val _: String | Int = x
6+
7+

tests/run/weak-conformance.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ object Test extends App {
2626
locally {
2727
def f(): Int = b + 1
2828
val x1 = ArrayBuffer(b, 33, 5.5) ; x1: ArrayBuffer[Double] // b is an inline val
29-
val x2 = ArrayBuffer(f(), 33, 5.5) ; x2: ArrayBuffer[AnyVal] // f() is not a constant
29+
val x2 = ArrayBuffer(f(), 33, 5.5) ; x2: ArrayBuffer[Int | Double] // f() is not a constant
3030
val x3 = ArrayBuffer(5, 11L) ; x3: ArrayBuffer[Long]
31-
val x4 = ArrayBuffer(5, 11L, 5.5) ; x4: ArrayBuffer[AnyVal] // Long and Double found
31+
val x4 = ArrayBuffer(5, 11L, 5.5) ; x4: ArrayBuffer[Int | Long | Double] // Long and Double found
3232
val x5 = ArrayBuffer(1.0f, 2) ; x5: ArrayBuffer[Float]
33-
val x6 = ArrayBuffer(1.0f, 1234567890); x6: ArrayBuffer[AnyVal] // loss of precision
33+
val x6 = ArrayBuffer(1.0f, 1234567890); x6: ArrayBuffer[Float | Int] // loss of precision
3434
val x7 = ArrayBuffer(b, 33, 'a') ; x7: ArrayBuffer[Char]
3535
val x8 = ArrayBuffer(5.toByte, 11) ; x8: ArrayBuffer[Byte]
3636

0 commit comments

Comments
 (0)