Skip to content

Commit

Permalink
Merge pull request #6551 from dotty-staging/trust-case-class-unapply
Browse files Browse the repository at this point in the history
Fix (part of) #6323: trust case class unapplies to produce checkable type tests
  • Loading branch information
abgruszecki authored Jun 6, 2019
2 parents 49dd34d + 94cff4a commit 3cf3dd3
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 10 deletions.
27 changes: 18 additions & 9 deletions compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName}
import config.Printers.patmatch
import reporting.diagnostic.messages._
import dotty.tools.dotc.ast._
import util.Property._

/** The pattern matching transform.
* After this phase, the only Match nodes remaining in the code are simple switches
Expand Down Expand Up @@ -52,6 +53,8 @@ object PatternMatcher {
/** Minimal number of cases to emit a switch */
final val MinSwitchCases = 4

val TrustedTypeTestKey: Key[Unit] = new StickyKey[Unit]

/** Was symbol generated by pattern matcher? */
def isPatmatGenerated(sym: Symbol)(implicit ctx: Context): Boolean =
sym.is(Synthetic) && sym.name.is(PatMatStdBinderName)
Expand Down Expand Up @@ -153,24 +156,24 @@ object PatternMatcher {

/** The different kinds of tests */
sealed abstract class Test
case class TypeTest(tpt: Tree) extends Test { // scrutinee.isInstanceOf[tpt]
case class TypeTest(tpt: Tree, trusted: Boolean) extends Test { // scrutinee.isInstanceOf[tpt]
override def equals(that: Any): Boolean = that match {
case that: TypeTest => this.tpt.tpe =:= that.tpt.tpe
case _ => false
}
override def hashCode: Int = tpt.tpe.hash
}
case class EqualTest(tree: Tree) extends Test { // scrutinee == tree
case class EqualTest(tree: Tree) extends Test { // scrutinee == tree
override def equals(that: Any): Boolean = that match {
case that: EqualTest => this.tree === that.tree
case _ => false
}
override def hashCode: Int = tree.hash
}
case class LengthTest(len: Int, exact: Boolean) extends Test // scrutinee (== | >=) len
case object NonEmptyTest extends Test // !scrutinee.isEmpty
case object NonNullTest extends Test // scrutinee ne null
case object GuardTest extends Test // scrutinee
case class LengthTest(len: Int, exact: Boolean) extends Test // scrutinee (== | >=) len
case object NonEmptyTest extends Test // !scrutinee.isEmpty
case object NonNullTest extends Test // scrutinee ne null
case object GuardTest extends Test // scrutinee

// ------- Generating plans from trees ------------------------

Expand Down Expand Up @@ -352,7 +355,12 @@ object PatternMatcher {
// begin patternPlan
swapBind(tree) match {
case Typed(pat, tpt) =>
TestPlan(TypeTest(tpt), scrutinee, tree.span,
val isTrusted = pat match {
case UnApply(extractor, _, _) =>
extractor.symbol.is(Synthetic) && extractor.symbol.owner.linkedClass.is(Case)
case _ => false
}
TestPlan(TypeTest(tpt, isTrusted), scrutinee, tree.span,
letAbstract(ref(scrutinee).cast(tpt.tpe)) { casted =>
nonNull += casted
patternPlan(casted, pat, onSuccess)
Expand Down Expand Up @@ -685,7 +693,7 @@ object PatternMatcher {
.select(defn.Seq_length.matchingMember(scrutinee.tpe))
.select(if (exact) defn.Int_== else defn.Int_>=)
.appliedTo(Literal(Constant(len)))
case TypeTest(tpt) =>
case TypeTest(tpt, trusted) =>
val expectedTp = tpt.tpe

// An outer test is needed in a situation like `case x: y.Inner => ...`
Expand Down Expand Up @@ -716,6 +724,7 @@ object PatternMatcher {
scrutinee.isInstance(expectedTp) // will be translated to an equality test
case _ =>
val typeTest = scrutinee.select(defn.Any_typeTest).appliedToType(expectedTp)
if (trusted) typeTest.pushAttachment(TrustedTypeTestKey, ())
if (outerTestNeeded) typeTest.and(outerTest) else typeTest
}
}
Expand Down Expand Up @@ -899,7 +908,7 @@ object PatternMatcher {
val seen = mutable.Set[Int]()
def showTest(test: Test) = test match {
case EqualTest(tree) => i"EqualTest($tree)"
case TypeTest(tpt) => i"TypeTest($tpt)"
case TypeTest(tpt, trusted) => i"TypeTest($tpt, trusted=$trusted)"
case _ => test.toString
}
def showPlan(plan: Plan): Unit =
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@ object TypeTestsCasts {

if (sym.isTypeTest) {
val argType = tree.args.head.tpe
if (!checkable(expr.tpe, argType, tree.span))
val isTrusted = tree.getAttachment(PatternMatcher.TrustedTypeTestKey).nonEmpty
if (!isTrusted && !checkable(expr.tpe, argType, tree.span))
ctx.warning(i"the type test for $argType cannot be checked at runtime", tree.sourcePos)
transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
object Test {
sealed trait Foo[A, B]
final case class Bar[X](x: X) extends Foo[X, X]

def foo[A, B](value: Foo[A, B], a: A => Int): B = value match {
case Bar(x) => a(x); x
}

def bar[A, B](value: Foo[A, B], a: A => Int): B = value match {
case b: Bar[a] => b.x
}

def err1[A, B](value: Foo[A, B], a: A => Int): B = value match {
case b: Bar[A] => // spurious // error
b.x
}

def err2[A, B](value: Foo[A, B], a: A => Int): B = value match {
case b: Bar[B] => // spurious // error
b.x
}

def fail[A, B](value: Foo[A, B], a: A => Int): B = value match {
case b: Bar[Int] => // error
b.x
}
}

0 comments on commit 3cf3dd3

Please sign in to comment.