Skip to content

Commit 6fc2cd9

Browse files
committed
Improve atomic type intersection
Handle: - nested and-types - refined types - type aliasing Add line end trimming to patmat tests to work around an issue where lines end with spaces.
1 parent bfa5aea commit 6fc2cd9

File tree

8 files changed

+211
-15
lines changed

8 files changed

+211
-15
lines changed

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,70 @@ trait SpaceLogic {
246246
}
247247
}
248248

249+
object SpaceEngine {
250+
private sealed trait Implementability
251+
private object ClassOrTrait extends Implementability
252+
private case class SubclassOf(classSyms: List[Symbol]) extends Implementability
253+
private object Unimplementable extends Implementability
254+
}
255+
249256
/** Scala implementation of space logic */
250257
class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
258+
import SpaceEngine._
251259
import tpd._
252260

253-
override def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type) = {
254-
def isOpen(tp: Type) =
255-
!tp.classSymbol.is(Sealed | Final) && !tp.termSymbol.is(Module)
261+
/** Checks if `tp` can be implemented by a new class/trait.
262+
*
263+
* - doesn't handle member collisions (assumes they don't happen)
264+
* - expects that neither Any nor Object reach it
265+
* (this is currently true due to both isSubType and and/or type simplification)
266+
*/
267+
private def implementability(tp: Type): Implementability = tp.dealias match {
268+
case AndType(tp1, tp2) =>
269+
(implementability(tp1), implementability(tp2)) match {
270+
case (Unimplementable, _) | (_, Unimplementable) => Unimplementable
271+
case (SubclassOf(classSyms1), SubclassOf(classSyms2)) =>
272+
(for {
273+
sym1 <- classSyms1
274+
sym2 <- classSyms2
275+
result <-
276+
if (sym1 isSubClass sym2) List(sym1)
277+
else if (sym2 isSubClass sym1) List(sym2)
278+
else Nil
279+
} yield result) match {
280+
case Nil => Unimplementable
281+
case lst => SubclassOf(lst)
282+
}
283+
case (ClassOrTrait, ClassOrTrait) => ClassOrTrait
284+
case (SubclassOf(clss), _) => SubclassOf(clss)
285+
case (_, SubclassOf(clss)) => SubclassOf(clss)
286+
}
287+
case OrType(tp1, tp2) =>
288+
(implementability(tp1), implementability(tp2)) match {
289+
case (ClassOrTrait, _) | (_, ClassOrTrait) => ClassOrTrait
290+
case (SubclassOf(classSyms1), SubclassOf(classSyms2)) =>
291+
SubclassOf(classSyms1 ::: classSyms2)
292+
case (SubclassOf(classSyms), _) => SubclassOf(classSyms)
293+
case (_, SubclassOf(classSyms)) => SubclassOf(classSyms)
294+
case _ => Unimplementable
295+
}
296+
case tp: RefinedType => implementability(tp.parent)
297+
case other =>
298+
val classSym = other.classSymbol
299+
val isOpen =
300+
!classSym.is(Sealed | Final) && !other.termSymbol.is(Module)
301+
302+
if (isOpen) {
303+
if (classSym is Trait) ClassOrTrait else SubclassOf(List(classSym))
304+
} else Unimplementable
305+
}
256306

257-
if ((tp1.classSymbol.is(Trait) || tp2.classSymbol.is(Trait))
258-
&& isOpen(tp1) && isOpen(tp2))
259-
Typ(AndType(tp1, tp2), true)
260-
else Empty
307+
override def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type) = {
308+
val and = AndType(tp1, tp2)
309+
implementability(and) match {
310+
case Unimplementable => Empty
311+
case _ => Typ(and, true)
312+
}
261313
}
262314

263315
/** Return the space that represents the pattern `pat`

compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ class PatmatExhaustivityTest {
2828
e.printStackTrace()
2929
}
3030

31-
val actual = stringBuffer.toString.trim
31+
val actual = stringBuffer.toString.trim.replaceAll("\\s+\n", "\n")
3232
val checkFilePath = file.getAbsolutePath.stripSuffix(".scala") + ".check"
3333
val checkContent =
3434
if (new File(checkFilePath).exists)
35-
fromFile(checkFilePath).getLines.mkString("\n").trim
35+
fromFile(checkFilePath).getLines.map(_.replaceAll("\\s+$", "")).mkString("\n").trim
3636
else ""
3737

3838
(file, checkContent, actual)
@@ -55,11 +55,11 @@ class PatmatExhaustivityTest {
5555
e.printStackTrace()
5656
}
5757

58-
val actual = stringBuffer.toString.trim
58+
val actual = stringBuffer.toString.trim.replaceAll("\\s+\n", "\n")
5959
val checkFilePath = file.getPath + File.separator + "expected.check"
6060
val checkContent =
6161
if (new File(checkFilePath).exists)
62-
fromFile(checkFilePath).getLines.mkString("\n").trim
62+
fromFile(checkFilePath).getLines.map(_.replaceAll("\\s+$", "")).mkString("\n").trim
6363
else ""
6464

6565
(file, checkContent, actual)

tests/patmat/aliasing.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
14: Pattern Match Exhaustivity: _: Clazz & Test.Alias1, _: Trait & Test.Alias1
2+
19: Pattern Match Exhaustivity: _: Trait & Test.Alias2
3+
23: Pattern Match Exhaustivity: _: Trait & (Test.Alias2 & OpenTrait2){x: Int}

tests/patmat/aliasing.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
sealed trait T
2+
trait Trait extends T
3+
class Clazz extends T
4+
object Obj extends T
5+
6+
trait OpenTrait
7+
trait OpenTrait2
8+
class OpenClass
9+
10+
trait Unrelated
11+
12+
object Test {
13+
type Alias1 = OpenTrait
14+
def traitAndClass(s: T & Alias1) = s match {
15+
case _: Unrelated => ;
16+
}
17+
18+
type Alias2 = OpenTrait & OpenClass
19+
def classOnly(s: T & Alias2) = s match {
20+
case _: Unrelated => ;
21+
}
22+
23+
def classOnlyRefined(s: T & (Alias2 & OpenTrait2) { val x: Int }) = s match {
24+
case _: Unrelated => ;
25+
}
26+
}
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1-
19: Pattern Match Exhaustivity: _: AbstractClass & OpenTrait, _: Clazz & OpenTrait, _: Trait & OpenTrait
2-
23: Pattern Match Exhaustivity: _: Trait & OpenClass
3-
27: Pattern Match Exhaustivity: _: Trait & OpenAbstractClass
1+
23: Pattern Match Exhaustivity: _: AbstractClass & OpenTrait, _: Clazz & OpenTrait, _: Trait & OpenTrait
2+
27: Pattern Match Exhaustivity: _: AbstractClass & OpenTrait & OpenTrait2, _: Clazz & OpenTrait & OpenTrait2, _: Trait & OpenTrait & OpenTrait2
3+
31: Pattern Match Exhaustivity: _: Trait & OpenClass
4+
35: Pattern Match Exhaustivity: _: Trait & OpenTrait & OpenClass
5+
43: Pattern Match Exhaustivity: _: Trait & OpenAbstractClass
6+
47: Pattern Match Exhaustivity: _: Trait & OpenTrait & OpenClassSubclass

tests/patmat/andtype-opentype-interaction.scala

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,40 @@ object Obj extends T
1111

1212
trait OpenTrait
1313
class OpenClass
14+
class OpenClassSubclass extends OpenClass
1415
abstract class OpenAbstractClass
1516

1617
trait Unrelated
1718

19+
trait OpenTrait2
20+
class OpenClass2
21+
1822
object Test {
19-
def m1(s: T & OpenTrait) = s match {
23+
def m1a(s: T & OpenTrait) = s match {
24+
case _: Unrelated => ;
25+
}
26+
27+
def m1b(s: T & OpenTrait & OpenTrait2) = s match {
2028
case _: Unrelated => ;
2129
}
2230

2331
def m2(s: T & OpenClass) = s match {
2432
case _: Unrelated => ;
2533
}
2634

35+
def m2b(s: T & OpenTrait & OpenClass) = s match {
36+
case _: Unrelated => ;
37+
}
38+
39+
def m2c(s: (T & OpenClass) & (OpenTrait & OpenClass2)) = s match {
40+
case _: Unrelated => ;
41+
}
42+
2743
def m3(s: T & OpenAbstractClass) = s match {
2844
case _: Unrelated => ;
2945
}
46+
47+
def m4(s: (T & OpenClass) & (OpenTrait & OpenClassSubclass)) = s match {
48+
case _: Unrelated => ;
49+
}
3050
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
32: Pattern Match Exhaustivity: _: Trait & C1{x: Int}
2+
48: Pattern Match Exhaustivity: _: Clazz & (C1 | C2 | T1){x: Int} & (C3 | C4 | T2){x: Int}, _: Trait & (C1 | C2 | T1){x: Int} & (C3 | C4 | T2){x: Int}
3+
54: Pattern Match Exhaustivity: _: Trait & (C1 | C2 | T1){x: Int} & C3{x: Int}
4+
65: Pattern Match Exhaustivity: _: Trait & (C1 | C2){x: Int} & (C3 | SubC1){x: Int}
5+
72: Pattern Match Exhaustivity: _: Trait & (T1 & C1 | T1 & SubC2){x: Int} &
6+
(T2 & C2 | T2 & C3 | T2 & SubC1){x: Int}
7+
& SubSubC1{x: Int}
8+
79: Pattern Match Exhaustivity: _: Trait & (T1 & C1 | T1 & SubC2){x: Int} &
9+
(T2 & C2 | T2 & C3 | T2 & SubC1){x: Int}
10+
& SubSubC2{x: Int}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
sealed trait S
2+
trait Trait extends S
3+
class Clazz extends S
4+
object Obj extends S
5+
6+
trait T1
7+
trait T2
8+
9+
class C1
10+
class C2
11+
class C3
12+
class C4
13+
14+
class SubC1 extends C1
15+
class SubSubC1 extends SubC1
16+
class SubC2 extends C2
17+
class SubSubC2 extends SubC2
18+
19+
trait Unrelated
20+
21+
object Test {
22+
/*
23+
Note: error message on this suggests Obj to be unmatched as well.
24+
This isn't a bug in atomic type intersection, but a consequence
25+
of approximating all types to be a subtype of a structural type.
26+
27+
def basic1(s: S & { val x: Int }) = s match {
28+
case _: Unrelated => ;
29+
}
30+
*/
31+
32+
def basic2(s: S & C1 { val x: Int }) = s match {
33+
case _: Unrelated => ;
34+
}
35+
36+
def doubleInheritance1(s: S & C1 { val x: Int } & C2) = s match {
37+
case _: Unrelated => ;
38+
}
39+
40+
def doubleInheritance2(s: S & C1 { val x: Int }
41+
& C2 { val x: Int }) =
42+
s match {
43+
case _: Unrelated => ;
44+
}
45+
46+
def classOrTrait(s: S & (C1 | (C2 | T1)) { val x: Int }
47+
& (C3 | (C4 | T2)) { val x: Int }) =
48+
s match {
49+
case _: Unrelated => ;
50+
}
51+
52+
def traitOnly(s: S & (C1 | (C2 | T1)) { val x: Int }
53+
& C3 { val x: Int }) =
54+
s match {
55+
case _: Unrelated => ;
56+
}
57+
58+
def nestedDoubleInheritance(s: S & (C1 & C2) { val x: Int }) =
59+
s match {
60+
case _: Unrelated => ;
61+
}
62+
63+
def subclassingA(s: S & (C1 | C2) { val x: Int }
64+
& (C3 | SubC1) { val x: Int }) =
65+
s match {
66+
case _: Unrelated => ;
67+
}
68+
69+
def subclassingB(s: S & (T1 & (C1 | SubC2)) { val x: Int }
70+
& (T2 & (C2 | C3 | SubC1)) { val x: Int }
71+
& SubSubC1 { val x: Int }) =
72+
s match {
73+
case _: Unrelated => ;
74+
}
75+
76+
def subclassingC(s: S & (T1 & (C1 | SubC2)) { val x: Int }
77+
& (T2 & (C2 | C3 | SubC1)) { val x: Int }
78+
& SubSubC2 { val x: Int }) =
79+
s match {
80+
case _: Unrelated => ;
81+
}
82+
}

0 commit comments

Comments
 (0)