Skip to content

Fix #3443: adapt child compatibility check #3446

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
Nov 11, 2017
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
90 changes: 15 additions & 75 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -405,35 +405,22 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
Prod(pat.tpe.stripAnnots, fun.tpe.widen, fun.symbol, pats.map(project), irrefutable(fun))
case Typed(pat @ UnApply(_, _, _), _) => project(pat)
case Typed(expr, tpt) =>
val unchecked = expr.tpe.hasAnnotation(ctx.definitions.UncheckedAnnot)
def warn(msg: String): Unit = if (!unchecked) ctx.warning(UncheckedTypePattern(msg), tpt.pos)
Typ(erase(expr.tpe.stripAnnots)(warn), true)
Typ(erase(expr.tpe.stripAnnots), true)
case _ =>
debug.println(s"unknown pattern: $pat")
Empty
}

/* Erase a type binding according to erasure semantics in pattern matching */
def erase(tp: Type)(implicit warn: String => Unit): Type = tp match {
def erase(tp: Type): Type = tp match {
case tp @ AppliedType(tycon, args) =>
if (tycon.isRef(defn.ArrayClass)) tp.derivedAppliedType(tycon, args.map(erase))
else {
val ignoreWarning = args.forall { p =>
p.typeSymbol.is(BindDefinedType) ||
p.hasAnnotation(defn.UncheckedAnnot) ||
p.isInstanceOf[TypeBounds]
}
if (!ignoreWarning)
warn("type arguments are not checked since they are eliminated by erasure")

tp.derivedAppliedType(tycon, args.map(t => WildcardType))
}
else tp.derivedAppliedType(tycon, args.map(t => WildcardType))
case OrType(tp1, tp2) =>
OrType(erase(tp1), erase(tp2))
case AndType(tp1, tp2) =>
AndType(erase(tp1), erase(tp2))
case tp: RefinedType =>
warn("type refinement is not checked since it is eliminated by erasure")
tp.derivedRefinedType(erase(tp.parent), tp.refinedName, WildcardType)
case _ => tp
}
Expand Down Expand Up @@ -606,29 +593,27 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
}
}

val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
val protoTp1 = thisTypeMap(tp1.appliedTo(tvars))

// replace type parameter references with fresh type vars or bounds
// replace type parameter references with bounds
val typeParamMap = new TypeMap {
def apply(t: Type): Type = t match {

case tp: TypeRef if tp.symbol.is(TypeParam) && tp.underlying.isInstanceOf[TypeBounds] =>
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala
val bound =
if (variance == 0) tp.underlying.bounds // non-variant case is not well-founded
else if (variance == 1) TypeBounds.upper(tp)
else TypeBounds.lower(tp)
newTypeVar(bound)
case tp: RefinedType if tp.refinedInfo.isInstanceOf[TypeBounds] =>
// Ideally, we would expect type inference to do the job
// Check tests/patmat/t9657.scala
expose(tp)
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
val exposed =
if (variance == 0) newTypeVar(tp.underlying.bounds)
else if (variance == 1) mapOver(tp.underlying.hiBound)
else mapOver(tp.underlying.loBound)

debug.println(s"$tp exposed to =====> $exposed")
exposed
case _ =>
mapOver(t)
}
}

val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
val protoTp1 = thisTypeMap(tp1.appliedTo(tvars))

if (protoTp1 <:< tp2 && isFullyDefined(protoTp1, ForceDegree.noBottom)) protoTp1
else {
val protoTp2 = typeParamMap(tp2)
Expand Down Expand Up @@ -773,51 +758,6 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
res
}


/** Eliminate reference to type parameters in refinements
*
* A <: X :> Y B <: U :> V M { type T <: A :> B } ~~> M { type T <: X :> V }
*/
def expose(tp: Type, refineCtx: Boolean = false, up: Boolean = true): Type = tp match {
case tp: AppliedType =>
tp.derivedAppliedType(expose(tp.tycon, refineCtx, up), tp.args.map(expose(_, refineCtx, up)))

case tp: TypeAlias =>
val hi = expose(tp.alias, refineCtx, up)
val lo = expose(tp.alias, refineCtx, up)

if (hi =:= lo)
tp.derivedTypeAlias(hi)
else
tp.derivedTypeBounds(lo, hi)

case tp @ TypeBounds(lo, hi) =>
tp.derivedTypeBounds(expose(lo, refineCtx, false), expose(hi, refineCtx, true))

case tp: RefinedType =>
tp.derivedRefinedType(
expose(tp.parent),
tp.refinedName,
expose(tp.refinedInfo, true, up)
)
case tp: TypeProxy if refineCtx =>
tp.underlying match {
case TypeBounds(lo, hi) =>
expose(if (up) hi else lo, refineCtx, up)
case _ =>
tp
}

case OrType(tp1, tp2) =>
OrType(expose(tp1, refineCtx, up), expose(tp2, refineCtx, up))

case AndType(tp1, tp2) =>
AndType(expose(tp1, refineCtx, up), expose(tp2, refineCtx, up))

case _ => tp
}


def checkExhaustivity(_match: Match): Unit = {
val Match(sel, cases) = _match
val selTyp = sel.tpe.widen.dealias
Expand Down
2 changes: 0 additions & 2 deletions tests/patmat/3144.check

This file was deleted.

2 changes: 0 additions & 2 deletions tests/patmat/3144b.check

This file was deleted.

1 change: 0 additions & 1 deletion tests/patmat/enum-HList.check

This file was deleted.

1 change: 0 additions & 1 deletion tests/patmat/enum-Tree.check

This file was deleted.

19 changes: 19 additions & 0 deletions tests/patmat/i3443.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
object Test {
// shapeless.Coproduct
sealed trait Coproduct extends Product with Serializable
sealed trait :+:[+H, +T <: Coproduct] extends Coproduct
final case class Inl[+H, +T <: Coproduct](head : H) extends :+:[H, T]
final case class Inr[+H, +T <: Coproduct](tail : T) extends :+:[H, T]
sealed trait CNil extends Coproduct

// Note that this only appears when T is a type parameter. Replaying T with
// a concrete type (CNil or another :+:) leads to accurate warnnings
def f[T <: Coproduct](fa: Int :+: T) =
fa match {
case Inl(x) => 1
case Inr(x) => 2 // Dotty thinks this unreachable, but it is!
}

def main(args: Array[String]): Unit =
assert(f(Inr(Inl(-1))) == 2) // Example of reachability
}
1 change: 0 additions & 1 deletion tests/patmat/t3683.check

This file was deleted.

1 change: 0 additions & 1 deletion tests/patmat/t3683a.check
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
8: Pattern Match Exhaustivity
14: Pattern Match Exhaustivity: XX()