Skip to content

Commit f69b305

Browse files
committed
fix SI-9657 about type bounds
1 parent 2d6e803 commit f69b305

File tree

6 files changed

+161
-35
lines changed

6 files changed

+161
-35
lines changed

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

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ import core.StdNames._
1414
import core.NameOps._
1515
import core.Constants._
1616

17-
/** Space logic for checking exhaustivity and unreachability of pattern matching.
17+
/** Space logic for checking exhaustivity and unreachability of pattern matching
1818
*
19-
* The core idea of the algorithm is that patterns and types are value
20-
* spaces, which is recursively defined as follows:
19+
* Space can be thought of as a set of possible values. A type or a pattern
20+
* both refer to spaces. The space of a type is the values that inhabit the
21+
* type. The space of a pattern is the values that can be covered by the
22+
* pattern.
23+
*
24+
* Space is recursively defined as follows:
2125
*
2226
* 1. `Empty` is a space
2327
* 2. For a type T, `Typ(T)` is a space
@@ -279,7 +283,11 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
279283
}
280284

281285
/** Is `tp1` a subtype of `tp2`? */
282-
def isSubType(tp1: Type, tp2: Type): Boolean = tp1 <:< tp2
286+
def isSubType(tp1: Type, tp2: Type): Boolean = {
287+
// expose is important due to type bounds
288+
// check SI-9657 and tests/patmat/gadt.scala
289+
tp1 <:< expose(tp2)
290+
}
283291

284292
def isEqualType(tp1: Type, tp2: Type): Boolean = tp1 =:= tp2
285293

@@ -472,6 +480,59 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
472480
isCheckable(sel.tpe.widen.elimAnonymousClass)
473481
}
474482

483+
484+
/** Expose refined type to eliminate reference to type variables
485+
*
486+
* A = B M { type T = A } ~~> M { type T = B }
487+
*
488+
* A <: X :> Y M { type T = A } ~~> M { type T <: X :> Y }
489+
*
490+
* A <: X :> Y B <: U :> V M { type T <: A :> B } ~~> M { type T <: X :> B }
491+
*
492+
* A = X B = Y M { type T <: A :> B } ~~> M { type T <: X :> Y }
493+
*/
494+
def expose(tp: Type): Type = {
495+
def follow(tp: Type, up: Boolean): Type = tp match {
496+
case tp: TypeProxy =>
497+
tp.underlying match {
498+
case TypeBounds(lo, hi) =>
499+
follow(if (up) hi else lo, up)
500+
case _ =>
501+
tp
502+
}
503+
case OrType(tp1, tp2) =>
504+
OrType(follow(tp1, up), follow(tp2, up))
505+
case AndType(tp1, tp2) =>
506+
AndType(follow(tp1, up), follow(tp2, up))
507+
}
508+
509+
tp match {
510+
case tp: RefinedType =>
511+
tp.refinedInfo match {
512+
case tpa : TypeAlias =>
513+
val hi = follow(tpa.alias, true)
514+
val lo = follow(tpa.alias, false)
515+
val refined = if (hi =:= lo)
516+
tpa.derivedTypeAlias(hi)
517+
else
518+
tpa.derivedTypeBounds(lo, hi)
519+
520+
tp.derivedRefinedType(
521+
expose(tp.parent),
522+
tp.refinedName,
523+
refined
524+
)
525+
case tpb @ TypeBounds(lo, hi) =>
526+
tp.derivedRefinedType(
527+
expose(tp.parent),
528+
tp.refinedName,
529+
tpb.derivedTypeBounds(follow(lo, false), follow(hi, true))
530+
)
531+
}
532+
case _ => tp
533+
}
534+
}
535+
475536
def checkExhaustivity(_match: Match): Unit = {
476537
val Match(sel, cases) = _match
477538
val selTyp = sel.tpe.widen.elimAnonymousClass.dealias
@@ -491,7 +552,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
491552

492553
def checkRedundancy(_match: Match): Unit = {
493554
val Match(sel, cases) = _match
494-
val selTyp = sel.tpe.widen.elimAnonymousClass
555+
// ignore selector type for now
556+
// val selTyp = sel.tpe.widen.elimAnonymousClass.dealias
495557

496558
// starts from the second, the first can't be redundant
497559
(1 until cases.length).foreach { i =>

tests/patmat/gadt.check

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ It would fail on the following input: Or(_, _)
1010
It would fail on the following input: BooleanLit(_), IntLit(_)
1111
def foo4b(x: Expr) = x match {
1212
^
13-
three warnings found
13+
./tests/patmat/gadt.scala:55: warning: match may not be exhaustive.
14+
It would fail on the following input: Sum(_, _)
15+
def foo5b[T <: Int](x: Expr[T]) = x match {
16+
^
17+
four warnings found

tests/patmat/gadt.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,13 @@ object Test {
4646
case _: Sum => true
4747
case _: Or => true
4848
}
49-
}
49+
50+
def foo5a[T <: Int](x: Expr[T]) = x match {
51+
case _: IntLit => true
52+
case _: Sum => true
53+
}
54+
55+
def foo5b[T <: Int](x: Expr[T]) = x match {
56+
case _: IntLit => true
57+
}
58+
}

tests/patmat/t9657.check

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
./tests/patmat/t9657.scala:29: warning: match may not be exhaustive.
2+
It would fail on the following input: Bus(_)
3+
def refuel2[P <: Petrol.type](vehicle: Vehicle {type A = P} ): Vehicle = vehicle match {
4+
^
5+
./tests/patmat/t9657.scala:38: warning: match may not be exhaustive.
6+
It would fail on the following input: Bus(_)
7+
def foo2(vehicle: Vehicle {type A <: Petrol.type} ): Vehicle = vehicle match {
8+
^
9+
./tests/patmat/t9657.scala:49: warning: match may not be exhaustive.
10+
It would fail on the following input: Bus(_)
11+
def bar2(vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match {
12+
^
13+
./tests/patmat/t9657.scala:58: warning: match may not be exhaustive.
14+
It would fail on the following input: Bus(_)
15+
def qux2[P <: Petrol.type](vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match {
16+
^
17+
four warnings found

tests/patmat/t9657.scala

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
sealed trait PowerSource
2+
3+
case object Petrol extends PowerSource
4+
5+
case object Pedal extends PowerSource
6+
7+
sealed abstract class Vehicle {
8+
type A <: PowerSource
9+
}
10+
11+
case object Bicycle extends Vehicle {
12+
type A = Pedal.type
13+
}
14+
15+
case class Bus(fuel: Int) extends Vehicle {
16+
type A = Petrol.type
17+
}
18+
19+
case class Car(fuel: Int) extends Vehicle {
20+
type A = Petrol.type
21+
}
22+
23+
class Test {
24+
def refuel[P <: Petrol.type](vehicle: Vehicle {type A = P} ): Vehicle = vehicle match {
25+
case Car(_) => Car(100)
26+
case Bus(_) => Bus(100)
27+
}
28+
29+
def refuel2[P <: Petrol.type](vehicle: Vehicle {type A = P} ): Vehicle = vehicle match {
30+
case Car(_) => Car(100)
31+
}
32+
33+
def foo1(vehicle: Vehicle {type A <: Petrol.type} ): Vehicle = vehicle match {
34+
case Car(_) => Car(100)
35+
case Bus(_) => Bus(100)
36+
}
37+
38+
def foo2(vehicle: Vehicle {type A <: Petrol.type} ): Vehicle = vehicle match {
39+
case Car(_) => Car(100)
40+
}
41+
42+
type P = Petrol.type
43+
44+
def bar1(vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match {
45+
case Car(_) => Car(100)
46+
case Bus(_) => Bus(100)
47+
}
48+
49+
def bar2(vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match {
50+
case Car(_) => Car(100)
51+
}
52+
53+
def qux1[P <: Petrol.type](vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match {
54+
case Car(_) => Car(100)
55+
case Bus(_) => Bus(100)
56+
}
57+
58+
def qux2[P <: Petrol.type](vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match {
59+
case Car(_) => Car(100)
60+
}
61+
62+
}

tests/patmat/t9657.scala.ignore

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)