summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala120
-rw-r--r--test/files/run/bug3175.check3
-rw-r--r--test/files/run/bug3175.scala11
3 files changed, 91 insertions, 43 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
index 107a73aa66..dc7f62565b 100644
--- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala
+++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
@@ -58,6 +58,11 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
* not part of it, as opposed to isValueClass in definitions. scala.Int is
* a value class, java.lang.Integer is not. */
def isJavaValueClass(sym: Symbol) = boxedClass contains sym
+ def isJavaValueType(tp: Type) = isJavaValueClass(tp.typeSymbol)
+
+ /** The boxed type if it's a primitive; identity otherwise.
+ */
+ def toBoxedType(tp: Type) = if (isJavaValueType(tp)) boxedClass(tp.typeSymbol).tpe else tp
override def transform(tree: Tree): Tree = tree match {
@@ -326,25 +331,30 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
* a dynamic call will box them as a side-effect. */
/* ### CALLING THE APPLY ### */
- def callAsReflective(paramTypes: List[Type], resType: Type, structResType: Type): Tree = localTyper typed {
- def fixResult(tree: Tree): Tree = localTyper typed {
- structResType.typeSymbol match {
- case UnitClass => BLOCK(tree, REF(BoxedUnit_UNIT))
- case ObjectClass => tree
- case _ => tree AS_ATTR structResType
- }
- }
- val qualSym = qual.tpe.typeSymbol
- val methSym = ad.symbol
- def args = qual :: params
-
- def isArrayMethodSignature = {
+ def callAsReflective(paramTypes: List[Type], resType: Type): Tree = {
+ /* Some info about the type of the method being called. */
+ val methSym = ad.symbol
+ val boxedResType = toBoxedType(resType) // Int -> Integer
+ val resultSym = boxedResType.typeSymbol
+ // If this is a primitive method type (like '+' in 5+5=10) then the
+ // parameter types and the (unboxed) result type should all be primitive types,
+ // and the method name should be in the primitive->structural map.
+ def isJavaValueMethod = (
+ (resType :: paramTypes forall isJavaValueType) && // issue #1110
+ (getPrimitiveReplacementForStructuralCall isDefinedAt methSym.name)
+ )
+ // Erasure lets Unit through as Unit, but a method returning Any will have an
+ // erased return type of Object and should also allow Unit.
+ def isDefinitelyUnit = (resultSym == UnitClass)
+ def isMaybeUnit = (resultSym == ObjectClass) || isDefinitelyUnit
+ // If there's any chance this signature could be met by an Array.
+ val isArrayMethodSignature = {
def typesMatchApply = paramTypes match {
case List(tp) => tp <:< IntClass.tpe
case _ => false
}
def typesMatchUpdate = paramTypes match {
- case List(tp1, tp2) => (tp1 <:< IntClass.tpe) && (UnitClass.tpe <:< structResType)
+ case List(tp1, tp2) => (tp1 <:< IntClass.tpe) && isMaybeUnit
case _ => false
}
@@ -354,8 +364,29 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
(methSym.name == nme.update && typesMatchUpdate)
}
+ /* Some info about the argument at the call site. */
+ val qualSym = qual.tpe.typeSymbol
+ val args = qual :: params
+ def isDefinitelyArray = (qualSym == ArrayClass)
+ def isMaybeArray = (qualSym == ObjectClass) || isDefinitelyArray
+ def isMaybeBoxed = platform isMaybeBoxed qualSym
+
+ // This is complicated a bit by trying to handle Arrays correctly.
+ // Under normal circumstances if the erased return type is Object then
+ // we're not going to box it to Unit, but that is the situation with
+ // a signature like def f(x: { def update(x: Int, y: Long): Any })
+ //
+ // However we only want to do that boxing if it has been determined
+ // to be an Array and a method returning Unit. But for this fixResult
+ // could be called in one place: instead it is called separately from the
+ // unconditional outcomes (genValueCall, genArrayCall, genDefaultCall.)
+ def fixResult(tree: Tree, mustBeUnit: Boolean = false) =
+ if (mustBeUnit || resultSym == UnitClass) BLOCK(tree, REF(BoxedUnit_UNIT)) // boxed unit
+ else if (resultSym == ObjectClass) tree // no cast necessary
+ else tree AS_ATTR boxedResType // cast to expected type
+
/** Normal non-Array call */
- def defaultCall = {
+ def genDefaultCall = {
// reflective method call machinery
val invokeName = MethodClass.tpe member nme.invoke_ // reflect.Method.invoke(...)
def cache = REF(reflectiveMethodCache(ad.symbol.name.toString, paramTypes)) // cache Symbol
@@ -369,36 +400,40 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
def catchBody = Throw(Apply(Select(Ident(invokeExc), nme.getCause), Nil))
// try { method.invoke } catch { case e: InvocationTargetExceptionClass => throw e.getCause() }
- fixResult( TRY (invocation) CATCH { CASE (catchVar) ==> catchBody } ENDTRY )
+ fixResult(TRY (invocation) CATCH { CASE (catchVar) ==> catchBody } ENDTRY)
}
- def useValueOperator =
- platform.isMaybeBoxed(qualSym) && // may be a boxed value class
- (getPrimitiveReplacementForStructuralCall isDefinedAt methSym.name) &&
- ((resType :: paramTypes) forall (x => isJavaValueClass(x.typeSymbol))) // issue #1110
-
- def genArrayCall = methSym.name match {
- case nme.length => REF(boxMethod(IntClass)) APPLY (REF(arrayLengthMethod) APPLY args)
- case nme.update => REF(arrayUpdateMethod) APPLY List(args(0), (REF(unboxMethod(IntClass)) APPLY args(1)), args(2))
- case nme.apply => REF(arrayApplyMethod) APPLY List(args(0), (REF(unboxMethod(IntClass)) APPLY args(1)))
- case nme.clone_ => REF(arrayCloneMethod) APPLY List(args(0))
+ /** A possible primitive method call, represented by methods in BoxesRunTime. */
+ def genValueCall(operator: Symbol) = fixResult(REF(operator) APPLY args)
+ def genValueCallWithTest = {
+ val (operator, test) = getPrimitiveReplacementForStructuralCall(methSym.name)
+ IF (test) THEN genValueCall(operator) ELSE genDefaultCall
}
- def genConditionalArrayCall =
- IF ((qual GETCLASS()) DOT nme.isArray) THEN genArrayCall ELSE defaultCall
- val callCode =
- if (useValueOperator) {
- val (operator, test) = getPrimitiveReplacementForStructuralCall(methSym.name)
- IF (test) THEN fixResult(REF(operator) APPLY args) ELSE defaultCall
- }
- else if (isArrayMethodSignature) {
- if (qualSym == ArrayClass) genArrayCall
- else if (qualSym == ObjectClass) genConditionalArrayCall
- else defaultCall
- }
- else defaultCall
+ /** A native Array call. */
+ def genArrayCall = fixResult(
+ methSym.name match {
+ case nme.length => REF(boxMethod(IntClass)) APPLY (REF(arrayLengthMethod) APPLY args)
+ case nme.update => REF(arrayUpdateMethod) APPLY List(args(0), (REF(unboxMethod(IntClass)) APPLY args(1)), args(2))
+ case nme.apply => REF(arrayApplyMethod) APPLY List(args(0), (REF(unboxMethod(IntClass)) APPLY args(1)))
+ case nme.clone_ => REF(arrayCloneMethod) APPLY List(args(0))
+ },
+ mustBeUnit = methSym.name == nme.update
+ )
- localTyper typed callCode
+ /** A conditional Array call, when we can't determine statically if the argument is
+ * an Array, but the structural type method signature is consistent with an Array method
+ * so we have to generate both kinds of code.
+ */
+ def genArrayCallWithTest =
+ IF ((qual GETCLASS()) DOT nme.isArray) THEN genArrayCall ELSE genDefaultCall
+
+ localTyper typed (
+ if (isMaybeBoxed && isJavaValueMethod) genValueCallWithTest
+ else if (isArrayMethodSignature && isDefinitelyArray) genArrayCall
+ else if (isArrayMethodSignature && isMaybeArray) genArrayCallWithTest
+ else genDefaultCall
+ )
}
if (settings.refinementMethodDispatch.value == "invoke-dynamic") {
@@ -423,7 +458,7 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
/* 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 on the class, 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
@@ -445,10 +480,9 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
val sym = currentOwner.newValue(ad.pos, mkTerm("qual")) setInfo qual0.tpe
qual = REF(sym)
- def structResType = if (isJavaValueClass(resType.typeSymbol)) boxedClass(resType.typeSymbol).tpe else resType
BLOCK(
VAL(sym) === qual0,
- callAsReflective(mparams map (_.tpe), resType, structResType)
+ callAsReflective(mparams map (_.tpe), resType)
)
}
}
diff --git a/test/files/run/bug3175.check b/test/files/run/bug3175.check
index daf2c23ab5..12246140f4 100644
--- a/test/files/run/bug3175.check
+++ b/test/files/run/bug3175.check
@@ -6,3 +6,6 @@
5
5
5
+100
+jabooboo
+hi mom
diff --git a/test/files/run/bug3175.scala b/test/files/run/bug3175.scala
index 9cccff52f9..78660d4085 100644
--- a/test/files/run/bug3175.scala
+++ b/test/files/run/bug3175.scala
@@ -17,6 +17,11 @@ object Test {
def f9(x: { def apply(x: Int): Int }) = x(0)
def f10(x: { def apply(x: Int): Long }) = x(0)
+ // update has some interesting special cases
+ def f11(x:{ def update(x: Int, y: Long): Any }, y: Long) = x(0) = y
+ def f12(x:{ def update(x: Int, y: String): AnyVal }, y: String) = x(0) = y
+ def f13(x:{ def update(x: Int, y: String): AnyRef }, y: String) = x(0) = y
+
// doesn't work yet, see #3197
// def fclone(x:{ def clone(): AnyRef }) = x.clone()
@@ -40,5 +45,11 @@ object Test {
println(f8(Array(5)))
println(f9(Array(5)))
println(f10(Array(5)))
+
+ f11(longs, 100L)
+ f12(strs, "jabooboo")
+ println(longs(0))
+ println(strs(0))
+ f13(new { def update(x: Int, y: String): List[Int] = { println("hi mom") ; Nil } }, "irrelevant")
}
}