package dotty.tools.dotc
package transform
import dotty.tools.dotc.util.Positions._
import TreeTransforms.{MiniPhaseTransform, TransformerInfo}
import core._
import Contexts.Context, Types._, Constants._, Decorators._, Symbols._
import TypeUtils._, TypeErasure._, Flags._
/** Implements partial evaluation of `sc.isInstanceOf[Sel]` according to:
*
* | Sel\sc | trait | class | final class |
* | ----------: | :------------------------: | :------------------------: | :--------------: |
* | trait | ? | ? | statically known |
* | class | ? | false if classes unrelated | statically known |
* | final class | false if classes unrelated | false if classes unrelated | statically known |
*
* This is a generalized solution to raising an error on unreachable match
* cases and warnings on other statically known results of `isInstanceOf`.
*
* Steps taken:
*
* 1. `evalTypeApply` will establish the matrix and choose the appropriate
* handling for the case:
* - Sel/sc is a value class or scrutinee is `Any`
* - `handleStaticallyKnown`
* - `falseIfUnrelated` with `scrutinee <:< selector`
* - `handleFalseUnrelated`
* - leave as is (`happens`)
* 2. Rewrite according to steps taken in 1
*/
class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer =>
import dotty.tools.dotc.ast.tpd._
val phaseName = "isInstanceOfEvaluator"
/** Transforms a [TypeApply](dotty.tools.dotc.ast.Trees.TypeApply) in order to
* evaluate an `isInstanceOf` check according to the rules defined above.
*/
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = {
val defn = ctx.definitions
/** Handles the four cases of statically known `isInstanceOf`s and gives
* the correct warnings, or an error if statically known to be false in
* match
*/
def handleStaticallyKnown(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree = {
val scrutineeSubSelector = scrutinee <:< selector
if (!scrutineeSubSelector && inMatch) {
ctx.error(
s"this case is unreachable due to `${selector.show}` not being a subclass of `${scrutinee.show}`",
Position(pos.start - 5, pos.end - 5)
)
rewrite(select, to = false)
} else if (!scrutineeSubSelector && !inMatch) {
ctx.warning(
s"this will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}` (will be optimized away)",
pos
)
rewrite(select, to = false)
} else if (scrutineeSubSelector && !inMatch) {
ctx.warning(
s"this will always yield true if the scrutinee is non-null, since `${scrutinee.show}` is a subclass of `${selector.show}` (will be optimized away)",
pos
)
rewrite(select, to = true)
} else /* if (scrutineeSubSelector && inMatch) */ rewrite(select, to = true)
}
/** Rewrites cases with unrelated types */
def handleFalseUnrelated(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean) =
if (inMatch) {
ctx.error(
s"will never match since `${selector.show}` is not a subclass of `${scrutinee.show}`",
Position(select.pos.start - 5, select.pos.end - 5)
)
rewrite(select, to = false)
} else {
ctx.warning(
s"will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}`",
select.pos
)
rewrite(select, to = false)
}
/** Rewrites the select to a boolean if `to` is false or if the qualifier
* is a value class.
*
* If `to` is set to true and the qualifier is not a primitive, the
* instanceOf is replaced by a null check, since:
*
* `scrutinee.isInstanceOf[Selector]` if `scrutinee eq null`
*/
def rewrite(tree: Select, to: Boolean): Tree =
if (!to || !tree.qualifier.tpe.widen.derivesFrom(defn.AnyRefAlias)) {
val literal = Literal(Constant(to))
if (!isPureExpr(tree.qualifier)) Block(List(tree.qualifier), literal)
else literal
} else
Apply(tree.qualifier.select(defn.Object_ne), List(Literal(Constant(null))))
/** Attempts to rewrite TypeApply to either `scrutinee ne null` or a
* constant
*/
def evalTypeApply(tree: TypeApply): Tree =
if (tree.symbol != defn.Any_isInstanceOf) tree
else tree.fun match {
case s: Select => {
val scrutinee = erasure(s.qualifier.tpe.widen)
val selector = erasure(tree.args.head.tpe.widen)
val scTrait = scrutinee.typeSymbol is Trait
val scClass =
scrutinee.typeSymbol.isClass &&
!(scrutinee.typeSymbol is Trait) &&
!(scrutinee.typeSymbol is Module)
val scClassNonFinal = scClass && !(scrutinee.typeSymbol is Final)
val scFinalClass = scClass && (scrutinee.typeSymbol is Final)
val selTrait = selector.typeSymbol is Trait
val selClass =
selector.typeSymbol.isClass &&
!(selector.typeSymbol is Trait) &&
!(selector.typeSymbol is Module)
val selClassNonFinal = selClass && !(selector.typeSymbol is Final)
val selFinalClass = selClass && (selector.typeSymbol is Final)
// Cases ---------------------------------
val valueClassesOrAny =
ValueClasses.isDerivedValueClass(scrutinee.typeSymbol) ||
ValueClasses.isDerivedValueClass(selector.typeSymbol) ||
scrutinee == defn.ObjectType
val knownStatically = scFinalClass
val falseIfUnrelated =
(scClassNonFinal && selClassNonFinal) ||
(scClassNonFinal && selFinalClass) ||
(scTrait && selFinalClass)
val happens =
(scClassNonFinal && selClassNonFinal) ||
(scTrait && selClassNonFinal) ||
(scTrait && selTrait)
val inMatch = s.qualifier.symbol is Case
// FIXME: This will misclassify case objects! We need to find another way to characterize
// isInstanceOfs generated by matches.
// Probably the most robust way is to use another symbol for the isInstanceOf method.
if (valueClassesOrAny) tree
else if (knownStatically)
handleStaticallyKnown(s, scrutinee, selector, inMatch, tree.pos)
else if (falseIfUnrelated && scrutinee <:< selector)
// scrutinee is a subtype of the selector, safe to rewrite
rewrite(s, to = true)
else if (falseIfUnrelated && !(selector <:< scrutinee))
// selector and scrutinee are unrelated
handleFalseUnrelated(s, scrutinee, selector, inMatch)
else if (happens) tree
else tree
}
case _ => tree
}
evalTypeApply(tree)
}
}