Skip to content

Commit

Permalink
Trust case class unapplies to produce checkable type tests
Browse files Browse the repository at this point in the history
Note that in the case where the unapply cannot match, exhaustivity
checker will issue a warning.
  • Loading branch information
abgruszecki committed May 22, 2019
1 parent 3bcaf1d commit 92c23ee
Show file tree
Hide file tree
Showing 2 changed files with 20 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.Key

/** 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

object TrustedTypeTestKey extends Key[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

0 comments on commit 92c23ee

Please sign in to comment.