Skip to content

Commit e48ecb0

Browse files
committed
Add some flexibility in comparing named and unnamed parameterized types.
1 parent 1dbb437 commit e48ecb0

File tree

2 files changed

+96
-3
lines changed

2 files changed

+96
-3
lines changed

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
365365
case _ =>
366366
compareRefinedSlow ||
367367
fourthTry(tp1, tp2) ||
368-
compareHkLambda(tp2, tp1, inOrder = false)
368+
compareHkLambda(tp2, tp1, inOrder = false) ||
369+
compareAliasedRefined(tp2, tp1, inOrder = false)
369370
}
370371
else // fast path, in particular for refinements resulting from parameterization.
371372
isSubType(tp1, skipped2) &&
@@ -485,7 +486,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
485486
}
486487
isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths
487488
case tp1: RefinedType =>
488-
isNewSubType(tp1.parent, tp2) || compareHkLambda(tp1, tp2, inOrder = true)
489+
isNewSubType(tp1.parent, tp2) ||
490+
compareHkLambda(tp1, tp2, inOrder = true) ||
491+
compareAliasedRefined(tp1, tp2, inOrder = true)
489492
case AndType(tp11, tp12) =>
490493
// Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2
491494
// and analogously for T11 & (T121 | T122) & T12 <: T2
@@ -608,6 +611,35 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
608611
false
609612
}
610613

614+
/** Say we are comparing a refined type `P{type M = U}` or `P{type M >: L <: U}`.
615+
* If P#M refers to a BaseTypeArg aliased to some other typeref P#N,
616+
* do the same comparison with `P{type N = U}` or `P{type N >: L <: U}`, respectively.
617+
* This allows to handle situations involving named type params like this one:
618+
*
619+
* trait Lambda[type Elem]
620+
* trait Lst[T] extends Lambda[T]
621+
*
622+
* compareAliasedRefined is necessary so we establish that
623+
*
624+
* Lst[Int] = Lst[Elem = Int]
625+
*/
626+
private def compareAliasedRefined(rt: RefinedType, other: Type, inOrder: Boolean) = {
627+
val mbr = refinedSymbol(rt)
628+
mbr.is(BaseTypeArg) && {
629+
mbr.info match {
630+
case TypeAlias(TypeRef(_, aliasName)) =>
631+
val rt1 = rt.derivedRefinedType(rt.parent, aliasName, rt.refinedInfo)
632+
subtyping.println(i"rewiring $rt to $rt1 in comparison with $other")
633+
if (inOrder) isSubType(rt1, other) else isSubType(other, rt1)
634+
case _ =>
635+
false
636+
}
637+
}
638+
}
639+
640+
/** The symbol referred to in the refinement of `rt` */
641+
private def refinedSymbol(rt: RefinedType) = rt.parent.member(rt.refinedName).symbol
642+
611643
/** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time
612644
* to keep the constraint as wide as possible. Specifically, if
613645
*
@@ -736,11 +768,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
736768
/** A type has been covered previously in subtype checking if it
737769
* is some combination of TypeRefs that point to classes, where the
738770
* combiners are RefinedTypes, AndTypes or AnnotatedTypes.
771+
* One exception: Refinements referring to basetype args are never considered
772+
* to be already covered. This is necessary because such refined types might
773+
* still need to be compared with a compareAliasRefined.
739774
*/
740775
private def isCovered(tp: Type): Boolean = tp.dealias.stripTypeVar match {
741776
case tp: TypeRef => tp.symbol.isClass && tp.symbol != NothingClass && tp.symbol != NullClass
742777
case tp: ProtoType => false
743-
case tp: RefinedType => isCovered(tp.parent)
778+
case tp: RefinedType => isCovered(tp.parent) && !refinedSymbol(tp).is(BaseTypeArg)
744779
case tp: AnnotatedType => isCovered(tp.underlying)
745780
case AndType(tp1, tp2) => isCovered(tp1) && isCovered(tp2)
746781
case _ => false

tests/pos/hk-named.scala

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import language.higherKinds
2+
3+
object hk0 {
4+
5+
trait Lambda[type Elem]
6+
7+
abstract class Functor[F <: Lambda] {
8+
def map[A, B](f: A => B): F[Elem = A] => F[Elem = B]
9+
}
10+
11+
object test1 {
12+
class ListT[T] extends Lambda[T]
13+
14+
val ml: Functor[ListT] = ???
15+
val mx = ml
16+
var xs: ListT[Int] = ???
17+
var ys: ListT { type Elem = Int } = xs
18+
xs = ys
19+
val mm: (Int => Boolean) => ListT[Int] => ListT[Boolean] = mx.map[Int, Boolean]
20+
val mm2: (Int => Boolean) => ListT[Int] => ListT[Boolean] = mx.map
21+
}
22+
}
23+
24+
25+
object higherKinded {
26+
27+
type Untyped = Null
28+
29+
class Tree[type -Attr >: Untyped] {
30+
type ThisType <: Tree
31+
def withString(s: String): ThisType[Attr = String] = withString(s)
32+
}
33+
/*
34+
class Ident[-Attr >: Untyped] extends Tree[Attr] {
35+
type ThisType = Ident
36+
}
37+
38+
val id = new Ident[Integer]
39+
40+
val y = id.withString("abc")
41+
42+
val z: Ident[String] = y
43+
44+
val zz: tpd.Tree = y
45+
46+
abstract class Instance[T >: Untyped] {g
47+
type Tree = higherKinded.Tree[T]
48+
}
49+
50+
object tpd extends Instance[String]
51+
52+
def transform(tree: Tree[String]) = {
53+
val tree1 = tree.withString("")
54+
tree1: Tree[String]
55+
}
56+
*/
57+
}
58+

0 commit comments

Comments
 (0)