Skip to content

Match Types: implement cantPossiblyMatch #5996

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 20 commits into from
Mar 9, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c1b7e84
Minor TypeTestsCasts refactoring
OlivierBlanvillain Feb 11, 2019
6e39fcb
Rename evalOnce to letBind
OlivierBlanvillain Feb 11, 2019
eef623a
Rename cmp to typeComparer
OlivierBlanvillain Feb 11, 2019
6755f52
Remove reduceParallel
OlivierBlanvillain Feb 18, 2019
88cfb7e
Fix spacing for TypeComparer comments
OlivierBlanvillain Feb 21, 2019
bb1515e
Flag ChildrenQueried in hasAnonymousChild
OlivierBlanvillain Feb 22, 2019
f79d937
Implement cantPossiblyMatch
OlivierBlanvillain Mar 1, 2019
60d0e20
Replace Space.inhabited by typeComparer.intersecting
OlivierBlanvillain Feb 28, 2019
d1180cc
Move refineUsingParent to Typer
OlivierBlanvillain Feb 26, 2019
b0c1e7b
Check inhabitation of children in Space
OlivierBlanvillain Feb 28, 2019
c3d23fe
Only trust isSameType for fully instanciated types
OlivierBlanvillain Feb 28, 2019
4f934ad
Use derivesFrom instead of isSubType for classes
OlivierBlanvillain Mar 1, 2019
80c25e3
Handle type parameters using symbol.is(TypeParam)
OlivierBlanvillain Mar 4, 2019
ab74827
Fix AppliedType logic
OlivierBlanvillain Mar 4, 2019
1df0d8b
Revert "Rename evalOnce to letBind"
OlivierBlanvillain Mar 6, 2019
8827eff
Revert "Flag ChildrenQueried in hasAnonymousChild"
OlivierBlanvillain Mar 6, 2019
ffa8acf
Address review
OlivierBlanvillain Mar 6, 2019
e7f6049
Move refineUsingParent to TypeOps
OlivierBlanvillain Mar 6, 2019
f4df58d
Factor out cov. test and use it in the inv. case
OlivierBlanvillain Mar 6, 2019
ea04343
Update inhabited check in Space
OlivierBlanvillain Mar 6, 2019
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
7 changes: 2 additions & 5 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1898,11 +1898,8 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
* It should hold that `tp` and `decompose(tp).reduce(_ or _)`
* denote the same set of values.
*/
def decompose(sym: Symbol, tp: Type): List[Type] = {
import dotty.tools.dotc.transform.patmat.SpaceEngine
val se = new SpaceEngine
sym.children.map(x => se.refine(tp, x)).filter(_.exists)
}
def decompose(sym: Symbol, tp: Type): List[Type] =
sym.children.map(x => ctx.typer.refineUsingParent(tp, x)).filter(_.exists)

(tp1.dealias, tp2.dealias) match {
case (tp1: ConstantType, tp2: ConstantType) =>
Expand Down
156 changes: 2 additions & 154 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -466,10 +466,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
children.map(sym => Typ(sym.termRef, true))
case tp =>
val parts = children.map { sym =>
if (sym.is(ModuleClass))
refine(tp, sym.sourceModule)
else
refine(tp, sym)
val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym
ctx.typer.refineUsingParent(tp, sym1)
} filter(_.exists)

debug.println(s"${tp.show} decomposes to [${parts.map(_.show).mkString(", ")}]")
Expand All @@ -478,156 +476,6 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
}
}

/** Refine child based on parent
*
* In child class definition, we have:
*
* class Child[Ts] extends path.Parent[Us] with Es
* object Child extends path.Parent[Us] with Es
* val child = new path.Parent[Us] with Es // enum values
*
* Given a parent type `parent` and a child symbol `child`, we infer the prefix
* and type parameters for the child:
*
* prefix.child[Vs] <:< parent
*
* where `Vs` are fresh type variables and `prefix` is the symbol prefix with all
* non-module and non-package `ThisType` replaced by fresh type variables.
*
* If the subtyping is true, the instantiated type `p.child[Vs]` is
* returned. Otherwise, `NoType` is returned.
*
*/
def refine(parent: Type, child: Symbol): Type = {
if (child.isTerm && child.is(Case, butNot = Module)) return child.termRef // enum vals always match

// <local child> is a place holder from Scalac, it is hopeless to instantiate it.
//
// Quote from scalac (from nsc/symtab/classfile/Pickler.scala):
//
// ...When a sealed class/trait has local subclasses, a single
// <local child> class symbol is added as pickled child
// (instead of a reference to the anonymous class; that was done
// initially, but seems not to work, ...).
//
if (child.name == tpnme.LOCAL_CHILD) return child.typeRef

val childTp = if (child.isTerm) child.termRef else child.typeRef

instantiate(childTp, parent)(ctx.fresh.setNewTyperState()).dealias
}

/** expose abstract type references to their bounds or tvars according to variance */
private class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
def expose(lo: Type, hi: Type): Type =
if (variance == 0)
newTypeVar(TypeBounds(lo, hi))
else if (variance == 1)
if (maximize) hi else lo
else
if (maximize) lo else hi

def apply(tp: Type): Type = tp match {
case tp: TypeRef if isBounds(tp.underlying) =>
val lo = this(tp.info.loBound)
val hi = this(tp.info.hiBound)
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
val exposed = expose(lo, hi)
debug.println(s"$tp exposed to =====> $exposed")
exposed

case AppliedType(tycon: TypeRef, args) if isBounds(tycon.underlying) =>
val args2 = args.map(this)
val lo = this(tycon.info.loBound).applyIfParameterized(args2)
val hi = this(tycon.info.hiBound).applyIfParameterized(args2)
val exposed = expose(lo, hi)
debug.println(s"$tp exposed to =====> $exposed")
exposed

case _ =>
mapOver(tp)
}
}

private def minTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = false)
private def maxTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = true)

/** Instantiate type `tp1` to be a subtype of `tp2`
*
* Return the instantiated type if type parameters and this type
* in `tp1` can be instantiated such that `tp1 <:< tp2`.
*
* Otherwise, return NoType.
*
*/
def instantiate(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = {
// Fix subtype checking for child instantiation,
// such that `Foo(Test.this.foo) <:< Foo(Foo.this)`
// See tests/patmat/i3938.scala
class RemoveThisMap extends TypeMap {
var prefixTVar: Type = null
def apply(tp: Type): Type = tp match {
case ThisType(tref: TypeRef) if !tref.symbol.isStaticOwner =>
if (tref.symbol.is(Module))
TermRef(this(tref.prefix), tref.symbol.sourceModule)
else if (prefixTVar != null)
this(tref)
else {
prefixTVar = WildcardType // prevent recursive call from assigning it
prefixTVar = newTypeVar(TypeBounds.upper(this(tref)))
prefixTVar
}
case tp => mapOver(tp)
}
}

// replace uninstantiated type vars with WildcardType, check tests/patmat/3333.scala
def instUndetMap(implicit ctx: Context) = new TypeMap {
def apply(t: Type): Type = t match {
case tvar: TypeVar if !tvar.isInstantiated => WildcardType(tvar.origin.underlying.bounds)
case _ => mapOver(t)
}
}

val removeThisType = new RemoveThisMap
val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
val protoTp1 = removeThisType.apply(tp1).appliedTo(tvars)

val force = new ForceDegree.Value(
tvar =>
!(ctx.typerState.constraint.entry(tvar.origin) `eq` tvar.origin.underlying) ||
(tvar `eq` removeThisType.prefixTVar),
minimizeAll = false,
allowBottom = false
)

// If parent contains a reference to an abstract type, then we should
// refine subtype checking to eliminate abstract types according to
// variance. As this logic is only needed in exhaustivity check,
// we manually patch subtyping check instead of changing TypeComparer.
// See tests/patmat/i3645b.scala
def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent =>
implicit val ictx = ctx.fresh.setNewTyperState()
parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2)
}

if (protoTp1 <:< tp2) {
if (isFullyDefined(protoTp1, force)) protoTp1
else instUndetMap.apply(protoTp1)
}
else {
val protoTp2 = maxTypeMap.apply(tp2)
if (protoTp1 <:< protoTp2 || parentQualify) {
if (isFullyDefined(AndType(protoTp1, protoTp2), force)) protoTp1
else instUndetMap.apply(protoTp1)
}
else {
debug.println(s"$protoTp1 <:< $protoTp2 = false")
NoType
}
}
}

/** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
def canDecompose(tp: Type): Boolean = {
val dealiasedTp = tp.dealias
Expand Down
148 changes: 148 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2929,4 +2929,152 @@ class Typer extends Namer
!tree.tpe.isRef(defn.UnitClass) && !isSelfOrSuperConstrCall(tree))
ctx.warning(PureExpressionInStatementPosition(original, exprOwner), original.sourcePos)
}

/** Refine child based on parent
*
* In child class definition, we have:
*
* class Child[Ts] extends path.Parent[Us] with Es
* object Child extends path.Parent[Us] with Es
* val child = new path.Parent[Us] with Es // enum values
*
* Given a parent type `parent` and a child symbol `child`, we infer the prefix
* and type parameters for the child:
*
* prefix.child[Vs] <:< parent
*
* where `Vs` are fresh type variables and `prefix` is the symbol prefix with all
* non-module and non-package `ThisType` replaced by fresh type variables.
*
* If the subtyping is true, the instantiated type `p.child[Vs]` is
* returned. Otherwise, `NoType` is returned.
*/
def refineUsingParent(parent: Type, child: Symbol)(implicit ctx: Context): Type = {
if (child.isTerm && child.is(Case, butNot = Module)) return child.termRef // enum vals always match

// <local child> is a place holder from Scalac, it is hopeless to instantiate it.
//
// Quote from scalac (from nsc/symtab/classfile/Pickler.scala):
//
// ...When a sealed class/trait has local subclasses, a single
// <local child> class symbol is added as pickled child
// (instead of a reference to the anonymous class; that was done
// initially, but seems not to work, ...).
//
if (child.name == tpnme.LOCAL_CHILD) return child.typeRef

val childTp = if (child.isTerm) child.termRef else child.typeRef

instantiate(childTp, parent)(ctx.fresh.setNewTyperState()).dealias
}

/** Instantiate type `tp1` to be a subtype of `tp2`
*
* Return the instantiated type if type parameters and this type
* in `tp1` can be instantiated such that `tp1 <:< tp2`.
*
* Otherwise, return NoType.
*/
private def instantiate(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = {
/** expose abstract type references to their bounds or tvars according to variance */
class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
def expose(lo: Type, hi: Type): Type =
if (variance == 0)
newTypeVar(TypeBounds(lo, hi))
else if (variance == 1)
if (maximize) hi else lo
else
if (maximize) lo else hi

def apply(tp: Type): Type = tp match {
case tp: TypeRef if isBounds(tp.underlying) =>
val lo = this(tp.info.loBound)
val hi = this(tp.info.hiBound)
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
val exposed = expose(lo, hi)
typr.println(s"$tp exposed to =====> $exposed")
exposed

case AppliedType(tycon: TypeRef, args) if isBounds(tycon.underlying) =>
val args2 = args.map(this)
val lo = this(tycon.info.loBound).applyIfParameterized(args2)
val hi = this(tycon.info.hiBound).applyIfParameterized(args2)
val exposed = expose(lo, hi)
typr.println(s"$tp exposed to =====> $exposed")
exposed

case _ =>
mapOver(tp)
}
}

def minTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = false)
def maxTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = true)

// Fix subtype checking for child instantiation,
// such that `Foo(Test.this.foo) <:< Foo(Foo.this)`
// See tests/patmat/i3938.scala
class RemoveThisMap extends TypeMap {
var prefixTVar: Type = null
def apply(tp: Type): Type = tp match {
case ThisType(tref: TypeRef) if !tref.symbol.isStaticOwner =>
if (tref.symbol.is(Module))
TermRef(this(tref.prefix), tref.symbol.sourceModule)
else if (prefixTVar != null)
this(tref)
else {
prefixTVar = WildcardType // prevent recursive call from assigning it
prefixTVar = newTypeVar(TypeBounds.upper(this(tref)))
prefixTVar
}
case tp => mapOver(tp)
}
}

// replace uninstantiated type vars with WildcardType, check tests/patmat/3333.scala
def instUndetMap(implicit ctx: Context) = new TypeMap {
def apply(t: Type): Type = t match {
case tvar: TypeVar if !tvar.isInstantiated => WildcardType(tvar.origin.underlying.bounds)
case _ => mapOver(t)
}
}

val removeThisType = new RemoveThisMap
val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
val protoTp1 = removeThisType.apply(tp1).appliedTo(tvars)

val force = new ForceDegree.Value(
tvar =>
!(ctx.typerState.constraint.entry(tvar.origin) `eq` tvar.origin.underlying) ||
(tvar `eq` removeThisType.prefixTVar),
minimizeAll = false,
allowBottom = false
)

// If parent contains a reference to an abstract type, then we should
// refine subtype checking to eliminate abstract types according to
// variance. As this logic is only needed in exhaustivity check,
// we manually patch subtyping check instead of changing TypeComparer.
// See tests/patmat/i3645b.scala
def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent =>
implicit val ictx = ctx.fresh.setNewTyperState()
parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2)
}

if (protoTp1 <:< tp2) {
if (isFullyDefined(protoTp1, force)) protoTp1
else instUndetMap.apply(protoTp1)
}
else {
val protoTp2 = maxTypeMap.apply(tp2)
if (protoTp1 <:< protoTp2 || parentQualify) {
if (isFullyDefined(AndType(protoTp1, protoTp2), force)) protoTp1
else instUndetMap.apply(protoTp1)
}
else {
typr.println(s"$protoTp1 <:< $protoTp2 = false")
NoType
}
}
}
}