From 510b8cecc4951ff8092cfa931c2dc3717e21dded Mon Sep 17 00:00:00 2001 From: James Iry Date: Tue, 30 Jul 2013 11:22:55 -0700 Subject: Refactor Erasure for delambdafication. This commit is purely a refactor. It pulls code needed to adapt a tree of one type into a tree of another type (by casting, boxing, coercing, etc) out of Erasure and into common locations that will be usable from the Delambdafy phase. --- .../scala/tools/nsc/transform/Erasure.scala | 170 +------------------ .../tools/nsc/transform/ExtensionMethods.scala | 2 +- .../nsc/transform/TypeAdaptingTransformer.scala | 187 +++++++++++++++++++++ .../scala/reflect/internal/Definitions.scala | 4 + .../scala/reflect/internal/transform/Erasure.scala | 3 - 5 files changed, 198 insertions(+), 168 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index f0d3db1296..68f1c81c59 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -17,11 +17,15 @@ abstract class Erasure extends AddInterfaces with typechecker.Analyzer with TypingTransformers with ast.TreeDSL + with TypeAdaptingTransformer { import global._ import definitions._ import CODE._ + val analyzer: typechecker.Analyzer { val global: Erasure.this.global.type } = + this.asInstanceOf[typechecker.Analyzer { val global: Erasure.this.global.type }] + val phaseName: String = "erasure" def newTransformer(unit: CompilationUnit): Transformer = @@ -352,13 +356,6 @@ abstract class Erasure extends AddInterfaces override def newTyper(context: Context) = new Eraser(context) - private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = { - isUnbox(fn.symbol) && { - val cls = arg.tpe.typeSymbol - (cls == definitions.NullClass) || isBoxedValueClass(cls) - } - } - class ComputeBridges(unit: CompilationUnit, root: Symbol) { assert(phase == currentRun.erasurePhase, phase) @@ -522,158 +519,8 @@ abstract class Erasure extends AddInterfaces } /** The modifier typer which retypes with erased types. */ - class Eraser(_context: Context) extends Typer(_context) { - - private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) - - private def isDifferentErasedValueType(tpe: Type, other: Type) = - isErasedValueType(tpe) && (tpe ne other) - - private def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) - - @inline private def box(tree: Tree, target: => String): Tree = { - val result = box1(tree) - if (tree.tpe =:= UnitTpe) () - else log(s"boxing ${tree.summaryString}: ${tree.tpe} into $target: ${result.tpe}") - result - } - - /** Box `tree` of unboxed type */ - private def box1(tree: Tree): Tree = tree match { - case LabelDef(_, _, _) => - val ldef = deriveLabelDef(tree)(box1) - ldef setType ldef.rhs.tpe - case _ => - val tree1 = tree.tpe match { - case ErasedValueType(clazz, _) => - New(clazz, cast(tree, underlyingOfValueClass(clazz))) - case _ => - tree.tpe.typeSymbol match { - case UnitClass => - if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) - else BLOCK(tree, REF(BoxedUnit_UNIT)) - case NothingClass => tree // a non-terminating expression doesn't need boxing - case x => - assert(x != ArrayClass) - tree match { - /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x - * may lead to throwing an exception. - * - * This is important for specialization: calls to the super constructor should not box/unbox specialized - * fields (see TupleX). (ID) - */ - case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => - log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") - arg - case _ => - (REF(boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe - } - } - } - typedPos(tree.pos)(tree1) - } - - private def unbox(tree: Tree, pt: Type): Tree = { - val result = unbox1(tree, pt) - log(s"unboxing ${tree.shortClass}: ${tree.tpe} as a ${result.tpe}") - result - } - - /** Unbox `tree` of boxed type to expected type `pt`. - * - * @param tree the given tree - * @param pt the expected type. - * @return the unboxed tree - */ - private def unbox1(tree: Tree, pt: Type): Tree = tree match { -/* - case Boxed(unboxed) => - println("unbox shorten: "+tree) // this never seems to kick in during build and test; therefore disabled. - adaptToType(unboxed, pt) - */ - case LabelDef(_, _, _) => - val ldef = deriveLabelDef(tree)(unbox(_, pt)) - ldef setType ldef.rhs.tpe - case _ => - val tree1 = pt match { - case ErasedValueType(clazz, underlying) => - val tree0 = - if (tree.tpe.typeSymbol == NullClass && - isPrimitiveValueClass(underlying.typeSymbol)) { - // convert `null` directly to underlying type, as going - // via the unboxed type would yield a NPE (see SI-5866) - unbox1(tree, underlying) - } else - Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) - cast(tree0, pt) - case _ => - pt.typeSymbol match { - case UnitClass => - if (treeInfo isExprSafeToInline tree) UNIT - else BLOCK(tree, UNIT) - case x => - assert(x != ArrayClass) - // don't `setType pt` the Apply tree, as the Apply's fun won't be typechecked if the Apply tree already has a type - Apply(unboxMethod(pt.typeSymbol), tree) - } - } - typedPos(tree.pos)(tree1) - } - - /** Generate a synthetic cast operation from tree.tpe to pt. - * @pre pt eq pt.normalize - */ - private def cast(tree: Tree, pt: Type): Tree = { - if ((tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { - def word = ( - if (tree.tpe <:< pt) "upcast" - else if (pt <:< tree.tpe) "downcast" - else if (pt weak_<:< tree.tpe) "coerce" - else if (tree.tpe weak_<:< pt) "widen" - else "cast" - ) - log(s"erasure ${word}s from ${tree.tpe} to $pt") - } - if (pt =:= UnitTpe) { - // See SI-4731 for one example of how this occurs. - log("Attempted to cast to Unit: " + tree) - tree.duplicate setType pt - } else if (tree.tpe != null && tree.tpe.typeSymbol == ArrayClass && pt.typeSymbol == ArrayClass) { - // See SI-2386 for one example of when this might be necessary. - val needsExtraCast = isPrimitiveValueType(tree.tpe.typeArgs.head) && !isPrimitiveValueType(pt.typeArgs.head) - val tree1 = if (needsExtraCast) gen.mkRuntimeCall(nme.toObjectArray, List(tree)) else tree - gen.mkAttributedCast(tree1, pt) - } else gen.mkAttributedCast(tree, pt) - } - - /** Adapt `tree` to expected type `pt`. - * - * @param tree the given tree - * @param pt the expected type - * @return the adapted tree - */ - private def adaptToType(tree: Tree, pt: Type): Tree = { - if (settings.debug && pt != WildcardType) - log("adapting " + tree + ":" + tree.tpe + " : " + tree.tpe.parents + " to " + pt)//debug - if (tree.tpe <:< pt) - tree - else if (isDifferentErasedValueType(tree.tpe, pt)) - adaptToType(box(tree, pt.toString), pt) - else if (isDifferentErasedValueType(pt, tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) { - adaptToType(box(tree, pt.toString), pt) - } else if (isMethodTypeWithEmptyParams(tree.tpe)) { - // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val - //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt) - adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt) -// } else if (pt <:< tree.tpe) -// cast(tree, pt) - } else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else - cast(tree, pt) - } + class Eraser(_context: Context) extends Typer(_context) with TypeAdapter { + val typer = this.asInstanceOf[analyzer.Typer] /** Replace member references as follows: * @@ -834,11 +681,6 @@ abstract class Erasure extends AddInterfaces tree1 } } - - private def isMethodTypeWithEmptyParams(tpe: Type) = tpe match { - case MethodType(Nil, _) => true - case _ => false - } } /** The erasure transformer */ diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala index 9e8cbe6c03..2235a93ca4 100644 --- a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala +++ b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala @@ -129,7 +129,7 @@ abstract class ExtensionMethods extends Transform with TypingTransformers { if (seen contains clazz) unit.error(pos, "value class may not unbox to itself") else { - val unboxed = erasure.underlyingOfValueClass(clazz).typeSymbol + val unboxed = definitions.underlyingOfValueClass(clazz).typeSymbol if (unboxed.isDerivedValueClass) checkNonCyclic(pos, seen + clazz, unboxed) } diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala new file mode 100644 index 0000000000..41b8461c46 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala @@ -0,0 +1,187 @@ +package scala.tools.nsc +package transform + +import scala.reflect.internal._ +import scala.tools.nsc.ast.TreeDSL +import scala.tools.nsc.Global + +/** + * A trait usable by transforms that need to adapt trees of one type to another type + */ +trait TypeAdaptingTransformer { + self: TreeDSL => + + val analyzer: typechecker.Analyzer { val global: self.global.type } + + trait TypeAdapter { + val typer: analyzer.Typer + import global._ + import definitions._ + import CODE._ + + def isMethodTypeWithEmptyParams(tpe: Type) = tpe match { + case MethodType(Nil, _) => true + case _ => false + } + + private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = { + isUnbox(fn.symbol) && { + val cls = arg.tpe.typeSymbol + (cls == definitions.NullClass) || isBoxedValueClass(cls) + } + } + + private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) + + private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType] + + private def isDifferentErasedValueType(tpe: Type, other: Type) = + isErasedValueType(tpe) && (tpe ne other) + + def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) + + @inline def box(tree: Tree, target: => String): Tree = { + val result = box1(tree) + if (tree.tpe =:= UnitTpe) () + else log(s"boxing ${tree.summaryString}: ${tree.tpe} into $target: ${result.tpe}") + result + } + + /** Box `tree` of unboxed type */ + private def box1(tree: Tree): Tree = tree match { + case LabelDef(_, _, _) => + val ldef = deriveLabelDef(tree)(box1) + ldef setType ldef.rhs.tpe + case _ => + val tree1 = tree.tpe match { + case ErasedValueType(clazz, _) => + New(clazz, cast(tree, underlyingOfValueClass(clazz))) + case _ => + tree.tpe.typeSymbol match { + case UnitClass => + if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) + else BLOCK(tree, REF(BoxedUnit_UNIT)) + case NothingClass => tree // a non-terminating expression doesn't need boxing + case x => + assert(x != ArrayClass) + tree match { + /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x + * may lead to throwing an exception. + * + * This is important for specialization: calls to the super constructor should not box/unbox specialized + * fields (see TupleX). (ID) + */ + case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => + log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") + arg + case _ => + (REF(boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe + } + } + } + typer.typedPos(tree.pos)(tree1) + } + + def unbox(tree: Tree, pt: Type): Tree = { + val result = unbox1(tree, pt) + log(s"unboxing ${tree.shortClass}: ${tree.tpe} as a ${result.tpe}") + result + } + + /** Unbox `tree` of boxed type to expected type `pt`. + * + * @param tree the given tree + * @param pt the expected type. + * @return the unboxed tree + */ + private def unbox1(tree: Tree, pt: Type): Tree = tree match { +/* + case Boxed(unboxed) => + println("unbox shorten: "+tree) // this never seems to kick in during build and test; therefore disabled. + adaptToType(unboxed, pt) + */ + case LabelDef(_, _, _) => + val ldef = deriveLabelDef(tree)(unbox(_, pt)) + ldef setType ldef.rhs.tpe + case _ => + val tree1 = pt match { + case ErasedValueType(clazz, underlying) => + val tree0 = + if (tree.tpe.typeSymbol == NullClass && + isPrimitiveValueClass(underlying.typeSymbol)) { + // convert `null` directly to underlying type, as going + // via the unboxed type would yield a NPE (see SI-5866) + unbox1(tree, underlying) + } else + Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) + cast(tree0, pt) + case _ => + pt.typeSymbol match { + case UnitClass => + if (treeInfo isExprSafeToInline tree) UNIT + else BLOCK(tree, UNIT) + case x => + assert(x != ArrayClass) + // don't `setType pt` the Apply tree, as the Apply's fun won't be typechecked if the Apply tree already has a type + Apply(unboxMethod(pt.typeSymbol), tree) + } + } + typer.typedPos(tree.pos)(tree1) + } + + /** Generate a synthetic cast operation from tree.tpe to pt. + * @pre pt eq pt.normalize + */ + def cast(tree: Tree, pt: Type): Tree = { + if ((tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { + def word = ( + if (tree.tpe <:< pt) "upcast" + else if (pt <:< tree.tpe) "downcast" + else if (pt weak_<:< tree.tpe) "coerce" + else if (tree.tpe weak_<:< pt) "widen" + else "cast" + ) + log(s"erasure ${word}s from ${tree.tpe} to $pt") + } + if (pt =:= UnitTpe) { + // See SI-4731 for one example of how this occurs. + log("Attempted to cast to Unit: " + tree) + tree.duplicate setType pt + } else if (tree.tpe != null && tree.tpe.typeSymbol == ArrayClass && pt.typeSymbol == ArrayClass) { + // See SI-2386 for one example of when this might be necessary. + val needsExtraCast = isPrimitiveValueType(tree.tpe.typeArgs.head) && !isPrimitiveValueType(pt.typeArgs.head) + val tree1 = if (needsExtraCast) gen.mkRuntimeCall(nme.toObjectArray, List(tree)) else tree + gen.mkAttributedCast(tree1, pt) + } else gen.mkAttributedCast(tree, pt) + } + + /** Adapt `tree` to expected type `pt`. + * + * @param tree the given tree + * @param pt the expected type + * @return the adapted tree + */ + def adaptToType(tree: Tree, pt: Type): Tree = { + if (settings.debug && pt != WildcardType) + log("adapting " + tree + ":" + tree.tpe + " : " + tree.tpe.parents + " to " + pt)//debug + if (tree.tpe <:< pt) + tree + else if (isDifferentErasedValueType(tree.tpe, pt)) + adaptToType(box(tree, pt.toString), pt) + else if (isDifferentErasedValueType(pt, tree.tpe)) + adaptToType(unbox(tree, pt), pt) + else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) { + adaptToType(box(tree, pt.toString), pt) + } else if (isMethodTypeWithEmptyParams(tree.tpe)) { + // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val + //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt) + adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt) +// } else if (pt <:< tree.tpe) +// cast(tree, pt) + } else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe)) + adaptToType(unbox(tree, pt), pt) + else + cast(tree, pt) + } + } +} \ No newline at end of file diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 7b88514429..ea680867da 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -153,6 +153,10 @@ trait Definitions extends api.StandardDefinitions { DoubleClass ) def ScalaPrimitiveValueClasses: List[ClassSymbol] = ScalaValueClasses + + def underlyingOfValueClass(clazz: Symbol): Type = + clazz.derivedValueClassUnbox.tpe.resultType + } abstract class DefinitionsClass extends DefinitionsApi with ValueClassDefinitions { diff --git a/src/reflect/scala/reflect/internal/transform/Erasure.scala b/src/reflect/scala/reflect/internal/transform/Erasure.scala index 4aefc105e3..3eb3a4cdf4 100644 --- a/src/reflect/scala/reflect/internal/transform/Erasure.scala +++ b/src/reflect/scala/reflect/internal/transform/Erasure.scala @@ -77,9 +77,6 @@ trait Erasure { if (cls.owner.isClass) cls.owner.tpe_* else pre // why not cls.isNestedClass? } - def underlyingOfValueClass(clazz: Symbol): Type = - clazz.derivedValueClassUnbox.tpe.resultType - /** The type of the argument of a value class reference after erasure * This method needs to be called at a phase no later than erasurephase */ -- cgit v1.2.3