Skip to content

Commit d058973

Browse files
committed
Handle higher-kinded comparisons involving GADTs
Higher-kinded comparisons did not account for the fact that a type constructor in a higher-kinded application could have a narrowed GADT bound.
1 parent f3e8074 commit d058973

File tree

3 files changed

+122
-6
lines changed

3 files changed

+122
-6
lines changed

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

+12-6
Original file line numberDiff line numberDiff line change
@@ -765,9 +765,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
765765
* tp1 <:< tp2 using fourthTry (this might instantiate params in tp1)
766766
* tp1 <:< app2 using isSubType (this might instantiate params in tp2)
767767
*/
768-
def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean =
768+
def compareLower(tycon2bounds: TypeBounds, followSuperType: Boolean): Boolean =
769769
if (tycon2bounds.lo eq tycon2bounds.hi)
770-
if (tyconIsTypeRef) recur(tp1, tp2.superType)
770+
if (followSuperType) recur(tp1, tp2.superType)
771771
else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2))
772772
else
773773
fallback(tycon2bounds.lo)
@@ -776,12 +776,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
776776
case param2: TypeParamRef =>
777777
isMatchingApply(tp1) ||
778778
canConstrain(param2) && canInstantiate(param2) ||
779-
compareLower(bounds(param2), tyconIsTypeRef = false)
779+
compareLower(bounds(param2), followSuperType = false)
780780
case tycon2: TypeRef =>
781781
isMatchingApply(tp1) || {
782782
tycon2.info match {
783783
case info2: TypeBounds =>
784-
compareLower(info2, tyconIsTypeRef = true)
784+
val gbounds2 = ctx.gadt.bounds(tycon2.symbol)
785+
if (gbounds2 == null) compareLower(info2, followSuperType = true)
786+
else compareLower(gbounds2 & info2, followSuperType = false)
785787
case info2: ClassInfo =>
786788
val base = tp1.baseType(info2.cls)
787789
if (base.exists && base.ne(tp1))
@@ -813,8 +815,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
813815
}
814816
canConstrain(param1) && canInstantiate ||
815817
isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow)
816-
case tycon1: TypeRef if tycon1.symbol.isClass =>
817-
false
818+
case tycon1: TypeRef =>
819+
!tycon1.symbol.isClass && {
820+
val gbounds1 = ctx.gadt.bounds(tycon1.symbol)
821+
if (gbounds1 == null) recur(tp1.superType, tp2)
822+
else recur((gbounds1.hi & tycon1.info.bounds.hi).applyIfParameterized(args1), tp2)
823+
}
818824
case tycon1: TypeProxy =>
819825
recur(tp1.superType, tp2)
820826
case _ =>

tests/neg/tagging.scala

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import scala.reflect.ClassTag
2+
object tagging {
3+
4+
// Tagged[S, T] means that S is tagged with T
5+
opaque type Tagged[X, Y] = X
6+
7+
object Tagged {
8+
def tag[S, T](s: S): Tagged[S, T] = (s: S)
9+
def untag[S, T](st: Tagged[S, T]): S = st
10+
11+
def tags[F[_], S, T](fs: F[S]): F[Tagged[S, T]] = fs
12+
def untags[F[_], S, T](fst: F[Tagged[S, T]]): F[S] = fst
13+
14+
implicit def taggedClassTag[S, T](implicit ct: ClassTag[S]): ClassTag[Tagged[S, T]] =
15+
ct
16+
}
17+
18+
import Tagged._
19+
20+
type @@[S, T] = Tagged[S, T]
21+
22+
implicit class UntagOps[S, T](st: S @@ T) extends AnyVal {
23+
def untag: S = Tagged.untag(st)
24+
}
25+
26+
implicit class UntagsOps[F[_], S, T](fs: F[S @@ T]) extends AnyVal {
27+
def untags: F[S] = Tagged.untags(fs)
28+
}
29+
30+
implicit class TagOps[S](s: S) extends AnyVal {
31+
def tag[T]: S @@ T = Tagged.tag(s)
32+
}
33+
34+
implicit class TagsOps[F[_], S](fs: F[S]) extends AnyVal {
35+
def tags[T]: F[S @@ T] = Tagged.tags(fs)
36+
}
37+
38+
trait Meter
39+
trait Foot
40+
trait Fathom
41+
42+
val x: Double @@ Meter = (1e7).tag[Meter]
43+
val y: Double @@ Foot = (123.0).tag[Foot]
44+
val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags[Meter]
45+
46+
val o: Ordering[Double] = implicitly
47+
val om: Ordering[Double @@ Meter] = o.tags[Meter]
48+
om.compare(x, x) // 0
49+
om.compare(x, y) // error
50+
xs.min(om) // 1.0
51+
xs.min(o) // error
52+
53+
// uses ClassTag[Double] via 'Tagged.taggedClassTag'.
54+
val ys = new Array[Double @@ Foot](20)
55+
}

tests/pos/tagging.scala

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import scala.reflect.ClassTag
2+
object tagging {
3+
4+
// Tagged[S, T] means that S is tagged with T
5+
opaque type Tagged[X, Y] = X
6+
7+
object Tagged {
8+
def tag[S, T](s: S): Tagged[S, T] = (s: S)
9+
def untag[S, T](st: Tagged[S, T]): S = st
10+
11+
def tags[F[_], S, T](fs: F[S]): F[Tagged[S, T]] = fs
12+
def untags[F[_], S, T](fst: F[Tagged[S, T]]): F[S] = fst
13+
14+
implicit def taggedClassTag[S, T](implicit ct: ClassTag[S]): ClassTag[Tagged[S, T]] =
15+
ct
16+
}
17+
18+
import Tagged._
19+
20+
type @@[S, T] = Tagged[S, T]
21+
22+
implicit class UntagOps[S, T](st: S @@ T) extends AnyVal {
23+
def untag: S = Tagged.untag(st)
24+
}
25+
26+
implicit class UntagsOps[F[_], S, T](fs: F[S @@ T]) extends AnyVal {
27+
def untags: F[S] = Tagged.untags(fs)
28+
}
29+
30+
implicit class TagOps[S](s: S) extends AnyVal {
31+
def tag[T]: S @@ T = Tagged.tag(s)
32+
}
33+
34+
implicit class TagsOps[F[_], S](fs: F[S]) extends AnyVal {
35+
def tags[T]: F[S @@ T] = Tagged.tags(fs)
36+
}
37+
38+
trait Meter
39+
trait Foot
40+
trait Fathom
41+
42+
val x: Double @@ Meter = (1e7).tag[Meter]
43+
val y: Double @@ Foot = (123.0).tag[Foot]
44+
val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags[Meter]
45+
46+
val o: Ordering[Double] = implicitly
47+
val om: Ordering[Double @@ Meter] = o.tags[Meter]
48+
om.compare(x, x) // 0
49+
// om.compare(x, y) // does not compile
50+
xs.min(om) // 1.0
51+
// xs.min(o) // does not compile
52+
53+
// uses ClassTag[Double] via 'Tagged.taggedClassTag'.
54+
val ys = new Array[Double @@ Foot](20)
55+
}

0 commit comments

Comments
 (0)