Skip to content

Commit edbcc64

Browse files
committed
Merge pull request scala#1681 from paulp/issue/6731
Fix for SI-6731, dropped trees in selectDynamic.
2 parents fd57069 + a694194 commit edbcc64

File tree

10 files changed

+331
-48
lines changed

10 files changed

+331
-48
lines changed

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 38 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3926,62 +3926,53 @@ trait Typers extends Modes with Adaptations with Tags {
39263926
*
39273927
*/
39283928
def mkInvoke(cxTree: Tree, tree: Tree, qual: Tree, name: Name): Option[Tree] = {
3929-
debuglog(s"mkInvoke($cxTree, $tree, $qual, $name)")
3929+
log(s"dyna.mkInvoke($cxTree, $tree, $qual, $name)")
3930+
val treeSelection = treeInfo.methPart(tree)
3931+
def isDesugaredApply = treeSelection match {
3932+
case Select(`qual`, nme.apply) => true
3933+
case _ => false
3934+
}
39303935
acceptsApplyDynamicWithType(qual, name) map { tp =>
3931-
// tp eq NoType => can call xxxDynamic, but not passing any type args (unless specified explicitly by the user)
3932-
// in scala-virtualized, when not NoType, tp is passed as type argument (for selection on a staged Struct)
3933-
3934-
// strip off type application -- we're not doing much with outer,
3935-
// so don't bother preserving cxTree's attributes etc
3936-
val cxTree1 = cxTree match {
3937-
case t: ValOrDefDef => t.rhs
3938-
case t => t
3939-
}
3940-
val cxTree2 = cxTree1 match {
3941-
case Typed(t, tpe) => t // ignore outer type annotation
3942-
case t => t
3943-
}
3944-
val (outer, explicitTargs) = cxTree2 match {
3945-
case TypeApply(fun, targs) => (fun, targs)
3946-
case Apply(TypeApply(fun, targs), args) => (Apply(fun, args), targs)
3947-
case Select(TypeApply(fun, targs), nme) => (Select(fun, nme), targs)
3948-
case t => (t, Nil)
3949-
}
3950-
def hasNamedArg(as: List[Tree]) = as.collectFirst{case AssignOrNamedArg(lhs, rhs) =>}.nonEmpty
3951-
3952-
def desugaredApply = tree match {
3953-
case Select(`qual`, nme.apply) => true
3954-
case _ => false
3936+
// If tp == NoType, pass only explicit type arguments to applyXXX. Not used at all
3937+
// here - it is for scala-virtualized, where tp will be passed as an argument (for
3938+
// selection on a staged Struct)
3939+
def hasNamed(args: List[Tree]): Boolean = args exists (_.isInstanceOf[AssignOrNamedArg])
3940+
// not supported: foo.bar(a1,..., an: _*)
3941+
def hasStar(args: List[Tree]) = treeInfo.isWildcardStarArgList(args)
3942+
def applyOp(args: List[Tree]) = if (hasNamed(args)) nme.applyDynamicNamed else nme.applyDynamic
3943+
def matches(t: Tree) = isDesugaredApply || treeInfo.methPart(t) == treeSelection
3944+
3945+
/** Note that the trees which arrive here are potentially some distance from
3946+
* the trees of direct interest. `cxTree` is some enclosing expression which
3947+
* may apparently be arbitrarily larger than `tree`; and `tree` itself is
3948+
* too small, having at least in some cases lost its explicit type parameters.
3949+
* This logic is designed to use `tree` to pinpoint the immediately surrounding
3950+
* Apply/TypeApply/Select node, and only then creates the dynamic call.
3951+
* See SI-6731 among others.
3952+
*/
3953+
def findSelection(t: Tree): Option[(TermName, Tree)] = t match {
3954+
case Apply(fn, args) if hasStar(args) => DynamicVarArgUnsupported(tree, applyOp(args)) ; None
3955+
case Apply(fn, args) if matches(fn) => Some((applyOp(args), fn))
3956+
case Assign(lhs, _) if matches(lhs) => Some((nme.updateDynamic, lhs))
3957+
case _ if matches(t) => Some((nme.selectDynamic, t))
3958+
case _ => t.children flatMap findSelection headOption
39553959
}
3956-
// note: context.tree includes at most one Apply node
3957-
// thus, we can't use it to detect we're going to receive named args in expressions such as:
3958-
// qual.sel(a)(a2, arg2 = "a2")
3959-
val oper = outer match {
3960-
case Apply(q, as) if q == tree || desugaredApply =>
3961-
val oper =
3962-
if (hasNamedArg(as)) nme.applyDynamicNamed
3963-
else nme.applyDynamic
3964-
// not supported: foo.bar(a1,..., an: _*)
3965-
if (treeInfo.isWildcardStarArgList(as)) {
3966-
DynamicVarArgUnsupported(tree, oper)
3967-
return Some(setError(tree))
3968-
} else oper
3969-
case Assign(`tree`, _) => nme.updateDynamic
3970-
case _ => nme.selectDynamic
3960+
findSelection(cxTree) match {
3961+
case Some((opName, tapply)) =>
3962+
val targs = treeInfo.typeArguments(tapply)
3963+
val fun = gen.mkTypeApply(Select(qual, opName), targs)
3964+
atPos(qual.pos)(Apply(fun, Literal(Constant(name.decode)) :: Nil))
3965+
case _ =>
3966+
setError(tree)
39713967
}
3972-
3973-
val dynSel = Select(qual, oper)
3974-
val tappSel = if (explicitTargs.nonEmpty) TypeApply(dynSel, explicitTargs) else dynSel
3975-
3976-
atPos(qual.pos)(Apply(tappSel, List(Literal(Constant(name.decode)))))
39773968
}
39783969
}
39793970

39803971
def wrapErrors(tree: Tree, typeTree: Typer => Tree): Tree = {
39813972
silent(typeTree) match {
39823973
case SilentResultValue(r) => r
39833974
case SilentTypeError(err) => DynamicRewriteError(tree, err)
3984-
}
3975+
}
39853976
}
39863977
}
39873978

@@ -5375,7 +5366,7 @@ trait Typers extends Modes with Adaptations with Tags {
53755366
case tt @ TypeTree() => tree setOriginal tt.original
53765367
case _ => tree
53775368
}
5378-
}
5369+
}
53795370
else
53805371
// we should get here only when something before failed
53815372
// and we try again (@see tryTypedApply). In that case we can assign

test/files/pos/t5726.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import scala.language.dynamics
2+
3+
class DynamicTest extends Dynamic {
4+
def selectDynamic(name: String) = s"value of $name"
5+
def updateDynamic(name: String)(value: Any) {
6+
println(s"You have just updated property '$name' with value: $value")
7+
}
8+
}
9+
10+
object MyApp extends App {
11+
def testing() {
12+
val test = new DynamicTest
13+
test.firstName = "John"
14+
}
15+
16+
testing()
17+
}

test/files/pos/t6551.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import language.dynamics
1+
import scala.language.dynamics
22

33
object Test {
44
def main(args: Array[String]) {

test/files/pos/t6722.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.language.dynamics
2+
3+
class Dyn extends Dynamic {
4+
def selectDynamic(s: String): Dyn = new Dyn
5+
def get[T]: T = null.asInstanceOf[T]
6+
}
7+
8+
object Foo {
9+
val dyn = new Dyn
10+
dyn.foo.bar.baz.get[String]
11+
}

test/files/run/t5733.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Running ABTest asserts
2+
Done

test/files/run/t5733.scala

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import scala.language.dynamics
2+
3+
object A extends Dynamic {
4+
var a = "a"
5+
6+
def selectDynamic(method:String): String = a
7+
8+
def updateDynamic(method:String)(v:String) { a = v }
9+
}
10+
11+
class B extends Dynamic {
12+
var b = "b"
13+
14+
def selectDynamic(method:String): String = b
15+
16+
def updateDynamic(method:String)(v:String) { b = v }
17+
}
18+
19+
object Test extends App {
20+
assert( A.foo == "a" )
21+
assert( A.bar == "a" )
22+
A.aaa = "aaa"
23+
assert( A.bar == "aaa" )
24+
25+
val b = new B
26+
assert( b.foo == "b" )
27+
assert( b.bar == "b" )
28+
b.bbb = "bbb"
29+
assert( b.bar == "bbb" )
30+
31+
{
32+
println("Running ABTest asserts")
33+
A.a = "a"
34+
(new ABTest).test()
35+
}
36+
37+
println("Done")
38+
}
39+
40+
class ABTest {
41+
def test() {
42+
assert( A.foo == "a" )
43+
assert( A.bar == "a" )
44+
A.aaa = "aaa"
45+
assert( A.bar == "aaa" )
46+
47+
val b = new B
48+
assert( b.foo == "b" )
49+
assert( b.bar == "b" )
50+
b.bbb = "bbb"
51+
assert( b.bar == "bbb" )
52+
}
53+
}

test/files/run/t6320.check

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Type in expressions to have them evaluated.
2+
Type :help for more information.
3+
4+
scala>
5+
6+
scala> import scala.language.dynamics
7+
import scala.language.dynamics
8+
9+
scala> class Dyn(m: Map[String, Any]) extends Dynamic { def selectDynamic[T](s: String): T = m(s).asInstanceOf[T] }
10+
defined class Dyn
11+
12+
scala> new Dyn(Map("foo" -> 10)).foo[Int]
13+
res0: Int = 10
14+
15+
scala>
16+
17+
scala>

test/files/run/t6320.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.tools.partest.ReplTest
2+
3+
object Test extends ReplTest {
4+
def code = """
5+
import scala.language.dynamics
6+
class Dyn(m: Map[String, Any]) extends Dynamic { def selectDynamic[T](s: String): T = m(s).asInstanceOf[T] }
7+
new Dyn(Map("foo" -> 10)).foo[Int]
8+
"""
9+
}

test/files/run/t6731.check

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Mono$.bar()
2+
Mono$.bar
3+
Mono$.bar()
4+
Mono$.bar
5+
Mono$.bar
6+
Mono$.baz
7+
Mono$.bar(bippy=1, boppy=2)
8+
Mono$.baz
9+
Poly.bar[Nothing]
10+
Poly.bar[Int]
11+
Poly.bar[Nothing]()
12+
Poly.bar[Int]()
13+
Poly.bar[Int](1, 2, 3)
14+
Poly.bar[Nothing]
15+
Poly.bar[Int]
16+
Poly.bar[Nothing]()
17+
Poly.bar[Int]()
18+
Poly.bar[Int](1, 2, 3)
19+
Updating.bar
20+
Updating.bar = b
21+
Nest1$Nest2$Nest3$.bippy(1, 2, 3)
22+
Nest1$Nest2$Nest3$.bippy
23+
Named.bippy(a=1, b=2)
24+
Named.boppy(c=3, d=4)
25+
Named.apply()
26+
Named.apply()
27+
Named.apply(e=5, f=6)
28+
Named2.bippy(1)(q0, c)
29+
Named2.bippy(1)(q0, c)
30+
Named2.bippy(1)(b, q0)
31+
Named2.bippy(1)(q0, c)
32+
Named2.bippy(1)(c, b)
33+
Named2.bippy(1)(b, c)
34+
Named2.bippy(1)(q0, c)
35+
Named2.bippy(2)(b, c)
36+
Named2.bippy(1)(q0, c)
37+
Named2.bippy(5)(b, c)
38+
Named2.dingus(100)(b, dong)
39+
Named2.bippy(1)(q0, q1)
40+
Named2.hello(100)(!!, !!)

0 commit comments

Comments
 (0)