/* NSC -- new Scala compiler
* Copyright 2005-2006 LAMP/EPFL
* @author Martin Odersky
*/
// $Id$
package scala.tools.nsc.transform
import symtab._
import Flags._
import scala.tools.nsc.util.Position
import scala.collection.mutable.{ListBuffer, HashMap}
abstract class CleanUp extends Transform {
import global._
import definitions._
import posAssigner.atPos
/** the following two members override abstract members in Transform */
val phaseName: String = "cleanup"
protected def newTransformer(unit: CompilationUnit): Transformer =
new CleanUpTransformer(unit)
class CleanUpTransformer(unit: CompilationUnit) extends Transformer {
private val MethodClass = if (forCLDC || forMSIL) null
else definitions.getClass("java.lang.reflect.Method")
private val newDefs = new ListBuffer[Tree]
private val classConstantMeth = new HashMap[String, Symbol]
// a map from the symbols of the Scala primitive types to the symbols
// of the modules of the Java box classes
private val javaBoxClassModule = new HashMap[Symbol, Symbol]
if (!forMSIL) {
javaBoxClassModule(BooleanClass) = getModule("java.lang.Boolean")
javaBoxClassModule(ByteClass) = getModule("java.lang.Byte")
javaBoxClassModule(ShortClass) = getModule("java.lang.Short")
javaBoxClassModule(IntClass) = getModule("java.lang.Integer")
javaBoxClassModule(CharClass) = getModule("java.lang.Character")
javaBoxClassModule(LongClass) = getModule("java.lang.Long")
if (!forCLDC) {
javaBoxClassModule(FloatClass) = getModule("java.lang.Float")
javaBoxClassModule(DoubleClass) = getModule("java.lang.Double")
javaBoxClassModule(UnitClass) = getModule("java.lang.Void")
}
}
private var localTyper: analyzer.Typer = null
private def classConstantMethod(pos: Position, sig: String): Symbol = classConstantMeth.get(sig) match {
case Some(meth) =>
meth
case None =>
val forName = getMember(ClassClass.linkedModuleOfClass, nme.forName)
val owner = currentOwner.enclClass
val cvar = owner.newVariable(pos, unit.fresh.newName(pos, "class$Cache"))
.setFlag(PRIVATE | STATIC | MUTABLE | SYNTHETIC).setInfo(ClassClass.tpe)
owner.info.decls.enter(cvar)
val cdef =
localTyper.typed {
atPos(pos) {
ValDef(cvar, Literal(Constant(null)))
}
}
val meth = owner.newMethod(pos, unit.fresh.newName(pos, "class$Method"))
.setFlag(PRIVATE | STATIC | SYNTHETIC).setInfo(MethodType(List(), ClassClass.tpe))
owner.info.decls.enter(meth)
val mdef =
localTyper.typed {
atPos(pos) {
DefDef(meth, vparamss =>
gen.mkCached(
cvar,
Apply(
gen.mkAttributedRef(forName), List(Literal(sig)))))
}
}
newDefs.append(cdef, mdef);
classConstantMeth.update(sig, meth)
meth
}
private val existingReflectiveMethodCache = new HashMap[(String, List[Type]), Symbol]
/* Transforms a list of types into a list of trees representing these types
* as java.lang.Class instances. */
private def paramTypeClasses(paramTypes: List[Type]): List[Tree] =
paramTypes map { pt => Literal(Constant(pt)) }
private def reflectiveMethodCache(pos: Position, method: String, paramTypes: List[Type]): Symbol =
existingReflectiveMethodCache.get((method, paramTypes)) match {
case Some(cache) => cache
case None =>
val owner = currentOwner.enclClass
val rmvar = owner.newVariable(pos, unit.fresh.newName(pos, "reflMethod$Cache"))
.setFlag(PRIVATE | STATIC | MUTABLE | SYNTHETIC)
.setInfo(MethodClass.tpe)
owner.info.decls.enter(rmvar)
val rmdef =
localTyper.typed {
atPos(pos) {
ValDef(rmvar, Literal(Constant(null)))
}
}
val rmcvar = owner.newVariable(pos, unit.fresh.newName(pos, "reflClass$Cache"))
.setFlag(PRIVATE | STATIC | MUTABLE | SYNTHETIC)
.setInfo(ClassClass.tpe)
owner.info.decls.enter(rmcvar)
val rmcdef =
localTyper.typed {
atPos(pos) {
ValDef(rmcvar, Literal(Constant(null)))
}
}
val rmmeth = owner.newMethod(pos, unit.fresh.newName(pos, "reflMethod$Method"))
.setFlag(STATIC | SYNTHETIC)
.setInfo(MethodType(List(ClassClass.tpe), MethodClass.tpe))
owner.info.decls.enter(rmmeth)
val rmmdef =
localTyper.typed {
atPos(pos) {
DefDef(rmmeth, { vparamss =>
val callClass = vparamss(0)(0)
Block(
List(
If(
gen.mkOr(
Apply(Select(Select(This(owner), rmvar), nme.eq), List(Literal(Constant(null)))),
Apply(Select(Select(This(owner), rmcvar), nme.ne), List(gen.mkAttributedRef(callClass)))
),
Block(
List(
Assign(
Select(This(owner), rmvar),
Apply(
Select(
gen.mkAttributedRef(callClass),
ClassClass.tpe.member(nme.getMethod_)
),
List(
Literal(Constant(method)),
ArrayValue(TypeTree(ClassClass.tpe), paramTypeClasses(paramTypes))
)
)
),
Assign(Select(This(owner), rmcvar), gen.mkAttributedRef(callClass))
),
Literal(Constant(()))
),
EmptyTree
)
),
Select(This(owner), rmvar)
)
})
}
}
newDefs.append(transform(rmdef), transform(rmcdef), transform(rmmdef));
existingReflectiveMethodCache.update((method, paramTypes), rmmeth)
rmmeth
}
override def transformUnit(unit: CompilationUnit) =
unit.body = transform(unit.body)
/** A value class is defined to be only Java-compatible values: unit is
* not part of it, as opposed to isValueClass in definitions. scala.Int is
* a value class, java.lang.Integer is not. */
def isValueClass(sym: Symbol) = boxedClass contains sym
override def transform(tree: Tree): Tree = tree match {
/* Transforms dynamic calls (i.e. calls to methods that are undefined
* in the erased type space) to -- dynamically -- unsafe calls using
* reflection. This is used for structural sub-typing of refinement
* types.
* For 'a.f(b)' it will generate something like:
* 'a.getClass().
* ' getMethod("f", Array(classOf[b.type])).
* ' invoke(a, Array(b))
* plus all the necessary casting/boxing/etc. machinery required
* for type-compatibility (see fixResult and fixParams).
*
* USAGE CONTRACT:
* There are a number of assumptions made on the way a dynamic apply
* is used. Assumptions relative to type are handled by the erasure
* phase.
* - The applied arguments are compatible with AnyRef, which means
* that an argument tree typed as AnyVal has already been extended
* with the necessary boxing calls. This implies that passed
* arguments might not be strictly compatible with the method's
* parameter types (a boxed integeer while int is expected).
* - The expected return type is an AnyRef, even when the method's
* return type is an AnyVal. This means that the tree containing the
* call has already been extended with the necessary unboxing calls
* (or keeps the boxed type).
* - The type-checker has prevented dynamic applies on methods which
* parameter's erased types are not statically known at the call site.
* This is necessary to allow dispatching the call to the correct
* method (dispatching on paramters is static in Scala). In practice,
* this limitation only arises when the called method is defined as a
* refinement, where the refinement defines a parameter based on a
* type variable. */
case ad@ApplyDynamic(qual, params) =>
assert(ad.symbol.isPublic)
val testForNumber: Tree =
gen.mkOr(
Apply(
TypeApply(
gen.mkAttributedSelect(qual, definitions.Object_isInstanceOf),
List(TypeTree(BoxedNumberClass.tpe.normalize))
),
List()
),
Apply(
TypeApply(
gen.mkAttributedSelect(qual, definitions.Object_isInstanceOf),
List(TypeTree(BoxedCharacterClass.tpe.normalize))
),
List()
)
)
val testForBoolean: Tree =
Apply(
TypeApply(
gen.mkAttributedSelect(qual, definitions.Object_isInstanceOf),
List(TypeTree(BoxedBooleanClass.tpe.normalize))
),
List()
)
val testForNumberOrBoolean: Tree = gen.mkOr(testForNumber, testForBoolean)
def getPrimitiveReplacementForStructuralCall: PartialFunction[Name, (Symbol, Tree)] = {
/* Unary arithmetic */
case nme.UNARY_+ =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("positive")), testForNumber)
case nme.UNARY_- =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("negate")), testForNumber)
/* Unary logic */
case nme.UNARY_~ =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("complement")), testForNumber)
case nme.UNARY_! =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("takeNot")), testForBoolean)
/* Binary arithmetic */
case nme.ADD =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("add")), testForNumber)
case nme.SUB =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("subtract")), testForNumber)
case nme.MUL =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("multiply")), testForNumber)
case nme.DIV =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("divide")), testForNumber)
case nme.MOD =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("takeModulo")), testForNumber)
/* Binary logic */
case nme.OR =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("takeOr")), testForNumberOrBoolean)
case nme.XOR =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("takeXor")), testForNumberOrBoolean)
case nme.AND =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("takeAnd")), testForNumberOrBoolean)
case nme.ZOR =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("takeConditionalOr")), testForBoolean)
case nme.ZAND =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("takeConditionalAnd")), testForBoolean)
/* Shifting */
case nme.LSL =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("shiftSignedLeft")), testForNumber)
case nme.LSR =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("shiftSignedRight")), testForNumber)
case nme.ASR =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("shiftLogicalRight")), testForNumber)
case nme.EQ =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("testEqual")), testForNumberOrBoolean)
case nme.NE =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("testNotEqual")), testForNumberOrBoolean)
case nme.LT =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("testLessThan")), testForNumber)
case nme.LE =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("testLessOrEqualThan")), testForNumber)
case nme.GE =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("testGreaterOrEqualThan")), testForNumber)
case nme.GT =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("testGreaterThan")), testForNumber)
/* Conversions */
case nme.toByte =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("toByte")), testForNumber)
case nme.toShort =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("toShort")), testForNumber)
case nme.toChar =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("toCharacter")), testForNumber)
case nme.toInt =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("toInteger")), testForNumber)
case nme.toLong =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("toLong")), testForNumber)
case nme.toFloat =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("toFloat")), testForNumber)
case nme.toDouble =>
(definitions.getMember(definitions.BoxesRunTimeClass, newTermName("toDouble")), testForNumber)
}
/* Transforms the result of a reflective call (always an AnyRef) to
* the actual result value (an AnyRef too). The transformation
* depends on the method's static return type.
* - for units (void), the reflective call will return null: a new
* boxed unit is generated.
* - for arrays, the reflective call will return an unboxed array:
* the resulting array is boxed.
* - otherwise, the value is simply casted to the expected type. This
* is enough even for value (int et al.) values as the result of
* a dynamic call will box them as a side-effect. */
def fixResult(resType: Type)(tree: Tree): Tree =
localTyper.typed {
if (resType.typeSymbol == UnitClass)
Block (
List(tree),
gen.mkAttributedRef(BoxedUnit_UNIT)
)
else if (resType.typeSymbol == ArrayClass) {
val sym = currentOwner.newValue(tree.pos, newTermName(unit.fresh.newName(tree.pos))) setInfo ObjectClass.tpe
Block(
List(ValDef(sym, tree)),
If(
Apply(Select(Literal(Constant(null)), Any_==), List(gen.mkAttributedRef(sym))),
Literal(Constant(null)),
Apply(
Select(
gen.mkAttributedRef(ScalaRunTimeModule),
ScalaRunTimeModule.tpe.member(nme.boxArray)
),
List(gen.mkAttributedRef(sym))
)
)
)
}
else if (resType.typeSymbol == ObjectClass) // TODO: remove the cast always when unnecessary.
tree
else
gen.mkAttributedCast(tree, resType)
}
/* Transforms the parameters of a dynamic apply (always AnyRefs) to
* something compatible with reclective calls. The transformation depends
* on the method's static parameter types.
* - for (unboxed) arrays, the (non-null) value is tested for its erased
* type. If it is a boxed array, the array is unboxed. If it is an
* unboxed array, it is left alone. */
def fixParams(params: List[Tree], paramTypes: List[Type]): List[Tree] =
(params zip paramTypes) map { case (param, paramType) =>
localTyper.typed {
if (paramType.typeSymbol == ArrayClass) {
val sym = currentOwner.newValue(tree.pos, newTermName(unit.fresh.newName(tree.pos))) setInfo ObjectClass.tpe
val arrayType = {
assert(paramType.typeArgs.length == 1)
paramType.typeArgs(0).normalize
}
Block(
List(ValDef(sym, param)),
If(
Apply(Select(Literal(Constant(null)), Any_==), List(gen.mkAttributedRef(sym))),
Literal(Constant(null)),
If(
Apply(
TypeApply(
gen.mkAttributedSelect(gen.mkAttributedRef(sym), definitions.Object_isInstanceOf),
List(TypeTree(BoxedArrayClass.tpe.normalize))
),
List()
),
Apply(
Select(gen.mkAttributedCast(gen.mkAttributedRef(sym), BoxedArrayClass.tpe), getMember(BoxedArrayClass, nme.unbox)),
List(Literal(Constant(arrayType)))
),
gen.mkAttributedRef(sym)
)
)
)
}
else
param
}
}
def callAsOperator(paramTypes: List[Type], resType: Type): Tree = localTyper.typed {
if (getPrimitiveReplacementForStructuralCall isDefinedAt ad.symbol.name) {
val (operator, test) = getPrimitiveReplacementForStructuralCall(ad.symbol.name)
If(
test,
Apply(
gen.mkAttributedRef(operator),
qual :: fixParams(params, paramTypes)
),
callAsMethod(paramTypes, resType)
)
}
else callAsMethod(paramTypes, resType)
}
def callAsMethod(paramTypes: List[Type], resType: Type): Tree = localTyper.typed {
val invokeExc =
currentOwner.newValue(tree.pos, newTermName(unit.fresh.newName(tree.pos))) setInfo InvocationTargetExceptionClass.tpe
Try(
Apply(
Select(
Apply(
gen.mkAttributedRef(reflectiveMethodCache(tree.pos, ad.symbol.name.toString, paramTypes)),
List(Apply(Select(qual, ObjectClass.tpe.member(nme.getClass_)), Nil))
),
MethodClass.tpe.member(nme.invoke_)
),
List(
qual,
ArrayValue(TypeTree(ObjectClass.tpe), fixParams(params, paramTypes))
)
),
List(CaseDef(
Bind(invokeExc, Typed(Ident(nme.WILDCARD), TypeTree(InvocationTargetExceptionClass.tpe))),
EmptyTree,
Throw(Apply(Select(Ident(invokeExc), nme.getCause), Nil))
)),
EmptyTree
)
}
def mayRequirePrimitiveReplacement: Boolean = {
def isBoxed(sym: Symbol): Boolean =
if (forCLDC) {
(sym isNonBottomSubClass ByteClass) ||
(sym isNonBottomSubClass ShortClass) ||
(sym isNonBottomSubClass CharClass) ||
(sym isNonBottomSubClass IntClass) ||
(sym isNonBottomSubClass LongClass)
}
else ((sym isNonBottomSubClass BoxedNumberClass) ||
(!forMSIL && (sym isNonBottomSubClass BoxedCharacterClass)))
val sym = qual.tpe.typeSymbol
(sym == definitions.ObjectClass) || isBoxed(sym)
}
/* This creates the tree that does the reflective call (see general comment
* on the apply-dynamic tree for its format). This tree is simply composed
* of three succesive calls, first to getClass on the callee, then to
* getMethod on the classs, then to invoke on the method.
* - getMethod needs an array of classes for choosing one amongst many
* overloaded versions of the method. This is provided by paramTypeClasses
* and must be done on the static type as Scala's dispatching is static on
* the parameters.
* - invoke needs an array of AnyRefs that are the method's arguments. The
* erasure phase guarantees that any parameter passed to a dynamic apply
* is compatible (through boxing). Boxed ints et al. is what invoke expects
* when the applied method expects ints, hence no change needed there.
* On the other hand, arrays must be dealt with as they must be entered
* unboxed in the parameter array of invoke. fixParams is responsible for
* that.
* - in the end, the result of invoke must be fixed, again to deal with arrays.
* This is provided by fixResult. fixResult will cast the invocation's result
* to the method's return type, which is generally ok, except when this type
* is a value type (int et al.) in which case it must cast to the boxed version
* because invoke only returns object and erasure made sure the result is
* expected to be an AnyRef. */
val t: Tree = ad.symbol.tpe match {
case MethodType(paramTypes, resType) =>
assert(params.length == paramTypes.length)
atPos(tree.pos)(localTyper.typed {
fixResult(if (isValueClass(resType.typeSymbol)) boxedClass(resType.typeSymbol).tpe else resType) {
if (mayRequirePrimitiveReplacement)
callAsOperator(paramTypes, resType)
else
callAsMethod(paramTypes, resType)
}
})
}
/* For testing purposes, the dynamic application's condition
* can be printed-out in great detail. Remove? */
if (settings.debug.value) {
Console.println(
"Dynamically applying '" + qual + "." + ad.symbol.name +
"(" + params.map(_.toString).mkString(", ") + ")' with"
)
ad.symbol.tpe match {
case MethodType(paramTypes, resType) =>
Console.println(
" - declared parameters' types: " +
(paramTypes.map(_.toString)).mkString("'",", ","'"))
Console.println(
" - passed arguments' types: " +
(params.map(_.toString)).mkString("'",", ","'"))
Console.println(
" - result type: '" +
resType.toString + "'")
}
Console.println(" - resulting code: '" + t + "'")
}
/* We return the dynamic call tree, after making sure no other
* clean-up transformation are to be applied on it. */
transform(t)
/* end of dynamic call transformer. */
case Template(parents, self, body) =>
localTyper = typer.atOwner(tree, currentOwner)
if (!forMSIL) {
classConstantMeth.clear
newDefs.clear
val body1 = transformTrees(body)
copy.Template(tree, parents, self, newDefs.toList ::: body1)
}
else super.transform(tree)
case Literal(c) if (c.tag == ClassTag) && !forMSIL=>
val tpe = c.typeValue
atPos(tree.pos) {
localTyper.typed {
if ((isValueClass(tpe.typeSymbol) || tpe.typeSymbol == definitions.UnitClass)
&& !forCLDC)
Select(gen.mkAttributedRef(javaBoxClassModule(tpe.typeSymbol)), "TYPE")
else if (settings.target.value != "jvm-1.5" && !forMSIL)
Apply(
gen.mkAttributedRef(classConstantMethod(tree.pos, signature(tpe))),
List())
else tree
}
}
/* MSIL requires that the stack is empty at the end of a try-block.
* Hence, we here rewrite all try blocks with a result != {Unit, All} such that they
* store their result in a local variable. The catch blocks are adjusted as well.
* The try tree is subsituted by a block whose result expression is read of that variable. */
case theTry @ Try(block, catches, finalizer)
if theTry.tpe.typeSymbol != definitions.UnitClass && theTry.tpe.typeSymbol != definitions.NothingClass =>
val tpe = theTry.tpe.widen
val tempVar = currentOwner.newValue(theTry.pos, unit.fresh.newName(theTry.pos, "exceptionResult"))
.setInfo(tpe).setFlag(Flags.MUTABLE)
val newBlock = super.transform(Block(Nil, Assign(Ident(tempVar), transform(block))))
val newCatches = for (CaseDef(pattern, guard, body) <- catches) yield {
CaseDef(
super.transform(pattern),
super.transform(guard),
Block(Nil, Assign(Ident(tempVar), super.transform(body)))
)
}
val newTry = Try(newBlock, newCatches, super.transform(finalizer))
val res = Block(List(ValDef(tempVar, EmptyTree), newTry), Ident(tempVar))
localTyper.typed(res)
/* Adds @serializable annotation to anonymous function classes
* which do not have references to classes
* which are not marked @serializable.
*/
case cdef @ ClassDef(mods, name, tparams, impl) =>
val sym = cdef.symbol
// is this an anonymous function class?
if (!sym.hasAttribute(SerializableAttr) && sym.hasFlag(SYNTHETIC) &&
(sym.name.toString.indexOf("anonfun") != -1)) {
// check whether all of its field members are of serializable type
val serializable =
sym.info.members forall { m =>
m.isMethod || {
val typeSym = m.info.typeSymbol
// Value types are assumed to be serializable,
// reference types must be marked as such.
isValueType(typeSym) ||
typeSym.hasAttribute(SerializableAttr) ||
(m.info.baseClasses exists { bc => bc hasAttribute SerializableAttr })
}
}
if (serializable)
sym.attributes =
AnnotationInfo(definitions.SerializableAttr.tpe, List(), List()) :: sym.attributes
super.transform(tree)
} else
super.transform(tree)
case _ =>
super.transform(tree)
}
} // CleanUpTransformer
}