From 1f9fdafe96a6c2ef386f5cf45ed470c706342419 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Wed, 16 Dec 2015 16:04:29 +0100 Subject: [PATCH 1/5] Add a phase to Dotty that hides Dotty bottom types from JVM. It is very convenient to have bottom types in type system. Unfortunately JVM does not have bottom types and we need to insert casts that would make bytecode pass verification. --- src/dotty/tools/backend/jvm/BottomTypes.scala | 81 +++++++++++++++++++ src/dotty/tools/dotc/Compiler.scala | 3 +- src/dotty/tools/dotc/transform/Erasure.scala | 8 +- 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/dotty/tools/backend/jvm/BottomTypes.scala diff --git a/src/dotty/tools/backend/jvm/BottomTypes.scala b/src/dotty/tools/backend/jvm/BottomTypes.scala new file mode 100644 index 000000000000..f9557a36e587 --- /dev/null +++ b/src/dotty/tools/backend/jvm/BottomTypes.scala @@ -0,0 +1,81 @@ +package dotty.tools.backend.jvm + +import dotty.tools.dotc.ast.Trees.Thicket +import dotty.tools.dotc.ast.{Trees, tpd} +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Types +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, MiniPhase, MiniPhaseTransform} +import dotty.tools.dotc +import dotty.tools.dotc.backend.jvm.DottyPrimitives +import dotty.tools.dotc.core.Flags.FlagSet +import dotty.tools.dotc.transform.Erasure +import dotty.tools.dotc.transform.SymUtils._ +import java.io.{File => JFile} + +import scala.collection.generic.Clearable +import scala.collection.mutable +import scala.collection.mutable.{ListBuffer, ArrayBuffer} +import scala.reflect.ClassTag +import scala.reflect.internal.util.WeakHashSet +import scala.reflect.io.{Directory, PlainDirectory, AbstractFile} +import scala.tools.asm.{ClassVisitor, FieldVisitor, MethodVisitor} +import scala.tools.nsc.backend.jvm.{BCodeHelpers, BackendInterface} +import dotty.tools.dotc.core._ +import Periods._ +import SymDenotations._ +import Contexts._ +import Types._ +import Symbols._ +import Denotations._ +import Phases._ +import java.lang.AssertionError +import dotty.tools.dotc.util.Positions.Position +import Decorators._ +import tpd._ +import Flags._ +import StdNames.nme + +/** + * Ensures that tree does not contain type subsumptions where subsumed type is bottom type + * of our typesystem, but not the bottom type of JVM typesystem. + */ +class BottomTypes extends MiniPhaseTransform { + def phaseName: String = "bottomTypes" + + + def adaptBottom(treeOfBottomType: tpd.Tree, expectedType: Type)(implicit ctx: Context) = { + if (Erasure.Boxing.isNonJVMBottomType(treeOfBottomType.tpe) && (treeOfBottomType.tpe ne expectedType)) + Erasure.Boxing.adaptToType(treeOfBottomType, expectedType) + else treeOfBottomType + } + + override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + val returnTp = tree.symbol.info.dealias.finalResultType + cpy.DefDef(tree)(rhs = adaptBottom(tree.rhs, returnTp)) + } + + + override def transformAssign(tree: tpd.Assign)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + val returnTp = tree.lhs.symbol.info.dealias + cpy.Assign(tree)(tree.lhs, adaptBottom(tree.rhs, returnTp)) + } + + + override def transformTyped(tree: tpd.Typed)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + cpy.Typed(tree)(adaptBottom(tree.expr, tree.tpt.tpe), tree.tpt) + } + + override def transformApply(tree: tpd.Apply)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + val fun = tree.fun + val newArgs: List[tpd.Tree] = tree.args.zip(fun.tpe.dealias.firstParamTypes).map(x => adaptBottom(x._1, x._2)) + val changeNeeded = tree.args == newArgs // cpy.Apply does not check if elements are the same, + // it only does `eq` on lists as whole + if (changeNeeded) cpy.Apply(tree)(fun = fun, args = newArgs) + else tree + } + + override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + val returnTp = tree.symbol.info.dealias + cpy.ValDef(tree)(rhs = adaptBottom(tree.rhs, returnTp)) + } +} diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 42d223fe9e83..4b06d3bdf83b 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -15,7 +15,7 @@ import transform.TreeTransforms.{TreeTransform, TreeTransformer} import core.DenotTransformers.DenotTransformer import core.Denotations.SingleDenotation -import dotty.tools.backend.jvm.{LabelDefs, GenBCode} +import dotty.tools.backend.jvm.{BottomTypes, LabelDefs, GenBCode} class Compiler { @@ -82,6 +82,7 @@ class Compiler { List(/*new PrivateToStatic,*/ new ExpandPrivate, new CollectEntryPoints, + new BottomTypes, new LabelDefs), List(new GenBCode) ) diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 3445b4c444ec..812f387e0c74 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -254,7 +254,7 @@ object Erasure extends TypeTestsCasts{ case MethodType(Nil, _) if tree.isTerm => adaptToType(tree.appliedToNone, pt) case tpw => - if (pt.isInstanceOf[ProtoType] || tree.tpe <:< pt) + if (pt.isInstanceOf[ProtoType] || (!isNonJVMBottomType(tree.tpe) && (tree.tpe <:< pt))) tree else if (tpw.isErasedValueType) adaptToType(box(tree), pt) @@ -267,6 +267,12 @@ object Erasure extends TypeTestsCasts{ else cast(tree, pt) } + + + /** Is `tpe` a type which is a bottom type for Dotty, but not a bottom type for JVM */ + def isNonJVMBottomType(tpe: Type)(implicit ctx: Context): Boolean = { + tpe.derivesFrom(ctx.definitions.NothingClass) || tpe.derivesFrom(ctx.definitions.NullClass) + } } class Typer extends typer.ReTyper with NoChecking { From 4da5dfaf4f62f9ea8e2723e9cc61095966260c64 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Wed, 16 Dec 2015 16:04:45 +0100 Subject: [PATCH 2/5] Text that #828 is fixed. --- tests/pos/i828.scala | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/pos/i828.scala diff --git a/tests/pos/i828.scala b/tests/pos/i828.scala new file mode 100644 index 000000000000..77dc0d4901c3 --- /dev/null +++ b/tests/pos/i828.scala @@ -0,0 +1,3 @@ +object X { + val x: Int = null.asInstanceOf[Nothing] +} From 4168a25057399b069593216debb39481c32489d4 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Wed, 16 Dec 2015 23:32:44 +0100 Subject: [PATCH 3/5] Fix casting bottom types to non-value types. --- src/dotty/tools/dotc/transform/Erasure.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 812f387e0c74..e137a1dfcda5 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -254,7 +254,7 @@ object Erasure extends TypeTestsCasts{ case MethodType(Nil, _) if tree.isTerm => adaptToType(tree.appliedToNone, pt) case tpw => - if (pt.isInstanceOf[ProtoType] || (!isNonJVMBottomType(tree.tpe) && (tree.tpe <:< pt))) + if (pt.isInstanceOf[ProtoType] || ((!pt.isValueType || !isNonJVMBottomType(tree.tpe)) && (tree.tpe <:< pt))) tree else if (tpw.isErasedValueType) adaptToType(box(tree), pt) From 142268afa2f9d679d2bff22efd40d20c57ff1e95 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 4 Jan 2016 11:35:19 +0100 Subject: [PATCH 4/5] Rebase and address reviewer comments. --- src/dotty/tools/backend/jvm/BottomTypes.scala | 4 ++-- src/dotty/tools/dotc/transform/Erasure.scala | 9 ++------- tests/pos/i828.scala | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/dotty/tools/backend/jvm/BottomTypes.scala b/src/dotty/tools/backend/jvm/BottomTypes.scala index f9557a36e587..07e1315e515a 100644 --- a/src/dotty/tools/backend/jvm/BottomTypes.scala +++ b/src/dotty/tools/backend/jvm/BottomTypes.scala @@ -44,7 +44,7 @@ class BottomTypes extends MiniPhaseTransform { def adaptBottom(treeOfBottomType: tpd.Tree, expectedType: Type)(implicit ctx: Context) = { - if (Erasure.Boxing.isNonJVMBottomType(treeOfBottomType.tpe) && (treeOfBottomType.tpe ne expectedType)) + if (defn.isBottomType(treeOfBottomType.tpe) && (treeOfBottomType.tpe ne expectedType)) Erasure.Boxing.adaptToType(treeOfBottomType, expectedType) else treeOfBottomType } @@ -68,7 +68,7 @@ class BottomTypes extends MiniPhaseTransform { override def transformApply(tree: tpd.Apply)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { val fun = tree.fun val newArgs: List[tpd.Tree] = tree.args.zip(fun.tpe.dealias.firstParamTypes).map(x => adaptBottom(x._1, x._2)) - val changeNeeded = tree.args == newArgs // cpy.Apply does not check if elements are the same, + val changeNeeded = tree.args != newArgs // cpy.Apply does not check if elements are the same, // it only does `eq` on lists as whole if (changeNeeded) cpy.Apply(tree)(fun = fun, args = newArgs) else tree diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index e137a1dfcda5..355b588b9ca4 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -246,7 +246,7 @@ object Erasure extends TypeTestsCasts{ * e -> unbox(e, PT) otherwise, if `PT` is an erased value type * e -> box(e) if `e` is of primitive type and `PT` is not a primitive type * e -> unbox(e, PT) if `PT` is a primitive type and `e` is not of primitive type - * e -> cast(e, PT) otherwise + * e -> cast(e, PT) otherwise, including if `PT` is a bottom type. */ def adaptToType(tree: Tree, pt: Type)(implicit ctx: Context): Tree = if (pt.isInstanceOf[FunProto]) tree @@ -254,7 +254,7 @@ object Erasure extends TypeTestsCasts{ case MethodType(Nil, _) if tree.isTerm => adaptToType(tree.appliedToNone, pt) case tpw => - if (pt.isInstanceOf[ProtoType] || ((!pt.isValueType || !isNonJVMBottomType(tree.tpe)) && (tree.tpe <:< pt))) + if (pt.isInstanceOf[ProtoType] || ((!pt.isValueType || !defn.isBottomType(tree.tpe)) && (tree.tpe <:< pt))) tree else if (tpw.isErasedValueType) adaptToType(box(tree), pt) @@ -268,11 +268,6 @@ object Erasure extends TypeTestsCasts{ cast(tree, pt) } - - /** Is `tpe` a type which is a bottom type for Dotty, but not a bottom type for JVM */ - def isNonJVMBottomType(tpe: Type)(implicit ctx: Context): Boolean = { - tpe.derivesFrom(ctx.definitions.NothingClass) || tpe.derivesFrom(ctx.definitions.NullClass) - } } class Typer extends typer.ReTyper with NoChecking { diff --git a/tests/pos/i828.scala b/tests/pos/i828.scala index 77dc0d4901c3..9bf90552e18d 100644 --- a/tests/pos/i828.scala +++ b/tests/pos/i828.scala @@ -1,3 +1,18 @@ object X { val x: Int = null.asInstanceOf[Nothing] + def d: Int = null.asInstanceOf[Nothing] + var s: Int = 0 + s = null.asInstanceOf[Nothing] + def takeInt(i: Int): Unit + takeInt(null.asInstanceOf[Nothing]) +} + +object Y { + val n: Null = null + val x: Object = n + def d: Object = n + var s: Object = 0 + s = n + def takeInt(i: Object): Unit + takeInt(n) } From 15eaa101b4e414e33ce6a9f846d171e2b22d182f Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Tue, 26 Jan 2016 13:52:36 +0100 Subject: [PATCH 5/5] Play with Or-types of bottom types. --- tests/pos/BottomOr.scala | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/pos/BottomOr.scala diff --git a/tests/pos/BottomOr.scala b/tests/pos/BottomOr.scala new file mode 100644 index 000000000000..23626cd38bd7 --- /dev/null +++ b/tests/pos/BottomOr.scala @@ -0,0 +1,6 @@ +object Test{ + val a: Nothing = ??? + val b: Null = ??? + val c: Null | Nothing = if(a == b) a else b + val d: Nothing | Nothing = if(a == b) a else b +}