Skip to content

Fix #3938: adapt prefix to ease prefix inference #3939

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 2 commits into from
Jan 30, 2018
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
55 changes: 30 additions & 25 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {

debug.println(s"bases of ${tp1.show}: " + bases1)
debug.println(s"bases of ${tp2.show}: " + bases2)
debug.println(s"${tp1.show} <:< ${tp2.show} : " + (tp1 <:< tp2))
debug.println(s"${tp2.show} <:< ${tp1.show} : " + (tp2 <:< tp1))

val noClassConflict =
bases1.forall(sym1 => sym1.is(Trait) || bases2.forall(sym2 => sym2.is(Trait) || sym1.isSubClass(sym2))) ||
Expand Down Expand Up @@ -595,9 +597,9 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
* Otherwise, return NoType.
*
*/
def instantiate(tp1: Type, tp2: Type)(implicit ctx: Context): Type = {
def instantiate(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = {
// expose abstract type references to their bounds or tvars according to variance
abstract class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap {
def expose(tp: TypeRef): Type = {
val lo = this(tp.info.loBound)
val hi = this(tp.info.hiBound)
Expand All @@ -613,7 +615,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
exposed
}

override def mapOver(tp: Type): Type = tp match {
def apply(tp: Type): Type = tp match {
case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] =>
// See tests/patmat/gadt.scala tests/patmat/exhausting.scala tests/patmat/t9657.scala
expose(tp)
Expand All @@ -634,28 +636,31 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
exposed

case _ =>
super.mapOver(tp)
}
}

// We are checking the possibility of `tp1 <:< tp2`, thus we should
// minimize `tp1` while maximizing `tp2`. See tests/patmat/3645b.scala
def childTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = false) {
def apply(t: Type): Type = t.dealias match {
// map `ThisType` of `tp1` to a type variable
// precondition: `tp1` should have the same shape as `path.Child`, thus `ThisType` is always covariant
case tp @ ThisType(tref) if !tref.symbol.isStaticOwner =>
if (tref.symbol.is(Module)) this(tref)
else newTypeVar(TypeBounds.upper(tp.underlying))

case tp =>
mapOver(tp)
}
}

// replace type parameter references with bounds
def parentTypeMap(implicit ctx: Context) = new AbstractTypeMap(maximize = true) {
def apply(tp: Type): Type = mapOver(tp.dealias)
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
def removeThisType(implicit ctx: Context) = new TypeMap {
// is in tvarBounds? Don't create new tvars if true
private var tvarBounds: Boolean = false
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 (tvarBounds)
this(tref)
else {
tvarBounds = true
newTypeVar(TypeBounds.upper(this(tref)))
}
case tp => mapOver(tp)
}
}

// replace uninstantiated type vars with WildcardType, check tests/patmat/3333.scala
Expand All @@ -672,24 +677,24 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
)

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

// 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/3645b.scala
// See tests/patmat/i3645b.scala
def parentQualify = tp1.widen.classSymbol.info.parents.exists { parent =>
implicit val ictx = ctx.fresh.setNewTyperState()
parent.argInfos.nonEmpty && childTypeMap.apply(parent) <:< parentTypeMap.apply(tp2)
parent.argInfos.nonEmpty && minTypeMap.apply(parent) <:< maxTypeMap.apply(tp2)
}

if (protoTp1 <:< tp2) {
if (isFullyDefined(protoTp1, force)) protoTp1
else instUndetMap.apply(protoTp1)
}
else {
val protoTp2 = parentTypeMap.apply(tp2)
val protoTp2 = maxTypeMap.apply(tp2)
if (protoTp1 <:< protoTp2 || parentQualify) {
if (isFullyDefined(AndType(protoTp1, protoTp2), force)) protoTp1
else instUndetMap.apply(protoTp1)
Expand Down
32 changes: 32 additions & 0 deletions tests/patmat/i3938.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* subtyping logs
==> isSubType Test.this.foo.Bar(Test.this.foo.bar) <:< Foo.this.Bar?
==> isSubType Test.this.foo.Bar <:< Foo.this.Bar?
==> isSubType Foo(Test.this.foo) <:< Foo(Foo.this)?
==> isSubType Foo <:< Foo(Foo.this)?
<== isSubType Foo <:< Foo(Foo.this) = false
<== isSubType Foo(Test.this.foo) <:< Foo(Foo.this) = false
<== isSubType Test.this.foo.Bar <:< Foo.this.Bar = false
<== isSubType Test.this.foo.Bar(Test.this.foo.bar) <:< Foo.this.Bar = false
*/


class Foo {
val bar = new Bar
class Bar {
sealed abstract class A
case class B() extends A
case class C() extends A
}
}

class Test {
val foo = new Foo
import foo.bar._

def test(a: A) = {
a match {
case B() => 1
case _ => 2 // unreachable code
}
}
}