Skip to content

Add a phase to Dotty that hides Dotty bottom types from JVM. #1002

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

Closed
wants to merge 5 commits into from
Closed
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
81 changes: 81 additions & 0 deletions src/dotty/tools/backend/jvm/BottomTypes.scala
Original file line number Diff line number Diff line change
@@ -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 (defn.isBottomType(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)
Copy link
Member

Choose a reason for hiding this comment

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

Why fun = fun?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this method does not have default arguments.

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))
}
}
3 changes: 2 additions & 1 deletion src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -82,6 +82,7 @@ class Compiler {
List(/*new PrivateToStatic,*/
new ExpandPrivate,
new CollectEntryPoints,
new BottomTypes,
new LabelDefs),
List(new GenBCode)
)
Expand Down
5 changes: 3 additions & 2 deletions src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,15 @@ 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
else tree.tpe.widen match {
case MethodType(Nil, _) if tree.isTerm =>
adaptToType(tree.appliedToNone, pt)
case tpw =>
if (pt.isInstanceOf[ProtoType] || 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)
Expand All @@ -267,6 +267,7 @@ object Erasure extends TypeTestsCasts{
else
cast(tree, pt)
}

}
Copy link
Contributor

Choose a reason for hiding this comment

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

Indeed, why not use Definitions#isBottomType?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitions#isBottomType(tp) is defined to be tp.symbol \in {NothingClass, NullClass}
It does not work for types that do not return a usable symbol, such as And\Or types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I update Definitions#isBottomType to account for Null | Nothing and Int & Nothing?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, I think it's OK to leave the two separate. Maybe rename the second to
derivesFromBottomType
to make clear that refinements and & / | count as well?

On Fri, Dec 18, 2015 at 6:32 PM, Dmitry Petrashko [email protected]
wrote:

In src/dotty/tools/dotc/transform/Erasure.scala
#1002 (comment):

@@ -263,6 +263,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)
    
  • }
    }

Should I update Definitions#isBottomType to account for Null | Nothing
and Int & Nothing?


Reply to this email directly or view it on GitHub
https://github.com/lampepfl/dotty/pull/1002/files#r48048942.

Martin Odersky
EPFL

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.


class Typer extends typer.ReTyper with NoChecking {
Expand Down
6 changes: 6 additions & 0 deletions tests/pos/BottomOr.scala
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions tests/pos/i828.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
object X {
val x: Int = null.asInstanceOf[Nothing]
Copy link
Member

Choose a reason for hiding this comment

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

You should have test cases for the various nodes modified by BottomTypes and test cases for Null too.

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)
}