Skip to content

Commit ca2d87a

Browse files
committed
Union types: deduplication + Nothing type members absorption
Added the following simplification rules to OrType: - deduplication without subtype comparisons by the means of object equality/hash code - absorption of Nothing types (T | Nothing == T)
1 parent d876acf commit ca2d87a

File tree

7 files changed

+122
-33
lines changed

7 files changed

+122
-33
lines changed

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,18 @@ object TypeOps:
156156
tp.derivedAlias(simplify(tp.alias, theMap))
157157
case AndType(l, r) if !ctx.mode.is(Mode.Type) =>
158158
simplify(l, theMap) & simplify(r, theMap)
159-
case tp @ OrType(l, r)
160-
if !ctx.mode.is(Mode.Type)
161-
&& (tp.isSoft || l.isBottomType || r.isBottomType) =>
162-
// Normalize A | Null and Null | A to A even if the union is hard (i.e.
163-
// explicitly declared), but not if -Yexplicit-nulls is set. The reason is
164-
// that in this case the normal asSeenFrom machinery is not prepared to deal
165-
// with Nulls (which have no base classes). Under -Yexplicit-nulls, we take
166-
// corrective steps, so no widening is wanted.
167-
simplify(l, theMap) | simplify(r, theMap)
159+
case tp @ OrType(l, r) =>
160+
if !ctx.mode.is(Mode.Type) && (tp.isSoft || l.isBottomType || r.isBottomType) then
161+
// Normalize A | Null and Null | A to A even if the union is hard (i.e.
162+
// explicitly declared), but not if -Yexplicit-nulls is set. The reason is
163+
// that in this case the normal asSeenFrom machinery is not prepared to deal
164+
// with Nulls (which have no base classes). Under -Yexplicit-nulls, we take
165+
// corrective steps, so no widening is wanted.
166+
simplify(l, theMap) | simplify(r, theMap)
167+
else if r.isNothingType || (l eq r) then l
168+
else if l.isOrType || r.isOrType then tp.deduplicatedAbsorbingNothingTypes
169+
else if l.isNothingType then r
170+
else mapOver
168171
case tp @ CapturingType(parent, refs) =>
169172
if !ctx.mode.is(Mode.Type)
170173
&& refs.subCaptures(parent.captureSet, frozen = true).isOK

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,9 @@ object Types {
442442
final def containsWildcardTypes(using Context) =
443443
existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false)
444444

445+
/** Is this a union type? */
446+
final def isOrType: Boolean = this.isInstanceOf[OrType]
447+
445448
// ----- Higher-order combinators -----------------------------------
446449

447450
/** Returns true if there is a part of this type that satisfies predicate `p`.
@@ -3448,6 +3451,50 @@ object Types {
34483451
case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) && isSoft == that.isSoft
34493452
case _ => false
34503453
}
3454+
3455+
/** Returns the set of non-union (leaf) types composing this union tree with Nothing types
3456+
* absorbed by other types, if present.
3457+
* For example:
3458+
* {{{A | B | C | B | (A & (B | C)) | Nothing}}}
3459+
* returns
3460+
* {{{Set(A, B, C, (A & (B | C)))}}}
3461+
*/
3462+
private def gatherTreeUniqueMembersAbsorbingNothingTypes(using Context): Set[Type] = {
3463+
(tp1, tp2) match
3464+
case (l: OrType, r: OrType) =>
3465+
l.gatherTreeUniqueMembersAbsorbingNothingTypes ++ r.gatherTreeUniqueMembersAbsorbingNothingTypes
3466+
case (l: OrType, r) =>
3467+
if r.isNothingType
3468+
then l.gatherTreeUniqueMembersAbsorbingNothingTypes
3469+
else l.gatherTreeUniqueMembersAbsorbingNothingTypes + r
3470+
case (l, r: OrType) =>
3471+
if l.isNothingType
3472+
then r.gatherTreeUniqueMembersAbsorbingNothingTypes
3473+
else r.gatherTreeUniqueMembersAbsorbingNothingTypes + l
3474+
case (l, r) =>
3475+
if r.isNothingType then Set(l)
3476+
else if l.isNothingType then Set(r)
3477+
else Set(l, r)
3478+
}
3479+
3480+
/** Returns an equivalent union tree without repeated members and with Nothing types absorbed
3481+
* by other types, if present. Weaker than LUB.
3482+
*/
3483+
def deduplicatedAbsorbingNothingTypes(using Context): Type = {
3484+
val uniqueTreeMembers = this.gatherTreeUniqueMembersAbsorbingNothingTypes
3485+
val factorCount = orFactorCount(isSoft)
3486+
3487+
uniqueTreeMembers.size match {
3488+
case 1 =>
3489+
uniqueTreeMembers.head
3490+
case uniqueMembersCount if uniqueMembersCount < factorCount =>
3491+
val uniqueMembers = uniqueTreeMembers.iterator
3492+
val startingUnion = OrType(uniqueMembers.next(), uniqueMembers.next(), isSoft)
3493+
uniqueMembers.foldLeft(startingUnion)(OrType(_, _, isSoft))
3494+
case _ =>
3495+
this
3496+
}
3497+
}
34513498
}
34523499

34533500
final class CachedOrType(tp1: Type, tp2: Type, override val isSoft: Boolean) extends OrType(tp1, tp2)

tests/neg/i11694.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ def test1 = {
55
def f21: (Int => Int) | Null = x => x + 1
66
def f22: Null | (Int => Int) = x => x + 1
77

8-
def f31: (Int => Int) | (Int => Int) = x => x + 1 // error
9-
def f32: (Int => Int) | (Int => Int) | Unit = x => x + 1 // error
8+
def f31: (Int => Int) | (Int => Int) = x => x + 1
9+
def f32: (Int => Int) | (Int => Int) | Unit = x => x + 1
1010

1111
def f41: (Int => Int) & (Int => Int) = x => x + 1
1212
def f42: (Int => Int) & (Int => Int) & Any = x => x + 1

tests/neg/i14823a.check

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
-- Error: tests/neg/i14823a.scala:16:48 --------------------------------------------------------------------------------
2-
16 |val foo = summon[Mirror.Of[Box[Int] | Box[Int]]] // error
3-
| ^
4-
|No given instance of type deriving.Mirror.Of[Box[Int] | Box[Int]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[Box[Int] | Box[Int]]:
5-
| * type `Box[Int] | Box[Int]` is not a generic product because its subpart `Box[Int] | Box[Int]` is a top-level union type.
6-
| * type `Box[Int] | Box[Int]` is not a generic sum because its subpart `Box[Int] | Box[Int]` is a top-level union type.
7-
-- Error: tests/neg/i14823a.scala:17:58 --------------------------------------------------------------------------------
8-
17 |val bar = summon[MirrorK1.Of[[X] =>> Box[Int] | Box[Int]]] // error
9-
| ^
10-
|No given instance of type MirrorK1.Of[[X] =>> Box[Int] | Box[Int]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type MirrorK1.Of[[X] =>> Box[Int] | Box[Int]]:
11-
| * type `[A] =>> Box[Int] | Box[Int]` is not a generic product because its subpart `Box[Int] | Box[Int]` is a top-level union type.
12-
| * type `[A] =>> Box[Int] | Box[Int]` is not a generic sum because its subpart `Box[Int] | Box[Int]` is a top-level union type.
13-
-- Error: tests/neg/i14823a.scala:18:63 --------------------------------------------------------------------------------
14-
18 |def baz = summon[deriving.Mirror.Of[Foo[String] | Foo[String]]] // error
15-
| ^
16-
|No given instance of type deriving.Mirror.Of[Foo[String] | Foo[String]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[Foo[String] | Foo[String]]:
17-
| * type `Foo[String] | Foo[String]` is not a generic product because its subpart `Foo[String] | Foo[String]` is a top-level union type.
18-
| * type `Foo[String] | Foo[String]` is not a generic sum because its subpart `Foo[String] | Foo[String]` is a top-level union type.
1+
-- Error: tests/neg/i14823a.scala:16:51 --------------------------------------------------------------------------------
2+
16 |val foo = summon[Mirror.Of[Box[Int] | Box[String]]] // error
3+
| ^
4+
|No given instance of type deriving.Mirror.Of[Box[Int] | Box[String]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[Box[Int] | Box[String]]:
5+
| * type `Box[Int] | Box[String]` is not a generic product because its subpart `Box[Int] | Box[String]` is a top-level union type.
6+
| * type `Box[Int] | Box[String]` is not a generic sum because its subpart `Box[Int] | Box[String]` is a top-level union type.
7+
-- Error: tests/neg/i14823a.scala:17:61 --------------------------------------------------------------------------------
8+
17 |val bar = summon[MirrorK1.Of[[X] =>> Box[Int] | Box[String]]] // error
9+
| ^
10+
|No given instance of type MirrorK1.Of[[X] =>> Box[Int] | Box[String]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type MirrorK1.Of[[X] =>> Box[Int] | Box[String]]:
11+
| * type `[A] =>> Box[Int] | Box[String]` is not a generic product because its subpart `Box[Int] | Box[String]` is a top-level union type.
12+
| * type `[A] =>> Box[Int] | Box[String]` is not a generic sum because its subpart `Box[Int] | Box[String]` is a top-level union type.
13+
-- Error: tests/neg/i14823a.scala:18:60 --------------------------------------------------------------------------------
14+
18 |def baz = summon[deriving.Mirror.Of[Foo[Int] | Foo[String]]] // error
15+
| ^
16+
|No given instance of type deriving.Mirror.Of[Foo[Int] | Foo[String]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[Foo[Int] | Foo[String]]:
17+
| * type `Foo[Int] | Foo[String]` is not a generic product because its subpart `Foo[Int] | Foo[String]` is a top-level union type.
18+
| * type `Foo[Int] | Foo[String]` is not a generic sum because its subpart `Foo[Int] | Foo[String]` is a top-level union type.
1919
-- Error: tests/neg/i14823a.scala:20:66 --------------------------------------------------------------------------------
2020
20 |def qux = summon[deriving.Mirror.Of[Option[Int] | Option[String]]] // error
2121
| ^
22-
|No given instance of type deriving.Mirror.Of[Option[Int] | Option[String]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[Option[Int] | Option[String]]:
22+
|No given instance of type deriving.Mirror.Of[Option[Int] | Option[String]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[Option[Int] | Option[String]]:
2323
| * type `Option[Int] | Option[String]` is not a generic product because its subpart `Option[Int] | Option[String]` is a top-level union type.
2424
| * type `Option[Int] | Option[String]` is not a generic sum because its subpart `Option[Int] | Option[String]` is a top-level union type.

tests/neg/i14823a.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ object Foo {
1313
case class A[T]() extends Foo[T]
1414
}
1515

16-
val foo = summon[Mirror.Of[Box[Int] | Box[Int]]] // error
17-
val bar = summon[MirrorK1.Of[[X] =>> Box[Int] | Box[Int]]] // error
18-
def baz = summon[deriving.Mirror.Of[Foo[String] | Foo[String]]] // error
16+
val foo = summon[Mirror.Of[Box[Int] | Box[String]]] // error
17+
val bar = summon[MirrorK1.Of[[X] =>> Box[Int] | Box[String]]] // error
18+
def baz = summon[deriving.Mirror.Of[Foo[Int] | Foo[String]]] // error
1919

2020
def qux = summon[deriving.Mirror.Of[Option[Int] | Option[String]]] // error

tests/printing/i10693.check

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[[syntax trees at end of typer]] // tests/printing/i10693.scala
2+
package <empty> {
3+
final lazy module val i10693$package: i10693$package = new i10693$package()
4+
final module class i10693$package() extends Object() {
5+
this: i10693$package.type =>
6+
def test[A >: Nothing <: Any, B >: Nothing <: Any](a: A, b: B): A | B = a
7+
val v0: String | Int = test[String, Int]("string", 1)
8+
val v1: Int | String = test[Int, String](1, "string")
9+
val v2: String | Int = test[(String | Int), (Int | String)](v0, v1)
10+
val v3: Int | String = test[(Int | String), (String | Int)](v1, v0)
11+
val v4: String | Int = test[(String | Int), (Int | String)](v2, v3)
12+
val v5: Int | String = test[(Int | String), (String | Int)](v3, v2)
13+
val v6: String | Int = test[(String | Int), (Int | String)](v4, v5)
14+
val t0: List[Int] = Tuple1.apply[Int](1).toList
15+
val t1: List[Int] = Tuple2.apply[Int, Int](1, 2).toList
16+
val t2: List[Int] = Tuple3.apply[Int, Int, Int](1, 2, 3).toList
17+
val t3: List[Int | String] =
18+
Tuple3.apply[String, Int, Int]("A", 2, 3).toList
19+
val t4: List[String | Int] =
20+
Tuple3.apply[Int, String, String](1, "B", "C").toList
21+
}
22+
}
23+

tests/printing/i10693.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// minimized code
2+
def test[A, B](a: A, b: B): A | B = a
3+
val v0 = test("string", 1)
4+
val v1 = test(1, "string")
5+
val v2 = test(v0, v1)
6+
val v3 = test(v1, v0)
7+
val v4 = test(v2, v3)
8+
val v5 = test(v3, v2)
9+
val v6 = test(v4, v5)
10+
11+
// issue comments section examples
12+
val t0 = Tuple1(1).toList
13+
val t1 = (1, 2).toList
14+
val t2 = (1, 2, 3).toList
15+
val t3 = ("A", 2, 3).toList
16+
val t4 = (1, "B", "C").toList

0 commit comments

Comments
 (0)