-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add partial evaluation of isInstanceOf
mini-phase
#1275
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
Changes from all commits
7930293
efee4fa
a06d2ab
6b48e99
fc419f5
fa81e54
73f099b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
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: | ||
* 2. a) Sel/sc is a value class or scrutinee is `Any` | ||
* b) handleStaticallyKnown | ||
* c) falseIfUnrelated with `scrutinee <:< selector` | ||
* d) handleFalseUnrelated | ||
* e) leave as is (aka `happens`) | ||
* 3. Rewrite according to step taken in `2` | ||
*/ | ||
class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer => | ||
|
||
import dotty.tools.dotc.ast.tpd._ | ||
|
||
def phaseName = "isInstanceOfEvaluator" | ||
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 = scClass && !(selector.typeSymbol is Final) | ||
val selFinalClass = scClass && (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 | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's no difference between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, it was kept to showcase all cases - but I'll change it to and note this in a comment. |
||
else tree | ||
} | ||
|
||
case _ => tree | ||
} | ||
|
||
evalTypeApply(tree) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
object Test { | ||
def foo(x: Option[Int]) = x match { | ||
case Some(_: Double) => true // error | ||
case None => true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
1 | ||
2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
object Test extends dotty.runtime.LegacyApp { | ||
lazy val any = { | ||
println(1) | ||
1: Any | ||
} | ||
|
||
any.isInstanceOf[Int] | ||
|
||
lazy val int = { | ||
println(2) | ||
2 | ||
} | ||
|
||
int.isInstanceOf[Int] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,6 @@ | ||
|
||
object Test extends dotty.runtime.LegacyApp { | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why was this test deleted? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this test The new mini-phase will report an error here, as such the test will fail because it won't compile and Of course, the mini-phase could just warn here...decide what you think is best and I'll adjust the PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe it would be better to remove the |
||
class A ; class B ; List().head.isInstanceOf[A with B] | ||
List().head | ||
} catch { | ||
case _ :java.util.NoSuchElementException => println("ok") | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is a bit weird
rewrite(tree, to = true)
does not meanRewrite tree to true
it meansRewrite tree to true unless the type is nullable