Skip to content

Commit dfe79b6

Browse files
committed
[WIP] FIx #657: Add scala.Dynamic support.
* Supports the whole spec from scalac scala.Dynamic. * Lifted restriction on _* arguments for applyDynamic.
1 parent 5838fda commit dfe79b6

38 files changed

+573
-14
lines changed

src/dotty/tools/dotc/core/Mode.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,8 @@ object Mode {
8484
/** Use Scala2 scheme for overloading and implicit resolution */
8585
val OldOverloadingResolution = newMode(14, "OldOverloadingResolution")
8686

87+
/* Currently inside a potential scala.Dynamic rewrite. */
88+
val IgnoreNextDynamic = newMode(15, "IgnoreNextDynamic")
89+
8790
val PatternOrType = Pattern | Type
8891
}

src/dotty/tools/dotc/core/Types.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3016,6 +3016,11 @@ object Types {
30163016

30173017
object ErrorType extends ErrorType
30183018

3019+
/* Type used to track Select nodes that could not resolve a member and their qualifier is a scala.Dynamic. */
3020+
abstract class TryDynamicCallType extends UncachedGroundType with ValueType
3021+
3022+
object TryDynamicCallType extends TryDynamicCallType
3023+
30193024
/** Wildcard type, possibly with bounds */
30203025
abstract case class WildcardType(optBounds: Type) extends CachedGroundType with TermType {
30213026
def derivedWildcardType(optBounds: Type)(implicit ctx: Context) =

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@ object Applications {
8686

8787
import Applications._
8888

89-
trait Applications extends Compatibility { self: Typer =>
89+
trait Applications extends Compatibility { self: Typer with Dynamic =>
9090

9191
import Applications._
9292
import tpd.{ cpy => _, _ }
9393
import untpd.cpy
94+
import Dynamic.isDynamicMethod
9495

9596
/** @tparam Arg the type of arguments, could be tpd.Tree, untpd.Tree, or Type
9697
* @param methRef the reference to the method of the application
@@ -541,7 +542,25 @@ trait Applications extends Compatibility { self: Typer =>
541542

542543
def realApply(implicit ctx: Context): Tree = track("realApply") {
543544
var proto = new FunProto(tree.args, IgnoredProto(pt), this)(argCtx(tree))
544-
val fun1 = typedExpr(tree.fun, proto)
545+
546+
/* In the default case we type using Mode.IgnoreNextDynamic because fun1 might be a Select(foo, bar) where foo is of
547+
* type scala.Dynamic and bar is not one of its members. In which case the following transformation will be tried:
548+
* foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
549+
* Mode.IgnoreNextDynamic will ensure that Apply(Select(foo, bar), baz) is not transformed
550+
* into foo.selectDynamic(bar).apply(args).
551+
* We remove Mode.IgnoreNextDynamic if the name of the called method if is one of the scala.Dynamic methods or
552+
* if this node was generated by an assign (see typedAssign).
553+
* See: Dynamic.scala
554+
*/
555+
def generatedByTypedAssign(name: Name) =
556+
name == nme.update && ctx.mode.is(Mode.IgnoreNextDynamic)
557+
val ctx1 = tree.fun match {
558+
case Select(_, name) if isDynamicMethod(name) || generatedByTypedAssign(name) =>
559+
ctx.retractMode(Mode.IgnoreNextDynamic)
560+
case _ =>
561+
ctx.addMode(Mode.IgnoreNextDynamic)
562+
}
563+
val fun1 = typedExpr(tree.fun, proto)(ctx1)
545564

546565
// Warning: The following line is dirty and fragile. We record that auto-tupling was demanded as
547566
// a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application.
@@ -553,6 +572,13 @@ trait Applications extends Compatibility { self: Typer =>
553572

554573
fun1.tpe match {
555574
case ErrorType => tree.withType(ErrorType)
575+
case TryDynamicCallType =>
576+
tree match {
577+
case tree @ Apply(Select(qual, name), args) if !isDynamicMethod(name) =>
578+
typedDynamicApply(qual, name, args, pt)(tree)
579+
case _ =>
580+
handleUnexpectedFunType(tree, fun1)
581+
}
556582
case _ => methPart(fun1).tpe match {
557583
case funRef: TermRef =>
558584
tryEither { implicit ctx =>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package dotty.tools
2+
package dotc
3+
package typer
4+
5+
import dotty.tools.dotc.ast.Trees.NamedArg
6+
import dotty.tools.dotc.ast.tpd._
7+
import dotty.tools.dotc.ast.untpd
8+
import dotty.tools.dotc.core.Constants.Constant
9+
import dotty.tools.dotc.core.Contexts.Context
10+
import dotty.tools.dotc.core.Names.Name
11+
import dotty.tools.dotc.core.StdNames._
12+
import dotty.tools.dotc.core.Types._
13+
import dotty.tools.dotc.core.Mode
14+
import dotty.tools.dotc.core.Decorators._
15+
16+
object Dynamic {
17+
def isDynamicMethod(name: Name): Boolean =
18+
name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed
19+
}
20+
21+
/** Translates selection that does not typecheck according to the scala.Dynamic rules: *
22+
* foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
23+
* foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
24+
* foo.bar = baz ~~> foo.updateDynamic("bar")(baz)
25+
* foo.bar ~~> foo.selectDynamic(bar)
26+
* foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux)
27+
*/
28+
trait Dynamic { self: Applications =>
29+
import Dynamic._
30+
31+
def checkedDynamic(qual: Tree, originalTree: untpd.Select)(implicit ctx: Context): Option[Select] =
32+
if (qual.tpe != TryDynamicCallType) None
33+
else Some(originalTree.withType(TryDynamicCallType))
34+
35+
/* Returns the `select` unless it should be transformed into a `selectDynamic` */
36+
def typedSelectOrElseDynamicSelect(select: => Tree)(tree: untpd.Select, pt: Type, qual: Tree)(
37+
implicit ctx: Context): Tree = {
38+
if (select.tpe == TryDynamicCallType && !isDynamicMethod(tree.name) && !ctx.mode.is(Mode.IgnoreNextDynamic)) {
39+
typedDynamicSelect(untpd.cpy.Select(tree)(qual, tree.name), pt)
40+
} else select
41+
}
42+
43+
/** Translate selection that does not typecheck according to the normal rules into a applyDynamic/applyDynamicNamed.
44+
* foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
45+
* foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
46+
*/
47+
def typedDynamicApply(qual: untpd.Tree, name: Name, args: List[untpd.Tree], pt: Type)(original: untpd.Apply)(
48+
implicit ctx: Context): Tree = {
49+
def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false }
50+
val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic
51+
if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) {
52+
ctx.error("applyDynamicNamed does not support passing a vararg parameter", original.pos)
53+
original.withType(ErrorType)
54+
} else {
55+
def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg))
56+
def namedArgs = args.map {
57+
case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg)
58+
case arg => namedArgTuple("", arg)
59+
}
60+
val args1 = if (dynName == nme.applyDynamic) args else namedArgs
61+
typedApply(untpd.Apply(coreDynamic(qual, dynName, name), args1), pt)
62+
}
63+
}
64+
65+
/** Translate selection that does not typecheck according to the normal rules into a selectDynamic.
66+
* foo.bar ~~> foo.selectDynamic(bar)
67+
*/
68+
def typedDynamicSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree =
69+
typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name), pt)
70+
71+
/** Translate selection that does not typecheck according to the normal rules into a updateDynamic.
72+
* foo.bar = baz ~~> foo.updateDynamic(bar)(baz)
73+
*/
74+
def typedDynamicAssign(qual: untpd.Tree, name: Name, rhs: untpd.Tree, pt: Type)(implicit ctx: Context): Tree =
75+
typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name), rhs), pt)
76+
77+
private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name)(implicit ctx: Context): untpd.Apply =
78+
untpd.Apply(untpd.Select(qual, dynName), Literal(Constant(name.toString)))
79+
}

src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,16 @@ trait TypeAssigner {
196196
def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = {
197197
val mbr = site.member(name)
198198
if (reallyExists(mbr)) site.select(name, mbr)
199-
else {
199+
else if (site <:< defn.DynamicType && !Dynamic.isDynamicMethod(name)) {
200+
TryDynamicCallType
201+
} else {
200202
if (!site.isErroneous) {
201203
ctx.error(
202204
if (name == nme.CONSTRUCTOR) d"$site does not have a constructor"
203-
else d"$name is not a member of $site", pos)
205+
else if (site <:< defn.DynamicType) {
206+
d"$name is not a member of $site\n" +
207+
"possible cause: maybe a wrong Dynamic method signature?"
208+
} else d"$name is not a member of $site", pos)
204209
}
205210
ErrorType
206211
}

src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ object Typer {
5858
assert(tree.pos.exists, s"position not set for $tree # ${tree.uniqueId}")
5959
}
6060

61-
class Typer extends Namer with TypeAssigner with Applications with Implicits with Checking {
61+
class Typer extends Namer with TypeAssigner with Applications with Implicits with Dynamic with Checking {
6262

6363
import Typer._
6464
import tpd.{cpy => _, _}
6565
import untpd.cpy
66+
import Dynamic.isDynamicMethod
6667

6768
/** A temporary data item valid for a single typed ident:
6869
* The set of all root import symbols that have been
@@ -289,10 +290,14 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
289290
checkValue(tree1, pt)
290291
}
291292

292-
private def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(implicit ctx: Context): Select =
293-
healNonvariant(
294-
checkValue(assignType(cpy.Select(tree)(qual, tree.name), qual), pt),
295-
pt)
293+
private def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(implicit ctx: Context): Select = {
294+
checkedDynamic(qual, tree).getOrElse {
295+
val select = assignType(cpy.Select(tree)(qual, tree.name), qual)
296+
checkedDynamic(select, tree).getOrElse {
297+
healNonvariant(checkValue(select, pt), pt)
298+
}
299+
}
300+
}
296301

297302
/** Let `tree = p.n` where `p: T`. If tree's type is an unsafe instantiation
298303
* (see TypeOps#asSeenFrom for how this can happen), rewrite the prefix `p`
@@ -315,8 +320,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
315320
def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") {
316321
def asSelect(implicit ctx: Context): Tree = {
317322
val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))
318-
if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos)
319-
typedSelect(tree, pt, qual1)
323+
checkedDynamic(qual1, tree).getOrElse {
324+
if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos)
325+
typedSelectOrElseDynamicSelect(typedSelect(tree, pt, qual1))(tree, pt, qual1)
326+
}
320327
}
321328

322329
def asJavaSelectFromTypeTree(implicit ctx: Context): Tree = {
@@ -467,7 +474,14 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
467474
def typedAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context) = track("typedAssign") {
468475
tree.lhs match {
469476
case lhs @ Apply(fn, args) =>
470-
typed(cpy.Apply(lhs)(untpd.Select(fn, nme.update), args :+ tree.rhs), pt)
477+
/* We type using Mode.IgnoreNextDynamic because fn might be a Select(foo, bar) where foo is of type
478+
* scala.Dynamic and bar is not one of its members. In which case the following transformation will be tried:
479+
* foo.bar(baz) = quux ~~> foo.selectDynamic("bar").update(baz, quux)
480+
* Mode.IgnoreNextDynamic will ensure that Apply(Select(fn, update), args) is not transformed
481+
* into fn.applyDynamic(update)(args) and will only transform the contents of fn if needed.
482+
* See: Dynamic.scala
483+
*/
484+
typed(cpy.Apply(lhs)(untpd.Select(fn, nme.update), args :+ tree.rhs), pt)(ctx.addMode(Mode.IgnoreNextDynamic))
471485
case untpd.TypedSplice(Apply(MaybePoly(Select(fn, app), targs), args)) if app == nme.apply =>
472486
val rawUpdate: untpd.Tree = untpd.Select(untpd.TypedSplice(fn), nme.update)
473487
val wrappedUpdate =
@@ -476,7 +490,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
476490
val appliedUpdate = cpy.Apply(fn)(wrappedUpdate, (args map untpd.TypedSplice) :+ tree.rhs)
477491
typed(appliedUpdate, pt)
478492
case lhs =>
479-
val lhsCore = typedUnadapted(lhs)
493+
/* We type using Mode.IgnoreNextDynamic because this tree might be transformed with:
494+
* foo.bar = baz ~~> foo.updateDynamic("bar")(baz)
495+
* Mode.IgnoreNextDynamic will ensure that Assign(Select(foo, bar), baz) will not be transformed
496+
* into foo.selectDynamic(bar) = baz during the typing of the lhs.
497+
* See: Dynamic.scala
498+
*/
499+
val lhsCore = typedUnadapted(lhs)(ctx.addMode(Mode.IgnoreNextDynamic))
480500
def lhs1 = typed(untpd.TypedSplice(lhsCore))
481501
def canAssign(sym: Symbol) = // allow assignments from the primary constructor to class fields
482502
sym.is(Mutable, butNot = Accessor) ||
@@ -504,6 +524,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
504524
case _ =>
505525
reassignmentToVal
506526
}
527+
case TryDynamicCallType =>
528+
tree match {
529+
case Assign(Select(qual, name), rhs) if !isDynamicMethod(name) =>
530+
typedDynamicAssign(qual, name, rhs, pt)
531+
case _ => reassignmentToVal
532+
}
507533
case tpe =>
508534
reassignmentToVal
509535
}
@@ -1661,7 +1687,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
16611687
tree match {
16621688
case _: MemberDef | _: PackageDef | _: Import | _: WithoutTypeOrPos[_] => tree
16631689
case _ => tree.tpe.widen match {
1664-
case ErrorType =>
1690+
case ErrorType | TryDynamicCallType =>
16651691
tree
16661692
case ref: TermRef =>
16671693
pt match {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic
4+
5+
object DynamicTest {
6+
new Foo().bazApply()
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic
4+
5+
object DynamicTest {
6+
new Foo().bazApply("abc", 1)
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic
4+
5+
object DynamicTest {
6+
new Foo().bazApply _
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def selectDynamic(name: String): String = ???
5+
def applyDynamicNamed(name: String)(args: Any*): String = ???
6+
}
7+
8+
object DynamicTest {
9+
new Foo().bazApply()
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def applyDynamic(name: String)(args: String*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
new Foo().bazApply(1, 2, 3)
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def applyDynamic(name: String)(args: String*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
def test: Int = new Foo().bazApply()
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def applyDynamic(name: Int)(args: String*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
def test: String = new Foo().bazApply()
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def applyDynamic(name: String)(args: Any*): String = ???
5+
def applyDynamicNamed(name: String)(args: (Sting, Int)*): String = ???
6+
}
7+
8+
object DynamicTest {
9+
def test: String = new Foo().bazApply("1" -> 2)
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def applyDynamicNamed(name: String)(args: (Sting, Int)*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
new Foo().applyDynamic("bar")("1" -> 2)
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic
4+
5+
object DynamicTest {
6+
new Foo().bazApply(a = "abc", b = 1)
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def selectDynamic(name: String): Array[String] = ???
5+
def applyDynamic(name: String)(args: Any*): String = ???
6+
}
7+
8+
object DynamicTest {
9+
new Foo().bazApply(a = "abc", b = 1)
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic
4+
5+
object DynamicTest {
6+
new Foo().bazApply("abc", b = 1)
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic
4+
5+
object DynamicTest {
6+
new Foo().bazApply("abc", 4, b = 1, b = "bcd")
7+
}

0 commit comments

Comments
 (0)