From dac1488a889ff5952ff85e87ec4acd7d72a74891 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Wed, 28 Nov 2012 12:03:33 +0100 Subject: Fix for SI-6731, dropped trees in selectDynamic. I rewrote mkInvoke entirely, and boosted the test coverage. --- .../scala/tools/nsc/typechecker/Typers.scala | 85 ++++++------ test/files/run/t6731.check | 40 ++++++ test/files/run/t6731.scala | 143 +++++++++++++++++++++ 3 files changed, 221 insertions(+), 47 deletions(-) create mode 100644 test/files/run/t6731.check create mode 100644 test/files/run/t6731.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a2aca45e8f..757107a9b6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3926,54 +3926,45 @@ trait Typers extends Modes with Adaptations with Tags { * */ def mkInvoke(cxTree: Tree, tree: Tree, qual: Tree, name: Name): Option[Tree] = { - debuglog(s"mkInvoke($cxTree, $tree, $qual, $name)") + log(s"dyna.mkInvoke($cxTree, $tree, $qual, $name)") + val treeSelection = treeInfo.methPart(tree) + def isDesugaredApply = treeSelection match { + case Select(`qual`, nme.apply) => true + case _ => false + } acceptsApplyDynamicWithType(qual, name) map { tp => - // tp eq NoType => can call xxxDynamic, but not passing any type args (unless specified explicitly by the user) - // in scala-virtualized, when not NoType, tp is passed as type argument (for selection on a staged Struct) - - // strip off type application -- we're not doing much with outer, - // so don't bother preserving cxTree's attributes etc - val cxTree1 = cxTree match { - case t: ValOrDefDef => t.rhs - case t => t - } - val cxTree2 = cxTree1 match { - case Typed(t, tpe) => t // ignore outer type annotation - case t => t - } - val (outer, explicitTargs) = cxTree2 match { - case TypeApply(fun, targs) => (fun, targs) - case Apply(TypeApply(fun, targs), args) => (Apply(fun, args), targs) - case Select(TypeApply(fun, targs), nme) => (Select(fun, nme), targs) - case t => (t, Nil) - } - def hasNamedArg(as: List[Tree]) = as.collectFirst{case AssignOrNamedArg(lhs, rhs) =>}.nonEmpty - - def desugaredApply = tree match { - case Select(`qual`, nme.apply) => true - case _ => false + // If tp == NoType, pass only explicit type arguments to applyXXX. Not used at all + // here - it is for scala-virtualized, where tp will be passed as an argument (for + // selection on a staged Struct) + def hasNamed(args: List[Tree]): Boolean = args exists (_.isInstanceOf[AssignOrNamedArg]) + // not supported: foo.bar(a1,..., an: _*) + def hasStar(args: List[Tree]) = treeInfo.isWildcardStarArgList(args) + def applyOp(args: List[Tree]) = if (hasNamed(args)) nme.applyDynamicNamed else nme.applyDynamic + def matches(t: Tree) = isDesugaredApply || treeInfo.methPart(t) == treeSelection + + /** Note that the trees which arrive here are potentially some distance from + * the trees of direct interest. `cxTree` is some enclosing expression which + * may apparently be arbitrarily larger than `tree`; and `tree` itself is + * too small, having at least in some cases lost its explicit type parameters. + * This logic is designed to use `tree` to pinpoint the immediately surrounding + * Apply/TypeApply/Select node, and only then creates the dynamic call. + * See SI-6731 among others. + */ + def findSelection(t: Tree): Option[(TermName, Tree)] = t match { + case Apply(fn, args) if hasStar(args) => DynamicVarArgUnsupported(tree, applyOp(args)) ; None + case Apply(fn, args) if matches(fn) => Some((applyOp(args), fn)) + case Assign(lhs, _) if matches(lhs) => Some((nme.updateDynamic, lhs)) + case _ if matches(t) => Some((nme.selectDynamic, t)) + case _ => t.children flatMap findSelection headOption } - // note: context.tree includes at most one Apply node - // thus, we can't use it to detect we're going to receive named args in expressions such as: - // qual.sel(a)(a2, arg2 = "a2") - val oper = outer match { - case Apply(q, as) if q == tree || desugaredApply => - val oper = - if (hasNamedArg(as)) nme.applyDynamicNamed - else nme.applyDynamic - // not supported: foo.bar(a1,..., an: _*) - if (treeInfo.isWildcardStarArgList(as)) { - DynamicVarArgUnsupported(tree, oper) - return Some(setError(tree)) - } else oper - case Assign(`tree`, _) => nme.updateDynamic - case _ => nme.selectDynamic + findSelection(cxTree) match { + case Some((opName, tapply)) => + val targs = treeInfo.typeArguments(tapply) + val fun = gen.mkTypeApply(Select(qual, opName), targs) + atPos(qual.pos)(Apply(fun, Literal(Constant(name.decode)) :: Nil)) + case _ => + setError(tree) } - - val dynSel = Select(qual, oper) - val tappSel = if (explicitTargs.nonEmpty) TypeApply(dynSel, explicitTargs) else dynSel - - atPos(qual.pos)(Apply(tappSel, List(Literal(Constant(name.decode))))) } } @@ -3981,7 +3972,7 @@ trait Typers extends Modes with Adaptations with Tags { silent(typeTree) match { case SilentResultValue(r) => r case SilentTypeError(err) => DynamicRewriteError(tree, err) - } + } } } @@ -5375,7 +5366,7 @@ trait Typers extends Modes with Adaptations with Tags { case tt @ TypeTree() => tree setOriginal tt.original case _ => tree } - } + } else // we should get here only when something before failed // and we try again (@see tryTypedApply). In that case we can assign diff --git a/test/files/run/t6731.check b/test/files/run/t6731.check new file mode 100644 index 0000000000..a5d59bd378 --- /dev/null +++ b/test/files/run/t6731.check @@ -0,0 +1,40 @@ +Mono$.bar() +Mono$.bar +Mono$.bar() +Mono$.bar +Mono$.bar +Mono$.baz +Mono$.bar(bippy=1, boppy=2) +Mono$.baz +Poly.bar[Nothing] +Poly.bar[Int] +Poly.bar[Nothing]() +Poly.bar[Int]() +Poly.bar[Int](1, 2, 3) +Poly.bar[Nothing] +Poly.bar[Int] +Poly.bar[Nothing]() +Poly.bar[Int]() +Poly.bar[Int](1, 2, 3) +Updating.bar +Updating.bar = b +Nest1$Nest2$Nest3$.bippy(1, 2, 3) +Nest1$Nest2$Nest3$.bippy +Named.bippy(a=1, b=2) +Named.boppy(c=3, d=4) +Named.apply() +Named.apply() +Named.apply(e=5, f=6) +Named2.bippy(1)(q0, c) +Named2.bippy(1)(q0, c) +Named2.bippy(1)(b, q0) +Named2.bippy(1)(q0, c) +Named2.bippy(1)(c, b) +Named2.bippy(1)(b, c) +Named2.bippy(1)(q0, c) +Named2.bippy(2)(b, c) +Named2.bippy(1)(q0, c) +Named2.bippy(5)(b, c) +Named2.dingus(100)(b, dong) +Named2.bippy(1)(q0, q1) +Named2.hello(100)(!!, !!) diff --git a/test/files/run/t6731.scala b/test/files/run/t6731.scala new file mode 100644 index 0000000000..89d212e91e --- /dev/null +++ b/test/files/run/t6731.scala @@ -0,0 +1,143 @@ +import scala.language.dynamics +import scala.reflect.{ ClassTag, classTag } + +object Util { + def show[T](x: T): T = { println(x) ; x } + def mkArgs(xs: Any*) = xs map { case ((k, v)) => k + "=" + v ; case x => "" + x } mkString ("(", ", ", ")") +} +import Util._ + +abstract class MonoDynamic extends Dynamic { + def selectDynamic(name: String): String = show(this + "." + name) + def applyDynamic(name: String)(args: Any*): String = show(this + "." + name + mkArgs(args: _*)) + def applyDynamicNamed(name: String)(args: (String, Any)*): String = show(this + "." + name + mkArgs(args: _*)) + + override def toString = this.getClass.getName split '.' last +} + +object Mono extends MonoDynamic { + def f(s: String): String = s + + def f1 = this.bar() + def f2 = this.bar + def f3 = f(this.bar()) + def f4 = f(this.bar) + def f5 = f(f(f(f(f(f(this.bar)))))) + f(f(f(f(f(f(this.baz)))))) + def f6 = f(f(f(f(f(f(this.bar(bippy = 1, boppy = 2))))))) + f(f(f(f(f(f(this.baz)))))) +} + +object Poly extends Dynamic { + def selectDynamic[T: ClassTag](name: String): String = show(s"$this.$name[${classTag[T]}]") + def applyDynamic[T: ClassTag](name: String)(args: Any*): String = show(args.mkString(s"$this.$name[${classTag[T]}](", ", ", ")")) + + def f(s: String): String = s + + def f1 = this.bar + def f2 = this.bar[Int] + def f3 = this.bar() + def f4 = this.bar[Int]() + def f5 = this.bar[Int](1, 2, 3) + + def f6 = f(f(this.bar)) + def f7 = f(f(this.bar[Int])) + def f8 = f(f(this.bar())) + def f9 = f(f(this.bar[Int]())) + def f10 = f(f(this.bar[Int](1, 2, 3))) + + override def toString = "Poly" +} + +object Updating extends Dynamic { + def selectDynamic(name: String): String = show(s"$this.$name") + def updateDynamic(name: String)(value: Any): String = show(s"$this.$name = $value") + + def f1 = this.bar + def f2 = this.bar = "b" + + override def toString = "Updating" +} + +object Nest1 extends Dynamic { + def applyDynamic(name: String)(args: Any*): Nest2.type = Nest2 + + object Nest2 extends Dynamic { + def applyDynamicNamed(name: String)(args: (String, Any)*): Nest3.type = Nest3 + + object Nest3 extends MonoDynamic { + + } + } + + def f1 = Nest1.bip().bop(foo = "bar").bippy(1, 2, 3) + def f2 = Nest1.bip("abc").bop(foo = 5).bippy +} + +object Named extends Dynamic { + def applyDynamic(name: String)(args: Any*): Named.type = { + show(this + "." + name + mkArgs(args: _*)) + this + } + def applyDynamicNamed(name: String)(args: (String, Any)*): Named.type = { + show(this + "." + name + mkArgs(args: _*)) + this + } + + def f1 = this.bippy(a = 1, b = 2).boppy(c = 3, d = 4)()()(e = 5, f = 6) + override def toString = "Named" +} + +object Named2 extends Dynamic { + def applyDynamic(name: String)(a: Any)(b: Any = "b", c: Any = "c"): Named2.type = { + show(this + "." + name + mkArgs(a) + mkArgs(b, c)) + this + } + def applyDynamicNamed(name: String)(a: (String, Any))(b: (String, Any), c: (String, Any)): Named2.type = { + show(this + "." + name + mkArgs(a) + mkArgs(b, c)) + this + } + + def f1 = this.bippy(1)(b = "q0") + def f2 = this.bippy(1)("q0") + def f3 = this.bippy(1)(c = "q0") + def f4 = this.bippy(1)("q0") + def f5 = this.bippy(1)(c = "b", b = "c") + def f6 = this.bippy(1)("b", "c") + def f7 = this.bippy(1)(b = "q0").bippy(2)() + def f8 = this.bippy(1)("q0").bippy(5)(c = "c").dingus(100)(c = "dong") + def f9 = this.bippy(1)(b = "q0", c = "q1").hello(100)("!!", "!!") + + override def toString = "Named2" +} + + +object Test { + def main(args: Array[String]): Unit = { + { + import Mono._ + f1 ; f2 ; f3 ; f4 ; f5 + f6 + } + { + import Poly._ + f1 ; f2 ; f3 ; f4 ; f5 + f6 ; f7 ; f8 ; f9 ; f10 + } + { + import Updating._ + f1 ; f2 + } + { + import Nest1._ + f1 ; f2 + } + { + import Named._ + f1 + } + { + import Named2._ + f1 ; f2 ; f3 ; f4 ; f5 + f6 ; f7 ; f8 ; f9 + } + } +} -- cgit v1.2.3 From a6941944bf80f660722e9151801776715c3e4ab5 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Wed, 28 Nov 2012 18:36:47 +0100 Subject: Test cases for SI-5726, SI-5733, SI-6320, SI-6551, SI-6722. All tickets involving selectDynamic fixed by the prior commit. It also fixes SI-6663, but that already has a test case. --- test/files/pos/t5726.scala | 17 +++++++++++++++ test/files/pos/t6551.scala | 2 +- test/files/pos/t6722.scala | 11 ++++++++++ test/files/run/t5733.check | 2 ++ test/files/run/t5733.scala | 53 ++++++++++++++++++++++++++++++++++++++++++++++ test/files/run/t6320.check | 17 +++++++++++++++ test/files/run/t6320.scala | 9 ++++++++ 7 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 test/files/pos/t5726.scala create mode 100644 test/files/pos/t6722.scala create mode 100644 test/files/run/t5733.check create mode 100644 test/files/run/t5733.scala create mode 100644 test/files/run/t6320.check create mode 100644 test/files/run/t6320.scala diff --git a/test/files/pos/t5726.scala b/test/files/pos/t5726.scala new file mode 100644 index 0000000000..b28ebd8674 --- /dev/null +++ b/test/files/pos/t5726.scala @@ -0,0 +1,17 @@ +import scala.language.dynamics + +class DynamicTest extends Dynamic { + def selectDynamic(name: String) = s"value of $name" + def updateDynamic(name: String)(value: Any) { + println(s"You have just updated property '$name' with value: $value") + } +} + +object MyApp extends App { + def testing() { + val test = new DynamicTest + test.firstName = "John" + } + + testing() +} diff --git a/test/files/pos/t6551.scala b/test/files/pos/t6551.scala index fb68663809..8bb396a19f 100644 --- a/test/files/pos/t6551.scala +++ b/test/files/pos/t6551.scala @@ -1,4 +1,4 @@ -import language.dynamics +import scala.language.dynamics object Test { def main(args: Array[String]) { diff --git a/test/files/pos/t6722.scala b/test/files/pos/t6722.scala new file mode 100644 index 0000000000..576746c915 --- /dev/null +++ b/test/files/pos/t6722.scala @@ -0,0 +1,11 @@ +import scala.language.dynamics + +class Dyn extends Dynamic { + def selectDynamic(s: String): Dyn = new Dyn + def get[T]: T = null.asInstanceOf[T] +} + +object Foo { + val dyn = new Dyn + dyn.foo.bar.baz.get[String] +} diff --git a/test/files/run/t5733.check b/test/files/run/t5733.check new file mode 100644 index 0000000000..e697046a94 --- /dev/null +++ b/test/files/run/t5733.check @@ -0,0 +1,2 @@ +Running ABTest asserts +Done diff --git a/test/files/run/t5733.scala b/test/files/run/t5733.scala new file mode 100644 index 0000000000..360264e4ed --- /dev/null +++ b/test/files/run/t5733.scala @@ -0,0 +1,53 @@ +import scala.language.dynamics + +object A extends Dynamic { + var a = "a" + + def selectDynamic(method:String): String = a + + def updateDynamic(method:String)(v:String) { a = v } +} + +class B extends Dynamic { + var b = "b" + + def selectDynamic(method:String): String = b + + def updateDynamic(method:String)(v:String) { b = v } +} + +object Test extends App { + assert( A.foo == "a" ) + assert( A.bar == "a" ) + A.aaa = "aaa" + assert( A.bar == "aaa" ) + + val b = new B + assert( b.foo == "b" ) + assert( b.bar == "b" ) + b.bbb = "bbb" + assert( b.bar == "bbb" ) + + { + println("Running ABTest asserts") + A.a = "a" + (new ABTest).test() + } + + println("Done") +} + +class ABTest { + def test() { + assert( A.foo == "a" ) + assert( A.bar == "a" ) + A.aaa = "aaa" + assert( A.bar == "aaa" ) + + val b = new B + assert( b.foo == "b" ) + assert( b.bar == "b" ) + b.bbb = "bbb" + assert( b.bar == "bbb" ) + } +} diff --git a/test/files/run/t6320.check b/test/files/run/t6320.check new file mode 100644 index 0000000000..e56bacd223 --- /dev/null +++ b/test/files/run/t6320.check @@ -0,0 +1,17 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> + +scala> import scala.language.dynamics +import scala.language.dynamics + +scala> class Dyn(m: Map[String, Any]) extends Dynamic { def selectDynamic[T](s: String): T = m(s).asInstanceOf[T] } +defined class Dyn + +scala> new Dyn(Map("foo" -> 10)).foo[Int] +res0: Int = 10 + +scala> + +scala> diff --git a/test/files/run/t6320.scala b/test/files/run/t6320.scala new file mode 100644 index 0000000000..26085a3d7d --- /dev/null +++ b/test/files/run/t6320.scala @@ -0,0 +1,9 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ +import scala.language.dynamics +class Dyn(m: Map[String, Any]) extends Dynamic { def selectDynamic[T](s: String): T = m(s).asInstanceOf[T] } +new Dyn(Map("foo" -> 10)).foo[Int] + """ +} -- cgit v1.2.3