Skip to content

Merge isInstanceOfEvaluator with TypeTestCasts #2820

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

Merged
merged 6 commits into from
Jul 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ 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)
Expand Down
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
override def TypeApply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): TypeApply =
ta.assignType(untpd.cpy.TypeApply(tree)(fun, args), fun, args)
// Same remark as for Apply

override def Closure(tree: Tree)(env: List[Tree], meth: Tree, tpt: Tree)(implicit ctx: Context): Closure =
ta.assignType(untpd.cpy.Closure(tree)(env, meth, tpt), meth, tpt)

Expand Down Expand Up @@ -770,6 +770,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
else if (!ctx.erasedTypes) asInstance(tp)
else Erasure.Boxing.adaptToType(tree, tp)

/** `tree ne null` (might need a cast to be type correct) */
def testNotNull(implicit ctx: Context): Tree =
tree.ensureConforms(defn.ObjectType)
.select(defn.Object_ne).appliedTo(Literal(Constant(null)))

/** If inititializer tree is `_', the default value of its type,
* otherwise the tree itself.
*/
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ class Definitions {
lazy val Any_getClass = enterMethod(AnyClass, nme.getClass_, MethodType(Nil, ClassClass.typeRef.appliedTo(TypeBounds.empty)), Final)
lazy val Any_isInstanceOf = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOf_, _ => BooleanType, Final)
lazy val Any_asInstanceOf = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOf_, TypeParamRef(_, 0), Final)
lazy val Any_typeTest = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final)
// generated by pattern matcher, eliminated by erasure

def AnyMethods = List(Any_==, Any_!=, Any_equals, Any_hashCode,
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ object StdNames {
val isDefined: N = "isDefined"
val isEmpty: N = "isEmpty"
val isInstanceOf_ : N = "isInstanceOf"
val isInstanceOfPM: N = "$isInstanceOf$"
val java: N = "java"
val key: N = "key"
val lang: N = "lang"
Expand Down
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,15 @@ object Types {
* For the moment this is only true for modules, but it could
* be refined later.
*/
final def isNotNull(implicit ctx: Context): Boolean =
classSymbol is ModuleClass
final def isNotNull(implicit ctx: Context): Boolean = this match {
case tp: ConstantType => tp.value.value != null
case tp: ClassInfo => !tp.cls.isNullableClass && tp.cls != defn.NothingClass
case tp: TypeBounds => tp.lo.isNotNull
case tp: TypeProxy => tp.underlying.isNotNull
case AndType(tp1, tp2) => tp1.isNotNull || tp2.isNotNull
case OrType(tp1, tp2) => tp1.isNotNull && tp2.isNotNull
case _ => false
}

/** Is this type produced as a repair for an error? */
final def isError(implicit ctx: Context): Boolean = stripTypeVar match {
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ object Erasure extends TypeTestsCasts{
}

def constant(tree: Tree, const: Tree)(implicit ctx: Context) =
if (isPureExpr(tree)) const else Block(tree :: Nil, const)
(if (isPureExpr(tree)) const else Block(tree :: Nil, const))
.withPos(tree.pos)

final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = ctx.traceIndented(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") {
tree.tpe.widen match {
Expand Down
185 changes: 112 additions & 73 deletions compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package dotty.tools.dotc
package transform

import core.Contexts._
import core.Symbols._
import core.Types._
import core.Constants._
import core.StdNames._
import core.TypeErasure.isUnboundedGeneric
import core._
import Contexts._, Symbols._, Types._, Constants._, StdNames._, Decorators._
import ast.Trees._
import Erasure.Boxing._
import core.TypeErasure._
import TypeErasure._
import ValueClasses._
import core.Flags._
import util.Positions._


/** This transform normalizes type tests and type casts,
* also replacing type tests with singleton argument type with reference equality check
Expand All @@ -26,100 +25,140 @@ import ValueClasses._
trait TypeTestsCasts {
import ast.tpd._

// override def phaseName: String = "typeTestsCasts"

def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = ctx.traceIndented(s"transforming ${tree.show}", show = true) {
tree.fun match {
case fun @ Select(qual, selector) =>
case fun @ Select(expr, selector) =>
val sym = tree.symbol

def isPrimitive(tp: Type) = tp.classSymbol.isPrimitiveValueClass

def derivedTree(qual1: Tree, sym: Symbol, tp: Type) =
cpy.TypeApply(tree)(qual1.select(sym).withPos(qual.pos), List(TypeTree(tp)))

def qualCls = qual.tpe.widen.classSymbol

def transformIsInstanceOf(expr:Tree, argType: Type): Tree = {
def argCls = argType.classSymbol
if ((expr.tpe <:< argType) && isPureExpr(expr))
Literal(Constant(true)) withPos tree.pos
else if (argCls.isPrimitiveValueClass)
if (qualCls.isPrimitiveValueClass) Literal(Constant(qualCls == argCls)) withPos tree.pos
else transformIsInstanceOf(expr, defn.boxedType(argCls.typeRef))
else argType.dealias match {
case _: SingletonType =>
val cmpOp = if (argType derivesFrom defn.AnyValClass) defn.Any_equals else defn.Object_eq
expr.select(cmpOp).appliedTo(singleton(argType))
case AndType(tp1, tp2) =>
evalOnce(expr) { fun =>
val erased1 = transformIsInstanceOf(fun, tp1)
val erased2 = transformIsInstanceOf(fun, tp2)
erased1 match {
case Literal(Constant(true)) => erased2
case _ =>
erased2 match {
case Literal(Constant(true)) => erased1
case _ => erased1 and erased2
}
}
def derivedTree(expr1: Tree, sym: Symbol, tp: Type) =
cpy.TypeApply(tree)(expr1.select(sym).withPos(expr.pos), List(TypeTree(tp)))

def foundCls = expr.tpe.widen.classSymbol
// println(i"ta $tree, found = $foundCls")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgotten removal - or leave in as a nice point for debug?


def inMatch =
fun.symbol == defn.Any_typeTest || // new scheme
expr.symbol.is(Case) // old scheme

def transformIsInstanceOf(expr:Tree, testType: Type, flagUnrelated: Boolean): Tree = {
def testCls = testType.classSymbol

def unreachable(why: => String) =
if (flagUnrelated)
if (inMatch) ctx.error(em"this case is unreachable since $why", expr.pos)
else ctx.warning(em"this will always yield false since $why", expr.pos)

/** Are `foundCls` and `testCls` classes that allow checks
* whether a test would be always false?
*/
def isCheckable =
foundCls.isClass && testCls.isClass &&
!(testCls.isPrimitiveValueClass && !foundCls.isPrimitiveValueClass) &&
// if `test` is primitive but `found` is not, we might have a case like
// found = java.lang.Integer, test = Int, which could be true
// (not sure why that is so, but scalac behaves the same way)
!isDerivedValueClass(foundCls) && !isDerivedValueClass(testCls)
// we don't have the logic to handle derived value classes

/** Check whether a runtime test that a value of `foundCls` can be a `testCls`
* can be true in some cases. Issure a warning or an error if that's not the case.
*/
def checkSensical: Boolean =
if (!isCheckable) true
else if (foundCls.isPrimitiveValueClass && !testCls.isPrimitiveValueClass) {
ctx.error("cannot test if value types are references", tree.pos)
false
}
case defn.MultiArrayOf(elem, ndims) if isUnboundedGeneric(elem) =>
def isArrayTest(arg: Tree) =
ref(defn.runtimeMethodRef(nme.isArray)).appliedTo(arg, Literal(Constant(ndims)))
if (ndims == 1) isArrayTest(qual)
else evalOnce(qual) { qual1 =>
derivedTree(qual1, defn.Any_isInstanceOf, qual1.tpe) and isArrayTest(qual1)
else if (!foundCls.derivesFrom(testCls)) {
if (foundCls.is(Final)) {
unreachable(i"$foundCls is not a subclass of $testCls")
false
}
case _ =>
derivedTree(expr, defn.Any_isInstanceOf, argType)
}
else if (!testCls.derivesFrom(foundCls) &&
(testCls.is(Final) ||
!testCls.is(Trait) && !foundCls.is(Trait))) {
unreachable(i"$foundCls and $testCls are unrelated")
false
}
else true
}
else true

if (expr.tpe <:< testType)
if (expr.tpe.isNotNull) {
ctx.warning(
em"this will always yield true, since `$foundCls` is a subclass of `$testCls`",
expr.pos)
constant(expr, Literal(Constant(true)))
}
else expr.testNotNull
else if (!checkSensical)
constant(expr, Literal(Constant(false)))
else if (testCls.isPrimitiveValueClass)
if (foundCls.isPrimitiveValueClass)
constant(expr, Literal(Constant(foundCls == testCls)))
else
transformIsInstanceOf(expr, defn.boxedType(testCls.typeRef), flagUnrelated)
else
derivedTree(expr, defn.Any_isInstanceOf, testType)
}

def transformAsInstanceOf(argType: Type): Tree = {
def argCls = argType.widen.classSymbol
if (qual.tpe <:< argType)
Typed(qual, tree.args.head)
else if (qualCls.isPrimitiveValueClass) {
if (argCls.isPrimitiveValueClass) primitiveConversion(qual, argCls)
else derivedTree(box(qual), defn.Any_asInstanceOf, argType)
def transformAsInstanceOf(testType: Type): Tree = {
def testCls = testType.widen.classSymbol
if (expr.tpe <:< testType)
Typed(expr, tree.args.head)
else if (foundCls.isPrimitiveValueClass) {
if (testCls.isPrimitiveValueClass) primitiveConversion(expr, testCls)
else derivedTree(box(expr), defn.Any_asInstanceOf, testType)
}
else if (argCls.isPrimitiveValueClass)
unbox(qual.ensureConforms(defn.ObjectType), argType)
else if (isDerivedValueClass(argCls)) {
qual // adaptToType in Erasure will do the necessary type adaptation
else if (testCls.isPrimitiveValueClass)
unbox(expr.ensureConforms(defn.ObjectType), testType)
else if (isDerivedValueClass(testCls)) {
expr // adaptToType in Erasure will do the necessary type adaptation
}
else
derivedTree(qual, defn.Any_asInstanceOf, argType)
derivedTree(expr, defn.Any_asInstanceOf, testType)
}

/** Transform isInstanceOf OrType
*
* expr.isInstanceOf[A | B] ~~> expr.isInstanceOf[A] | expr.isInstanceOf[B]
* expr.isInstanceOf[A & B] ~~> expr.isInstanceOf[A] & expr.isInstanceOf[B]
*
* The transform happens before erasure of `argType`, thus cannot be merged
* with `transformIsInstanceOf`, which depends on erased type of `argType`.
* The transform happens before erasure of `testType`, thus cannot be merged
* with `transformIsInstanceOf`, which depends on erased type of `testType`.
*/
def transformTypeTest(qual: Tree, argType: Type): Tree = argType.dealias match {
def transformTypeTest(expr: Tree, testType: Type, flagUnrelated: Boolean): Tree = testType.dealias match {
case _: SingletonType =>
val cmpOp =
if (testType derivesFrom defn.AnyValClass) defn.Any_equals else defn.Object_eq
expr.select(cmpOp).appliedTo(singleton(testType))
case OrType(tp1, tp2) =>
evalOnce(qual) { fun =>
transformTypeTest(fun, tp1)
.select(defn.Boolean_||)
.appliedTo(transformTypeTest(fun, tp2))
evalOnce(expr) { e =>
transformTypeTest(e, tp1, flagUnrelated = false)
.or(transformTypeTest(e, tp2, flagUnrelated = false))
}
case AndType(tp1, tp2) =>
evalOnce(qual) { fun =>
transformTypeTest(fun, tp1)
.select(defn.Boolean_&&)
.appliedTo(transformTypeTest(fun, tp2))
evalOnce(expr) { e =>
transformTypeTest(e, tp1, flagUnrelated)
.and(transformTypeTest(e, tp2, flagUnrelated))
}
case defn.MultiArrayOf(elem, ndims) if isUnboundedGeneric(elem) =>
def isArrayTest(arg: Tree) =
ref(defn.runtimeMethodRef(nme.isArray)).appliedTo(arg, Literal(Constant(ndims)))
if (ndims == 1) isArrayTest(expr)
else evalOnce(expr) { e =>
derivedTree(e, defn.Any_isInstanceOf, e.tpe)
.and(isArrayTest(e))
}
case _ =>
transformIsInstanceOf(qual, erasure(argType))
transformIsInstanceOf(expr, erasure(testType), flagUnrelated)
}

if (sym eq defn.Any_isInstanceOf)
transformTypeTest(qual, tree.args.head.tpe)
if ((sym eq defn.Any_isInstanceOf) || (sym eq defn.Any_typeTest))
transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true)
else if (sym eq defn.Any_asInstanceOf)
transformAsInstanceOf(erasure(tree.args.head.tpe))
else tree
Expand Down
16 changes: 0 additions & 16 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -872,22 +872,6 @@ class RefChecks extends MiniPhase { thisTransformer =>
currentLevel.enterReference(tree.tpe.typeSymbol, tree.pos)
tree
}

override def transformTypeApply(tree: tpd.TypeApply)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
tree.fun match {
case fun@Select(qual, selector) =>
val sym = tree.symbol

if (sym == defn.Any_isInstanceOf) {
val argType = tree.args.head.tpe
val qualCls = qual.tpe.widen.classSymbol
val argCls = argType.classSymbol
if (qualCls.isPrimitiveValueClass && !argCls.isPrimitiveValueClass) ctx.error("isInstanceOf cannot test if value types are references", tree.pos)
}
case _ =>
}
tree
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion compiler/test/dotty/tools/vulpix/TestConfiguration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ object TestConfiguration {
private val yCheckOptions = Array("-Ycheck:tailrec,resolveSuper,mixin,arrayConstructors,labelDef")

val defaultUnoptimised = noCheckOptions ++ checkOptions ++ yCheckOptions ++ classPath
val defaultOptions = defaultUnoptimised :+ "-optimise"
val defaultOptimised = defaultUnoptimised :+ "-optimise"
val defaultOptions = defaultUnoptimised
val allowDeepSubtypes = defaultOptions diff Array("-Yno-deep-subtypes")
val allowDoubleBindings = defaultOptions diff Array("-Yno-double-bindings")
val picklingOptions = defaultUnoptimised ++ Array(
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/tryPatternMatchError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object Test {
case _: ExceptionTrait =>
case _: NoSuchElementException if a <= 1 =>
case _: NullPointerException | _:IOException =>
case e: Int => // error
case e: Int => // error: unrelated
case EX =>
case IAE(msg) =>
case e: IllegalArgumentException =>
Expand Down