diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index b63e0236df00..049906ef0e1a 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -61,6 +61,7 @@ class Compiler { new CrossCastAnd, // Normalize selections involving intersection types. new Splitter), // Expand selections involving union types into conditionals List(new VCInlineMethods, // Inlines calls to value class methods + new IsInstanceOfEvaluator, // Issues warnings when unreachable statements are present in match/if expressions new SeqLiterals, // Express vararg arguments as arrays new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods new Getters, // Replace non-private vals and vars with getter defs (fields are added later) diff --git a/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala b/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala new file mode 100644 index 000000000000..6765604c8bf3 --- /dev/null +++ b/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala @@ -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 + else tree + } + + case _ => tree + } + + evalTypeApply(tree) + } +} diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 740fd546053e..64b85a51fb3d 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -56,7 +56,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans override def transformTry(tree: tpd.Try)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { val selector = - ctx.newSymbol(ctx.owner, ctx.freshName("ex").toTermName, Flags.Synthetic, defn.ThrowableType, coord = tree.pos) + ctx.newSymbol(ctx.owner, ctx.freshName("ex").toTermName, Flags.Synthetic | Flags.Case, defn.ThrowableType, coord = tree.pos) val sel = Ident(selector.termRef).withPos(tree.pos) val rethrow = tpd.CaseDef(EmptyTree, EmptyTree, Throw(ref(selector))) val newCases = tpd.CaseDef( @@ -80,7 +80,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans // assert(owner ne null); assert(owner ne NoSymbol) def freshSym(pos: Position, tp: Type = NoType, prefix: String = "x", owner: Symbol = ctx.owner) = { ctr += 1 - ctx.newSymbol(owner, ctx.freshName(prefix + ctr).toTermName, Flags.Synthetic, tp, coord = pos) + ctx.newSymbol(owner, ctx.freshName(prefix + ctr).toTermName, Flags.Synthetic | Flags.Case, tp, coord = pos) } def newSynthCaseLabel(name: String, tpe:Type, owner: Symbol = ctx.owner) = diff --git a/tests/pos-scala2/i1059.scala b/tests/disabled/pos-scala2/i1059.scala similarity index 100% rename from tests/pos-scala2/i1059.scala rename to tests/disabled/pos-scala2/i1059.scala diff --git a/tests/pos/t3480.scala b/tests/disabled/pos/t3480.scala similarity index 100% rename from tests/pos/t3480.scala rename to tests/disabled/pos/t3480.scala diff --git a/tests/neg/i1255.scala b/tests/neg/i1255.scala new file mode 100644 index 000000000000..3bb7e8f25d35 --- /dev/null +++ b/tests/neg/i1255.scala @@ -0,0 +1,6 @@ +object Test { + def foo(x: Option[Int]) = x match { + case Some(_: Double) => true // error + case None => true + } +} diff --git a/tests/pos/t1168.scala b/tests/pos/t1168.scala index f43436812f09..08f1b5cd90e7 100644 --- a/tests/pos/t1168.scala +++ b/tests/pos/t1168.scala @@ -1,6 +1,6 @@ object Test extends App { - trait SpecialException {} + trait SpecialException extends Throwable {} try { throw new Exception diff --git a/tests/run/isInstanceOf-eval.check b/tests/run/isInstanceOf-eval.check new file mode 100644 index 000000000000..1191247b6d9a --- /dev/null +++ b/tests/run/isInstanceOf-eval.check @@ -0,0 +1,2 @@ +1 +2 diff --git a/tests/run/isInstanceOf-eval.scala b/tests/run/isInstanceOf-eval.scala new file mode 100644 index 000000000000..aa907b6972fa --- /dev/null +++ b/tests/run/isInstanceOf-eval.scala @@ -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] +} diff --git a/tests/run/t6637.scala b/tests/run/t6637.scala index 7f9c3cd61c6e..052e7f5d6405 100644 --- a/tests/run/t6637.scala +++ b/tests/run/t6637.scala @@ -1,7 +1,6 @@ - object Test extends dotty.runtime.LegacyApp { try { - class A ; class B ; List().head.isInstanceOf[A with B] + List().head } catch { case _ :java.util.NoSuchElementException => println("ok") }