Skip to content

Change GADT constrainer to consider all parents of the scrutinee type when upcasting it #11521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 37 additions & 25 deletions compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,38 +94,50 @@ trait PatternTypeConstrainer { self: TypeComparer =>
}

def constrainUpcasted(scrut: Type): Boolean = trace(i"constrainUpcasted($scrut)", gadts) {
val upcasted: Type = scrut match {
// Fold a list of types into an AndType
def buildAndType(xs: List[Type]): Type = {
@annotation.tailrec def recur(acc: Type, rem: List[Type]): Type = rem match {
case Nil => acc
case x :: rem => recur(AndType(acc, x), rem)
}
xs match {
case Nil => NoType
case x :: xs => recur(x, xs)
}
}

scrut match {
case scrut: TypeRef if scrut.symbol.isClass =>
// we do not infer constraints following from all parents for performance reasons
// in principle however, if `A extends B, C`, then `A` can be treated as `B & C`
scrut.firstParent
// consider all parents
val parents = scrut.parents
val andType = buildAndType(parents)
!andType.exists || constrainPatternType(pat, andType)
case scrut @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass =>
val patClassSym = pat.classSymbol
// as above, we do not consider all parents for performance reasons
def firstParentSharedWithPat(tp: Type, tpClassSym: ClassSymbol): Symbol = {
// find all shared parents in the inheritance hierarchy between pat and scrut
def allParentsSharedWithPat(tp: Type, tpClassSym: ClassSymbol): List[Symbol] = {
var parents = tpClassSym.info.parents
parents match {
case first :: rest =>
if (first.classSymbol == defn.ObjectClass) parents = rest
case _ => ;
}
parents match {
case first :: _ =>
val firstClassSym = first.classSymbol.asClass
val res = if (patClassSym.derivesFrom(firstClassSym)) firstClassSym
else firstParentSharedWithPat(first, firstClassSym)
res
case _ => NoSymbol
if parents.nonEmpty && parents.head.classSymbol == defn.ObjectClass then
parents = parents.tail
parents flatMap { tp =>
val sym = tp.classSymbol.asClass
if patClassSym.derivesFrom(sym) then List(sym)
else allParentsSharedWithPat(tp, sym)
}
}
val sym = firstParentSharedWithPat(tycon, tycon.symbol.asClass)
if (sym.exists) scrut.baseType(sym) else NoType
case scrut: TypeProxy => scrut.superType
case _ => NoType
val allSyms = allParentsSharedWithPat(tycon, tycon.symbol.asClass)
val baseClasses = allSyms map scrut.baseType
val andType = buildAndType(baseClasses)
!andType.exists || constrainPatternType(pat, andType)
case _ =>
val upcasted: Type = scrut match {
case scrut: TypeProxy => scrut.superType
case _ => NoType
}
if (upcasted.exists)
constrainSimplePatternType(pat, upcasted) || constrainUpcasted(upcasted)
else true
}
if (upcasted.exists)
constrainSimplePatternType(pat, upcasted) || constrainUpcasted(upcasted)
else true
}

scrut.dealias match {
Expand Down
14 changes: 14 additions & 0 deletions tests/pos/gadt-upcast.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
trait TagA[A]
trait TagB[B]
trait TagC[C]
trait TriTag[A, B, C] extends TagA[A] with TagB[B] with TagC[C]
class IntStrCharTag extends TagA[Int] with TagB[String] with TagC[Char]

def get[A, B, C]: TriTag[A, B, C] => (A, B, C) = {
case _: IntStrCharTag => (0, "zero", '0')
}

object GadtUpcast extends App {
val ret = get(new IntStrCharTag with TriTag[Int, String, Char])
println(ret)
}