diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 3ea941e25604..4566aaad486a 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index f1337287b28a..74f1909eaa52 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -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) @@ -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. */ diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 648ade2f39fa..e4991d569be8 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 04a540629f80..552a6e8f3ef5 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -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" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b2ae1daa40c0..c68f32cfaec8 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 417a94722c3a..3e812695668a 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index a2119bfe49dd..e79b56b94771 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -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 @@ -26,71 +25,101 @@ 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") + + 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 @@ -98,28 +127,38 @@ trait TypeTestsCasts { * 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 diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 76718ea894be..8f95eda31a4b 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -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 - } } } diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 440a0a629534..b2ab4a5c51d6 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -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( diff --git a/tests/neg/tryPatternMatchError.scala b/tests/neg/tryPatternMatchError.scala index b4564c758d8c..6b7b2c3addbe 100644 --- a/tests/neg/tryPatternMatchError.scala +++ b/tests/neg/tryPatternMatchError.scala @@ -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 =>