Skip to content

Commit

Permalink
Handle higher-kinded comparisons involving GADTs
Browse files Browse the repository at this point in the history
Higher-kinded comparisons did not account for the fact that a type constructor
in a higher-kinded application could have a narrowed GADT bound.
  • Loading branch information
odersky committed Feb 21, 2018
1 parent f3f82f4 commit 3482c27
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 6 deletions.
18 changes: 12 additions & 6 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -765,9 +765,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
* tp1 <:< tp2 using fourthTry (this might instantiate params in tp1)
* tp1 <:< app2 using isSubType (this might instantiate params in tp2)
*/
def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean =
def compareLower(tycon2bounds: TypeBounds, followSuperType: Boolean): Boolean =
if (tycon2bounds.lo eq tycon2bounds.hi)
if (tyconIsTypeRef) recur(tp1, tp2.superType)
if (followSuperType) recur(tp1, tp2.superType)
else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2))
else
fallback(tycon2bounds.lo)
Expand All @@ -776,12 +776,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
case param2: TypeParamRef =>
isMatchingApply(tp1) ||
canConstrain(param2) && canInstantiate(param2) ||
compareLower(bounds(param2), tyconIsTypeRef = false)
compareLower(bounds(param2), followSuperType = false)
case tycon2: TypeRef =>
isMatchingApply(tp1) || {
tycon2.info match {
case info2: TypeBounds =>
compareLower(info2, tyconIsTypeRef = true)
val gbounds2 = ctx.gadt.bounds(tycon2.symbol)
if (gbounds2 == null) compareLower(info2, followSuperType = true)
else compareLower(gbounds2 & info2, followSuperType = false)
case info2: ClassInfo =>
val base = tp1.baseType(info2.cls)
if (base.exists && base.ne(tp1))
Expand Down Expand Up @@ -813,8 +815,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
}
canConstrain(param1) && canInstantiate ||
isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow)
case tycon1: TypeRef if tycon1.symbol.isClass =>
false
case tycon1: TypeRef =>
!tycon1.symbol.isClass && {
val gbounds1 = ctx.gadt.bounds(tycon1.symbol)
if (gbounds1 == null) recur(tp1.superType, tp2)
else recur((gbounds1.hi & tycon1.info.bounds.hi).applyIfParameterized(args1), tp2)
}
case tycon1: TypeProxy =>
recur(tp1.superType, tp2)
case _ =>
Expand Down
55 changes: 55 additions & 0 deletions tests/neg/tagging.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import scala.reflect.ClassTag
object tagging {

// Tagged[S, T] means that S is tagged with T
opaque type Tagged[X, Y] = X

object Tagged {
def tag[S, T](s: S): Tagged[S, T] = (s: S)
def untag[S, T](st: Tagged[S, T]): S = st

def tags[F[_], S, T](fs: F[S]): F[Tagged[S, T]] = fs
def untags[F[_], S, T](fst: F[Tagged[S, T]]): F[S] = fst

implicit def taggedClassTag[S, T](implicit ct: ClassTag[S]): ClassTag[Tagged[S, T]] =
ct
}

import Tagged._

type @@[S, T] = Tagged[S, T]

implicit class UntagOps[S, T](st: S @@ T) extends AnyVal {
def untag: S = Tagged.untag(st)
}

implicit class UntagsOps[F[_], S, T](fs: F[S @@ T]) extends AnyVal {
def untags: F[S] = Tagged.untags(fs)
}

implicit class TagOps[S](s: S) extends AnyVal {
def tag[T]: S @@ T = Tagged.tag(s)
}

implicit class TagsOps[F[_], S](fs: F[S]) extends AnyVal {
def tags[T]: F[S @@ T] = Tagged.tags(fs)
}

trait Meter
trait Foot
trait Fathom

val x: Double @@ Meter = (1e7).tag[Meter]
val y: Double @@ Foot = (123.0).tag[Foot]
val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags[Meter]

val o: Ordering[Double] = implicitly
val om: Ordering[Double @@ Meter] = o.tags[Meter]
om.compare(x, x) // 0
om.compare(x, y) // error
xs.min(om) // 1.0
xs.min(o) // error

// uses ClassTag[Double] via 'Tagged.taggedClassTag'.
val ys = new Array[Double @@ Foot](20)
}
55 changes: 55 additions & 0 deletions tests/pos/tagging.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import scala.reflect.ClassTag
object tagging {

// Tagged[S, T] means that S is tagged with T
opaque type Tagged[X, Y] = X

object Tagged {
def tag[S, T](s: S): Tagged[S, T] = (s: S)
def untag[S, T](st: Tagged[S, T]): S = st

def tags[F[_], S, T](fs: F[S]): F[Tagged[S, T]] = fs
def untags[F[_], S, T](fst: F[Tagged[S, T]]): F[S] = fst

implicit def taggedClassTag[S, T](implicit ct: ClassTag[S]): ClassTag[Tagged[S, T]] =
ct
}

import Tagged._

type @@[S, T] = Tagged[S, T]

implicit class UntagOps[S, T](st: S @@ T) extends AnyVal {
def untag: S = Tagged.untag(st)
}

implicit class UntagsOps[F[_], S, T](fs: F[S @@ T]) extends AnyVal {
def untags: F[S] = Tagged.untags(fs)
}

implicit class TagOps[S](s: S) extends AnyVal {
def tag[T]: S @@ T = Tagged.tag(s)
}

implicit class TagsOps[F[_], S](fs: F[S]) extends AnyVal {
def tags[T]: F[S @@ T] = Tagged.tags(fs)
}

trait Meter
trait Foot
trait Fathom

val x: Double @@ Meter = (1e7).tag[Meter]
val y: Double @@ Foot = (123.0).tag[Foot]
val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags[Meter]

val o: Ordering[Double] = implicitly
val om: Ordering[Double @@ Meter] = o.tags[Meter]
om.compare(x, x) // 0
// om.compare(x, y) // does not compile
xs.min(om) // 1.0
// xs.min(o) // does not compile

// uses ClassTag[Double] via 'Tagged.taggedClassTag'.
val ys = new Array[Double @@ Foot](20)
}

0 comments on commit 3482c27

Please sign in to comment.