summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/ast/NodePrinters.scala13
-rw-r--r--src/compiler/scala/tools/nsc/symtab/Definitions.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala203
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala35
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala14
-rw-r--r--test/files/run/structural.check29
-rw-r--r--test/files/run/structural.scala131
7 files changed, 409 insertions, 18 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala
index 24fd0971cd..4d1cd80d84 100644
--- a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala
+++ b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala
@@ -131,6 +131,19 @@ abstract class NodePrinters {
println(" )")
}
printcln(")")
+ case ApplyDynamic(fun, args) =>
+ println("ApplyDynamic(" + nodeinfo(tree))
+ traverse(fun, level + 1, true)
+ if (args.isEmpty)
+ println(" List() // no argument")
+ else {
+ val n = args.length
+ println(" List( // " + n + " argument(s)")
+ for (i <- 0 until n)
+ traverse(args(i), level + 2, i < n-1)
+ println(" )")
+ }
+ printcln(")")
case Block(stats, expr) =>
println("Block(")
if (stats.isEmpty)
diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala
index c0c66fc68b..a4a3b0ce79 100644
--- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala
+++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala
@@ -38,6 +38,7 @@ trait Definitions {
var SingletonClass: Symbol = _
var ClassClass: Symbol = _
+ var MethodClass: Symbol = _
var StringClass: Symbol = _
var ThrowableClass: Symbol = _
var NullPointerExceptionClass: Symbol = _
@@ -758,6 +759,7 @@ trait Definitions {
StringClass = getClass(if (forMSIL) "System.String" else "java.lang.String")
ClassClass = getClass(if (forMSIL) "System.Type" else "java.lang.Class")
+ MethodClass = getClass("java.lang.reflect.Method")
ThrowableClass = getClass(if (forMSIL) "System.Exception" else "java.lang.Throwable")
NullPointerExceptionClass = getClass(if (forMSIL) "System.NullReferenceException"
else "java.lang.NullPointerException")
diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
index 27882f588f..c1bf23fbc0 100644
--- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala
+++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
@@ -89,7 +89,210 @@ abstract class CleanUp extends Transform {
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)
+
+ /* 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.symbol == UnitClass)
+ Block (
+ List(tree),
+ gen.mkAttributedRef(BoxedUnit_UNIT)
+ )
+ else {
+ val sym = currentOwner.newValue(tree.pos, newTermName(unit.fresh.newName)) setInfo ObjectClass.tpe
+ Block(
+ List(ValDef(sym, tree)),
+ If(
+ Apply(Select(Literal(Constant(null)), Any_==), List(gen.mkAttributedRef(sym))),
+ Literal(Constant(null)),
+ if (resType.symbol == ArrayClass)
+ Apply(
+ Select(
+ gen.mkAttributedRef(ScalaRunTimeModule),
+ ScalaRunTimeModule.tpe.member(nme.boxArray)
+ ),
+ List(gen.mkAttributedRef(sym))
+ )
+ else
+ gen.mkAttributedCast(gen.mkAttributedRef(sym), 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.symbol == ArrayClass) {
+ val sym = currentOwner.newValue(tree.pos, newTermName(unit.fresh.newName)) 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
+ }
+ }
+
+ /* Transforms a list of types into a list of trees representing these types
+ * as java.lang.Class instances. */
+ def paramTypeClasses(paramTypes: List[Type]): List[Tree] =
+ paramTypes map { pt => Literal(Constant(pt)) }
+
+ /* 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 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
+ * 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.symbol)) boxedClass(resType.symbol).tpe else resType) {
+ Apply(
+ Select(
+ Apply(
+ Select(
+ Apply(Select(qual, ObjectClass.tpe.member(nme.getClass_)), Nil),
+ ClassClass.tpe.member(nme.getMethod_)
+ ),
+ List(
+ Literal(Constant(ad.symbol.name.toString)),
+ ArrayValue(TypeTree(ClassClass.tpe), paramTypeClasses(paramTypes))
+ )
+ ),
+ MethodClass.tpe.member(nme.invoke_)
+ ),
+ List(
+ transform(qual),
+ ArrayValue(TypeTree(ObjectClass.tpe), fixParams(params, paramTypes))
+ )
+ )
+ }
+ })
+ }
+
+ /* 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) =>
classConstantMeth.clear
newDefs.clear
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala
index bb42922961..63445b714c 100644
--- a/src/compiler/scala/tools/nsc/transform/Erasure.scala
+++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala
@@ -817,13 +817,34 @@ abstract class Erasure extends AddInterfaces with typechecker.Analyzer {
}
case _ => tree
}
- else fn match {
- case Select(qual, _) if (settings.Xexperimental.value &&
- fn.symbol.owner.isRefinementClass &&
- fn.symbol.allOverriddenSymbols.isEmpty) =>
- ApplyDynamic(qual, args) setSymbol fn.symbol setPos tree.pos
- case _ =>
- tree
+ else {
+ def doDynamic(fn: Tree, qual: Tree): Tree = {
+ if (fn.symbol.owner.isRefinementClass && fn.symbol.allOverriddenSymbols.isEmpty) {
+ /* a dynamic apply can only be done when all parameters are statically known
+ * (i.e. no type parameters in refinement) so that static dispatch can be done
+ * properly. See phase cleanup for more about that. */
+ def testParams(tpe: Type): Unit = tpe match {
+ case MethodType(paramTypes, resType) =>
+ paramTypes map { t => t match {
+ case TypeRef(NoPrefix, sym, Nil) if sym.isAbstractType && sym.owner != fn.symbol =>
+ unit.error(fn.pos,"Parameter is defined as type variable "+sym.name.toString+" in a refinement.")
+ case _ =>
+ }
+ }
+ case PolyType(tp, res) => testParams(res)
+ }
+ testParams(fn.symbol.tpe)
+
+ ApplyDynamic(qual, args) setSymbol fn.symbol setPos tree.pos
+ }
+ else tree
+ }
+ fn match {
+ case Select(qual, _) => doDynamic(fn, qual)
+ case TypeApply(fni@Select(qual, _), _) => doDynamic(fni, qual)// type parameters are irrelevant in case of dynamic call
+ case _ =>
+ tree
+ }
}
case Select(_, _) =>
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 6122ef4615..39bc7b9669 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -1217,13 +1217,8 @@ trait Typers { self: Analyzer =>
def anonymousClassRefinement(clazz: Symbol): Type = {
val tp = refinedType(clazz.info.parents, clazz.owner)
val thistp = tp.symbol.thisType
- def canBeInRefinement(sym: Symbol) =
- settings.Xexperimental.value ||
- tp.nonPrivateMember(sym.name).filter { other =>
- !other.isTerm || (thistp.memberType(other) matches thistp.memberType(sym))
- } != NoSymbol
for (val sym <- clazz.info.decls.toList) {
- if (sym.isPublic && !sym.isClass && !sym.isConstructor && canBeInRefinement(sym))
+ if (sym.isPublic && !sym.isClass && !sym.isConstructor)
addMember(thistp, tp, sym)
}
tp
@@ -1359,11 +1354,8 @@ trait Typers { self: Analyzer =>
val stats1 = typedStats(stats, NoSymbol)
for (val stat <- stats1; stat.isDef) {
val member = stat.symbol
- if (context.owner.info.baseClasses.tail forall
- (bc => member.matchingSymbol(bc, context.owner.thisType) == NoSymbol)) {
- if (!settings.Xexperimental.value && !member.isErroneous)
- error(member.pos, member.toString+" does not refine a member of its base type")
- } else {
+ if (!(context.owner.info.baseClasses.tail forall
+ (bc => member.matchingSymbol(bc, context.owner.thisType) == NoSymbol))) {
member setFlag OVERRIDE
}
}
diff --git a/test/files/run/structural.check b/test/files/run/structural.check
new file mode 100644
index 0000000000..4d1b20c40c
--- /dev/null
+++ b/test/files/run/structural.check
@@ -0,0 +1,29 @@
+ 1. hey
+ 2. 11
+ 3. dee
+ 4. iei
+ 5. 6
+ 6. 51
+ 7. 2
+ 8. 11
+10. 12
+11. eitch
+12. 1
+13. ohone
+14. 1
+15. ()
+16. one
+17. tieone
+18. 2
+19. 20432397
+20. 1
+21. ()
+22. one
+23. oy
+24. 1
+25. null
+26. iei
+31. 4
+32. ()
+33. iei
+33. tieone
diff --git a/test/files/run/structural.scala b/test/files/run/structural.scala
new file mode 100644
index 0000000000..d49a4ea3ef
--- /dev/null
+++ b/test/files/run/structural.scala
@@ -0,0 +1,131 @@
+object Test extends Application {
+
+ val o1 = new Object { override def toString = "ohone" }
+ val o2 = new Object { override def toString = "ohtwo" }
+
+ val t1 = new Tata("tieone")
+ val t2 = new Tata("tietwo")
+
+ class Tata(name: String) {
+ override def toString = name
+ def tatMe = "oy"
+ }
+
+ class Titi extends Tata("titi")
+
+ object Rec {
+ val a = 1
+ val b = 2
+ val c = "hey"
+ def d(x: AnyRef) = new Object { override def toString = "dee" }
+ def e(x: Tata) = new Tata("iei")
+ def f(x: Int) = x + 1
+ def g(x: Int) = { v = x }
+ def h(x: Unit) = new Object { override def toString = "eitch" }
+ def i(x: Array[Int]) = x(0)
+ def j(x: Array[AnyRef]) = x(0)
+ def k(x: Array[Char]) = x(0)
+ def l(x: Array[Unit]) = x(0)
+ def m(x: Array[String]) = x(0)
+ def n(x: Array[Tata]) = x(0)
+ def o: Array[Int] = Array(1, 2, 3)
+ def p: Array[AnyRef] = Array(o1, o2)
+ def q: Array[Char] = Array('1', '2')
+ def r: Array[Unit] = Array((), ())
+ def s: Array[String] = Array("one", "two")
+ def t: Array[Tata] = Array(t1, t2)
+ def u[T](f: T=>T, v:T): T = f(v)
+ var v = 4
+ var w = 11
+ val x = t1
+ val y: Tata = null
+ def z(t: Tata) = ()
+ }
+
+ type rt = Object {
+ val a: Int;
+ val c: String;
+ def d(x: AnyRef): AnyRef
+ def e(x: Tata): Tata
+ def f(x: Int): Int;
+ def h(x: Unit): AnyRef;
+ def i(x: Array[Int]): Int
+ def j(x: Array[AnyRef]): AnyRef
+ def k(x: Array[Char]): Char
+ def l(x: Array[Unit]): Unit
+ def m(x: Array[String]): String
+ def n(x: Array[Tata]): Tata
+ def o: Array[Int]
+ def p: Array[AnyRef]
+ def q: Array[Char]
+ def r: Array[Unit]
+ def s: Array[String]
+ def t: Array[Tata]
+ def u[T](f: T=>T, v:T): T
+ var v: Int
+ val y: Tata
+ }
+
+ def l (r: rt) = {
+
+ Console.println(" 1. " + r.c)
+ Console.println(" 2. " + r.a + 1)
+ Console.println(" 3. " + r.d(o1))
+ Console.println(" 4. " + r.e(t1))
+ Console.println(" 5. " + (r.f(4) + 1))
+ Console.println(" 6. " + r.f(4) + 1)
+ Console.println(" 7. " + r.f(r.a))
+ Console.println(" 8. " + r.v)
+ r.v = r.v + 1
+ Console.println("10. " + r.v)
+ Console.println("11. " + r.h(()))
+ Console.println("12. " + r.i(Array(1, 2, 3)))
+ Console.println("13. " + r.j(Array(o1, o2)))
+ Console.println("14. " + r.k(Array('1', '2')))
+ Console.println("15. " + r.l(Array((), ())))
+ Console.println("16. " + r.m(Array("one", "two")))
+ Console.println("17. " + r.n(Array(t1, t2)))
+ Console.println("18. " + (r.o(0) + 1))
+ Console.println("19. " + r.p(0).hashCode())
+ Console.println("20. " + r.q(0))
+ Console.println("21. " + r.r(0))
+ Console.println("22. " + r.m(r.s))
+ Console.println("23. " + r.t(0).tatMe)
+ Console.println("24. " + r.u[Int](_+1,0))
+ Console.println("25. " + r.y)
+ Console.println("26. " + r.e(null))
+
+ }
+
+ /*def ma[T](r: Object{def e(x: T): T; val x: T}) {
+ Console.println("30. " + r.e(r.x)) // static error
+ }*/
+
+ def mb(r: Object{def e[T](x: T): T}) {
+ Console.println("31. " + r.e[Int](4)) // while this is ok
+ }
+
+ def m1(r: Object{def z(x: Tata): Unit}) {
+ Console.println("32. " + r.z(new Titi)) // while this is ok
+ }
+
+ def m2[T](r: Object{def e(x: Tata): T; val x: Tata}) {
+ Console.println("33. " + r.e(r.x)) // and this too
+ }
+
+ class Rec3[T] {
+ def e(x: T): T = x
+ }
+
+ def m3[T](r: Rec3[T], x: T) {
+ Console.println("33. " + r.e(x)) // and this too
+ }
+
+ Rec.g(11)
+
+ this.l(Rec)
+ this.mb(new Object{def e[T](x: T): T = x})
+ this.m1(Rec)
+ this.m2[Tata](Rec)
+ this.m3[Tata](new Rec3[Tata], t1)
+} \ No newline at end of file