Skip to content

Commit 96bc3ff

Browse files
authored
Merge pull request #11521 from Linyxus/gadt-upcasting
Change GADT constrainer to consider all parents of the scrutinee type when upcasting it
2 parents 4ffc548 + 3dbffc1 commit 96bc3ff

File tree

2 files changed

+51
-25
lines changed

2 files changed

+51
-25
lines changed

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

+37-25
Original file line numberDiff line numberDiff line change
@@ -94,38 +94,50 @@ trait PatternTypeConstrainer { self: TypeComparer =>
9494
}
9595

9696
def constrainUpcasted(scrut: Type): Boolean = trace(i"constrainUpcasted($scrut)", gadts) {
97-
val upcasted: Type = scrut match {
97+
// Fold a list of types into an AndType
98+
def buildAndType(xs: List[Type]): Type = {
99+
@annotation.tailrec def recur(acc: Type, rem: List[Type]): Type = rem match {
100+
case Nil => acc
101+
case x :: rem => recur(AndType(acc, x), rem)
102+
}
103+
xs match {
104+
case Nil => NoType
105+
case x :: xs => recur(x, xs)
106+
}
107+
}
108+
109+
scrut match {
98110
case scrut: TypeRef if scrut.symbol.isClass =>
99-
// we do not infer constraints following from all parents for performance reasons
100-
// in principle however, if `A extends B, C`, then `A` can be treated as `B & C`
101-
scrut.firstParent
111+
// consider all parents
112+
val parents = scrut.parents
113+
val andType = buildAndType(parents)
114+
!andType.exists || constrainPatternType(pat, andType)
102115
case scrut @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass =>
103116
val patClassSym = pat.classSymbol
104-
// as above, we do not consider all parents for performance reasons
105-
def firstParentSharedWithPat(tp: Type, tpClassSym: ClassSymbol): Symbol = {
117+
// find all shared parents in the inheritance hierarchy between pat and scrut
118+
def allParentsSharedWithPat(tp: Type, tpClassSym: ClassSymbol): List[Symbol] = {
106119
var parents = tpClassSym.info.parents
107-
parents match {
108-
case first :: rest =>
109-
if (first.classSymbol == defn.ObjectClass) parents = rest
110-
case _ => ;
111-
}
112-
parents match {
113-
case first :: _ =>
114-
val firstClassSym = first.classSymbol.asClass
115-
val res = if (patClassSym.derivesFrom(firstClassSym)) firstClassSym
116-
else firstParentSharedWithPat(first, firstClassSym)
117-
res
118-
case _ => NoSymbol
120+
if parents.nonEmpty && parents.head.classSymbol == defn.ObjectClass then
121+
parents = parents.tail
122+
parents flatMap { tp =>
123+
val sym = tp.classSymbol.asClass
124+
if patClassSym.derivesFrom(sym) then List(sym)
125+
else allParentsSharedWithPat(tp, sym)
119126
}
120127
}
121-
val sym = firstParentSharedWithPat(tycon, tycon.symbol.asClass)
122-
if (sym.exists) scrut.baseType(sym) else NoType
123-
case scrut: TypeProxy => scrut.superType
124-
case _ => NoType
128+
val allSyms = allParentsSharedWithPat(tycon, tycon.symbol.asClass)
129+
val baseClasses = allSyms map scrut.baseType
130+
val andType = buildAndType(baseClasses)
131+
!andType.exists || constrainPatternType(pat, andType)
132+
case _ =>
133+
val upcasted: Type = scrut match {
134+
case scrut: TypeProxy => scrut.superType
135+
case _ => NoType
136+
}
137+
if (upcasted.exists)
138+
constrainSimplePatternType(pat, upcasted) || constrainUpcasted(upcasted)
139+
else true
125140
}
126-
if (upcasted.exists)
127-
constrainSimplePatternType(pat, upcasted) || constrainUpcasted(upcasted)
128-
else true
129141
}
130142

131143
scrut.dealias match {

tests/pos/gadt-upcast.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
trait TagA[A]
2+
trait TagB[B]
3+
trait TagC[C]
4+
trait TriTag[A, B, C] extends TagA[A] with TagB[B] with TagC[C]
5+
class IntStrCharTag extends TagA[Int] with TagB[String] with TagC[Char]
6+
7+
def get[A, B, C]: TriTag[A, B, C] => (A, B, C) = {
8+
case _: IntStrCharTag => (0, "zero", '0')
9+
}
10+
11+
object GadtUpcast extends App {
12+
val ret = get(new IntStrCharTag with TriTag[Int, String, Char])
13+
println(ret)
14+
}

0 commit comments

Comments
 (0)