Skip to content

Commit 63f379f

Browse files
committed
Closes lampepfl#10693: Deduplicate union types
OrType simplification enhancements: - member deduplication without subtype comparisons by the means of object equality/hash code - extended to member of type AppliedType which could be left unsimplified when in a union (Tuple.Union)
1 parent bf808b3 commit 63f379f

File tree

8 files changed

+127
-29
lines changed

8 files changed

+127
-29
lines changed

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,33 @@ 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) =>
159+
case tp @ OrType(l, r) =>
160+
if
161+
!ctx.mode.is(Mode.Type) && (tp.isSoft || l.isBottomType || r.isBottomType)
162+
then
162163
// Normalize A | Null and Null | A to A even if the union is hard (i.e.
163164
// explicitly declared), but not if -Yexplicit-nulls is set. The reason is
164165
// that in this case the normal asSeenFrom machinery is not prepared to deal
165166
// with Nulls (which have no base classes). Under -Yexplicit-nulls, we take
166167
// corrective steps, so no widening is wanted.
167-
simplify(l, theMap) | simplify(r, theMap)
168+
simplify(l, theMap) | simplify(r, theMap)
169+
else if
170+
l.isAppliedType || r.isAppliedType
171+
then
172+
// The simplification of some applied types could be a union or value type.
173+
// For example Tuple.Union.
174+
val lSimpl = simplify(l, theMap)
175+
val rSimpl = simplify(r, theMap)
176+
177+
if (lSimpl.isBottomType || rSimpl.isBottomType)
178+
lSimpl | rSimpl
179+
else if ((l eq lSimpl) && (r eq rSimpl))
180+
// Some applied types like Function1[A, B] are already simplified.
181+
tp.deduplicated
182+
else
183+
OrType(lSimpl, rSimpl, tp.isSoft).deduplicated
184+
else
185+
tp.deduplicated
168186
case tp @ CapturingType(parent, refs) =>
169187
if !ctx.mode.is(Mode.Type)
170188
&& 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,12 @@ 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+
448+
/** Is this a type application? */
449+
final def isAppliedType: Boolean = this.isInstanceOf[AppliedType]
450+
445451
// ----- Higher-order combinators -----------------------------------
446452

447453
/** Returns true if there is a part of this type that satisfies predicate `p`.
@@ -3454,6 +3460,47 @@ object Types {
34543460
case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) && isSoft == that.isSoft
34553461
case _ => false
34563462
}
3463+
3464+
/** Returns the set of non-union (leaf) types composing this union tree.
3465+
* For example:
3466+
* {{{A | B | C | A | (A & (B | C))}}}
3467+
* returns
3468+
* {{{Set(A, B, C, (A & (B | C)))}}}
3469+
*/
3470+
private def unionTreeUniqueMembers: Set[Type] = {
3471+
(tp1, tp2) match
3472+
case (l: OrType, r: OrType) => l.unionTreeUniqueMembers ++ r.unionTreeUniqueMembers
3473+
case (l: OrType, r) => l.unionTreeUniqueMembers + r
3474+
case (l, r: OrType) => r.unionTreeUniqueMembers + l
3475+
case (l, r) => Set(l, r)
3476+
}
3477+
3478+
private def deduplicateTree(using Context): Type = {
3479+
val unionTreeUniqueMembers = this.unionTreeUniqueMembers
3480+
val factorCount = orFactorCount(isSoft)
3481+
3482+
unionTreeUniqueMembers.size match {
3483+
case 1 =>
3484+
unionTreeUniqueMembers.head
3485+
case uniqueMembersCount if uniqueMembersCount < factorCount =>
3486+
val uniqueMembers = unionTreeUniqueMembers.iterator
3487+
val startingUnion = OrType(uniqueMembers.next(), uniqueMembers.next(), isSoft)
3488+
uniqueMembers.foldLeft(startingUnion)(OrType(_, _, isSoft))
3489+
case _ =>
3490+
this
3491+
}
3492+
3493+
}
3494+
3495+
/** Returns an equivalent union tree without duplicate members. Weaker than LUB. */
3496+
def deduplicated(using Context): Type = {
3497+
if (tp1 eq tp2)
3498+
tp1
3499+
else if (tp1.isOrType || tp2.isOrType)
3500+
deduplicateTree
3501+
else
3502+
this
3503+
}
34573504
}
34583505

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

compiler/test-resources/repl/10886

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ scala> type Channel = "A" | "B"
44
scala> type SelChannel[C <: Tuple] = C match { case x *: xs => x | SelChannel[xs] case _ => Nothing }
55

66
scala> lazy val a: SelChannel[("A", "B", "C")] = "A"
7-
lazy val a: "A" | ("B" | ("C" | Nothing))
7+
lazy val a: "A" | ("B" | "C")
88

99
scala>:type a
10-
("A" : String) | (("B" : String) | (("C" : String) | Nothing))
10+
("A" : String) | (("B" : String) | ("C" : String))

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: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
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
| ^

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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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 tuple1: List[Int] = Tuple1.apply[Int](1).toList
15+
val tuple2: List[String] = Tuple2.apply[String, String]("a", "b").toList
16+
val tuple3: List[Int] = Tuple3.apply[Int, Int, Int](1, 2, 3).toList
17+
}
18+
}
19+

tests/printing/i10693.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// actual issue
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+
// Tuple.Union related comments
12+
val tuple1 = Tuple1(1).toList
13+
val tuple2 = ("a", "b").toList
14+
val tuple3 = (1, 2, 3).toList

0 commit comments

Comments
 (0)