|
| 1 | +package dotty.tools.dotc |
| 2 | +package transform |
| 3 | + |
| 4 | +import dotty.tools.dotc.util.Positions._ |
| 5 | +import TreeTransforms.{MiniPhaseTransform, TransformerInfo} |
| 6 | +import core._ |
| 7 | +import Contexts.Context, Types._, Constants._, Decorators._, Symbols._ |
| 8 | +import TypeUtils._, TypeErasure._, Flags._ |
| 9 | + |
| 10 | + |
| 11 | +/** Implements partial evaluation of `sc.isInstanceOf[Sel]` according to: |
| 12 | + * |
| 13 | + * +-------------+----------------------------+----------------------------+------------------+ |
| 14 | + * | Sel\sc | trait | class | final class | |
| 15 | + * +-------------+----------------------------+----------------------------+------------------+ |
| 16 | + * | trait | ? | ? | statically known | |
| 17 | + * | class | ? | false if classes unrelated | statically known | |
| 18 | + * | final class | false if classes unrelated | false if classes unrelated | statically known | |
| 19 | + * +-------------+----------------------------+----------------------------+------------------+ |
| 20 | + * |
| 21 | + * This is a generalized solution to raising an error on unreachable match |
| 22 | + * cases and warnings on other statically known results of `isInstanceOf`. |
| 23 | + * |
| 24 | + * Steps taken: |
| 25 | + * |
| 26 | + * 1. evalTypeApply will establish the matrix and choose the appropriate |
| 27 | + * handling for the case: |
| 28 | + * 2. a) Sel/sc is a value class or scrutinee is `Any` |
| 29 | + * b) handleStaticallyKnown |
| 30 | + * c) falseIfUnrelated with `scrutinee <:< selector` |
| 31 | + * d) handleFalseUnrelated |
| 32 | + * e) leave as is (aka `happens`) |
| 33 | + * 3. Rewrite according to step taken in `2` |
| 34 | + */ |
| 35 | +class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer => |
| 36 | + |
| 37 | + import dotty.tools.dotc.ast.tpd._ |
| 38 | + |
| 39 | + def phaseName = "isInstanceOfEvaluator" |
| 40 | + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = { |
| 41 | + val defn = ctx.definitions |
| 42 | + |
| 43 | + /** Handles the four cases of statically known `isInstanceOf`s and gives |
| 44 | + * the correct warnings, or an error if statically known to be false in |
| 45 | + * match |
| 46 | + */ |
| 47 | + def handleStaticallyKnown(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree = { |
| 48 | + val scrutineeSubSelector = scrutinee <:< selector |
| 49 | + if (!scrutineeSubSelector && inMatch) { |
| 50 | + ctx.error( |
| 51 | + s"this case is unreachable due to `${selector.show}` not being a subclass of `${scrutinee.show}`", |
| 52 | + Position(pos.start - 5, pos.end - 5) |
| 53 | + ) |
| 54 | + rewrite(select, to = false) |
| 55 | + } else if (!scrutineeSubSelector && !inMatch) { |
| 56 | + ctx.warning( |
| 57 | + s"this will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}` (will be optimized away)", |
| 58 | + pos |
| 59 | + ) |
| 60 | + rewrite(select, to = false) |
| 61 | + } else if (scrutineeSubSelector && !inMatch) { |
| 62 | + ctx.warning( |
| 63 | + 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)", |
| 64 | + pos |
| 65 | + ) |
| 66 | + rewrite(select, to = true) |
| 67 | + } else /* if (scrutineeSubSelector && inMatch) */ rewrite(select, to = true) |
| 68 | + } |
| 69 | + |
| 70 | + /** Rewrites cases with unrelated types */ |
| 71 | + def handleFalseUnrelated(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean) = |
| 72 | + if (inMatch) { |
| 73 | + ctx.error( |
| 74 | + s"will never match since `${selector.show}` is not a subclass of `${scrutinee.show}`", |
| 75 | + Position(select.pos.start - 5, select.pos.end - 5) |
| 76 | + ) |
| 77 | + rewrite(select, to = false) |
| 78 | + } else { |
| 79 | + ctx.warning( |
| 80 | + s"will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}`", |
| 81 | + select.pos |
| 82 | + ) |
| 83 | + rewrite(select, to = false) |
| 84 | + } |
| 85 | + |
| 86 | + /** Rewrites the select to a boolean if `to` is false or if the qualifier |
| 87 | + * is a value class. |
| 88 | + * |
| 89 | + * If `to` is set to true and the qualifier is not a primitive, the |
| 90 | + * instanceOf is replaced by a null check, since: |
| 91 | + * |
| 92 | + * `scrutinee.isInstanceOf[Selector]` if `scrutinee eq null` |
| 93 | + */ |
| 94 | + def rewrite(tree: Select, to: Boolean): Tree = |
| 95 | + if (!to || !tree.qualifier.tpe.widen.derivesFrom(defn.AnyRefAlias)) { |
| 96 | + val literal = Literal(Constant(to)) |
| 97 | + if (!isPureExpr(tree.qualifier)) Block(List(tree.qualifier), literal) |
| 98 | + else literal |
| 99 | + } else |
| 100 | + Apply(tree.qualifier.select(defn.Object_ne), List(Literal(Constant(null)))) |
| 101 | + |
| 102 | + /** Attempts to rewrite TypeApply to either `scrutinee ne null` or a |
| 103 | + * constant |
| 104 | + */ |
| 105 | + def evalTypeApply(tree: TypeApply): Tree = |
| 106 | + if (tree.symbol != defn.Any_isInstanceOf) tree |
| 107 | + else tree.fun match { |
| 108 | + case s: Select => { |
| 109 | + val scrutinee = erasure(s.qualifier.tpe.widen) |
| 110 | + val selector = erasure(tree.args.head.tpe.widen) |
| 111 | + |
| 112 | + val scTrait = scrutinee.typeSymbol is Trait |
| 113 | + val scClass = |
| 114 | + scrutinee.typeSymbol.isClass && |
| 115 | + !(scrutinee.typeSymbol is Trait) && |
| 116 | + !(scrutinee.typeSymbol is Module) |
| 117 | + |
| 118 | + val scClassNonFinal = scClass && !(scrutinee.typeSymbol is Final) |
| 119 | + val scFinalClass = scClass && (scrutinee.typeSymbol is Final) |
| 120 | + |
| 121 | + val selTrait = selector.typeSymbol is Trait |
| 122 | + val selClass = |
| 123 | + selector.typeSymbol.isClass && |
| 124 | + !(selector.typeSymbol is Trait) && |
| 125 | + !(selector.typeSymbol is Module) |
| 126 | + |
| 127 | + val selClassNonFinal = scClass && !(selector.typeSymbol is Final) |
| 128 | + val selFinalClass = scClass && (selector.typeSymbol is Final) |
| 129 | + |
| 130 | + // Cases --------------------------------- |
| 131 | + val valueClassesOrAny = |
| 132 | + ValueClasses.isDerivedValueClass(scrutinee.typeSymbol) || |
| 133 | + ValueClasses.isDerivedValueClass(selector.typeSymbol) || |
| 134 | + scrutinee == defn.ObjectType |
| 135 | + |
| 136 | + val knownStatically = scFinalClass |
| 137 | + |
| 138 | + val falseIfUnrelated = |
| 139 | + (scClassNonFinal && selClassNonFinal) || |
| 140 | + (scClassNonFinal && selFinalClass) || |
| 141 | + (scTrait && selFinalClass) |
| 142 | + |
| 143 | + val happens = |
| 144 | + (scClassNonFinal && selClassNonFinal) || |
| 145 | + (scTrait && selClassNonFinal) || |
| 146 | + (scTrait && selTrait) |
| 147 | + |
| 148 | + val inMatch = s.qualifier.symbol is Case |
| 149 | + |
| 150 | + if (valueClassesOrAny) tree |
| 151 | + else if (knownStatically) |
| 152 | + handleStaticallyKnown(s, scrutinee, selector, inMatch, tree.pos) |
| 153 | + else if (falseIfUnrelated && scrutinee <:< selector) |
| 154 | + // scrutinee is a subtype of the selector, safe to rewrite |
| 155 | + rewrite(s, to = true) |
| 156 | + else if (falseIfUnrelated && !(selector <:< scrutinee)) |
| 157 | + // selector and scrutinee are unrelated |
| 158 | + handleFalseUnrelated(s, scrutinee, selector, inMatch) |
| 159 | + else if (happens) tree |
| 160 | + else tree |
| 161 | + } |
| 162 | + |
| 163 | + case _ => tree |
| 164 | + } |
| 165 | + |
| 166 | + evalTypeApply(tree) |
| 167 | + } |
| 168 | +} |
0 commit comments