Skip to content

Commit 2c88ba8

Browse files
committed
Fix #657: Add scala.Dynamic support.
1 parent b35eff9 commit 2c88ba8

38 files changed

+577
-7
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3021,6 +3021,9 @@ object Types {
30213021

30223022
object ErrorType extends ErrorType
30233023

3024+
/* Type used to track Select nodes that could not resolve a member and their qualifier is a scala.Dynamic. */
3025+
object TryDynamicCallType extends ErrorType
3026+
30243027
/** Wildcard type, possibly with bounds */
30253028
abstract case class WildcardType(optBounds: Type) extends CachedGroundType with TermType {
30263029
def derivedWildcardType(optBounds: Type)(implicit ctx: Context) =

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

Lines changed: 9 additions & 1 deletion
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
@@ -553,6 +554,13 @@ trait Applications extends Compatibility { self: Typer =>
553554

554555
fun1.tpe match {
555556
case ErrorType => tree.withType(ErrorType)
557+
case TryDynamicCallType =>
558+
tree match {
559+
case tree @ Apply(Select(qual, name), args) if !isDynamicMethod(name) =>
560+
typedDynamicApply(qual, name, args, pt)(tree)
561+
case _ =>
562+
handleUnexpectedFunType(tree, fun1)
563+
}
556564
case _ => methPart(fun1).tpe match {
557565
case funRef: TermRef =>
558566
tryEither { implicit ctx =>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux)
23+
* foo.bar = baz ~~> foo.updateDynamic("bar")(baz)
24+
* foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
25+
* foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
26+
* foo.bar ~~> foo.selectDynamic(bar)
27+
*
28+
* The first matching rule of is applied.
29+
*/
30+
trait Dynamic { self: Typer with Applications =>
31+
32+
/** Translate selection that does not typecheck according to the normal rules into a applyDynamic/applyDynamicNamed.
33+
* foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
34+
* foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
35+
*/
36+
def typedDynamicApply(qual: untpd.Tree, name: Name, args: List[untpd.Tree], pt: Type)(original: untpd.Apply)(
37+
implicit ctx: Context): Tree = {
38+
def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false }
39+
val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic
40+
if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) {
41+
ctx.error("applyDynamicNamed does not support passing a vararg parameter", original.pos)
42+
original.withType(ErrorType)
43+
} else {
44+
def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg))
45+
def namedArgs = args.map {
46+
case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg)
47+
case arg => namedArgTuple("", arg)
48+
}
49+
val args1 = if (dynName == nme.applyDynamic) args else namedArgs
50+
typedApply(untpd.Apply(coreDynamic(qual, dynName, name), args1), pt)
51+
}
52+
}
53+
54+
/** Translate selection that does not typecheck according to the normal rules into a selectDynamic.
55+
* foo.bar ~~> foo.selectDynamic(bar)
56+
*
57+
* Note: inner part of translation foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) is achieved
58+
* through an existing transformation of in typedAssign [foo.bar(baz) = quux ~~> foo.bar.update(baz, quux)].
59+
*/
60+
def typedDynamicSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree =
61+
typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name), pt)
62+
63+
/** Translate selection that does not typecheck according to the normal rules into a updateDynamic.
64+
* foo.bar = baz ~~> foo.updateDynamic(bar)(baz)
65+
*/
66+
def typedDynamicAssign(qual: untpd.Tree, name: Name, rhs: untpd.Tree, pt: Type)(implicit ctx: Context): Tree =
67+
typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name), rhs), pt)
68+
69+
private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name)(implicit ctx: Context): untpd.Apply =
70+
untpd.Apply(untpd.Select(qual, dynName), Literal(Constant(name.toString)))
71+
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,31 @@ object ProtoTypes {
430430
(if (theMap != null) theMap else new WildApproxMap).mapOver(tp)
431431
}
432432

433+
trait AssigningProto extends ProtoType
434+
435+
/** A prototype for expressions that appear in the lhs position of an assign
436+
*
437+
* lhs = (rhs: resType)
438+
*/
439+
case class AssignProto(lhs: untpd.Tree, rhsType: Type, typer: Typer)(implicit ctx: Context)
440+
extends UncachedGroundType with AssigningProto {
441+
override def resultType(implicit ctx: Context) = rhsType
442+
443+
def isMatchedBy(tp: Type)(implicit ctx: Context) = tp <:< rhsType
444+
445+
def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = ta(x, rhsType)
446+
447+
def map(tm: TypeMap)(implicit ctx: Context): ProtoType = derivedAssignProto(lhs, tm(rhsType), typer)
448+
449+
def derivedAssignProto(lhs: untpd.Tree, rhsType: Type, typer: Typer) =
450+
if ((lhs eq this.lhs) && (rhsType eq this.resultType) && (typer eq this.typer)) this
451+
else new AssignProto(lhs, rhsType, typer)
452+
453+
override def toString = s"AssignProto($lhs = (rhs: $resultType))"
454+
455+
override def deepenProto(implicit ctx: Context) = derivedAssignProto(lhs, resultType.deepenProto, typer)
456+
}
457+
433458
private[ProtoTypes] class WildApproxMap(implicit ctx: Context) extends TypeMap {
434459
def apply(tp: Type) = wildApprox(tp, this)
435460
}

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: 20 additions & 4 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
@@ -316,7 +317,15 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
316317
def asSelect(implicit ctx: Context): Tree = {
317318
val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))
318319
if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos)
319-
typedSelect(tree, pt, qual1)
320+
val select = typedSelect(tree, pt, qual1)
321+
if (select.tpe != TryDynamicCallType) {
322+
select
323+
} else {
324+
pt match {
325+
case _: FunProto | _: AssignProto => select
326+
case _ => typedDynamicSelect(tree, pt)
327+
}
328+
}
320329
}
321330

322331
def asJavaSelectFromTypeTree(implicit ctx: Context): Tree = {
@@ -480,7 +489,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
480489
val appliedUpdate = cpy.Apply(fn)(wrappedUpdate, (args map untpd.TypedSplice) :+ tree.rhs)
481490
typed(appliedUpdate, pt)
482491
case lhs =>
483-
val lhsCore = typedUnadapted(lhs)
492+
val proto = new AssignProto(tree.lhs, pt, this)(argCtx(tree))
493+
val lhsCore = typedUnadapted(lhs, proto)
484494
def lhs1 = typed(untpd.TypedSplice(lhsCore))
485495
def canAssign(sym: Symbol) = // allow assignments from the primary constructor to class fields
486496
sym.is(Mutable, butNot = Accessor) ||
@@ -508,6 +518,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
508518
case _ =>
509519
reassignmentToVal
510520
}
521+
case TryDynamicCallType =>
522+
tree match {
523+
case Assign(Select(qual, name), rhs) if !isDynamicMethod(name) =>
524+
typedDynamicAssign(qual, name, rhs, pt)
525+
case _ => reassignmentToVal
526+
}
511527
case tpe =>
512528
reassignmentToVal
513529
}
@@ -1665,7 +1681,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
16651681
tree match {
16661682
case _: MemberDef | _: PackageDef | _: Import | _: WithoutTypeOrPos[_] => tree
16671683
case _ => tree.tpe.widen match {
1668-
case ErrorType =>
1684+
case _: ErrorType =>
16691685
tree
16701686
case ref: TermRef =>
16711687
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() // error
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) // error
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 _ // error // error
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() // error
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) // error // error // error
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() // error
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() // error
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 applyDynamicNamed(name: String)(args: (String, Int)*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
def test: String = new Foo().bazApply("1" -> 2) // error
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 applyDynamicNamed(name: String)(args: (String, Int)*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
new Foo().applyDynamic("bar")("1" -> 2) // error
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) // error
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) // error
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) // error
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") // error
7+
}
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: String*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
new Foo().bazApply("abc", 4, b = 1, b = "bcd") // error // error // error // error
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 applyDynamicNamed(name: Int)(args: Any*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
new Foo().bazApply("abc", 4, b = 1, b = "bcd") // error
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 applyDynamicNamed(name: String)(args: Any*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
def test: Int = new Foo().bazApply("abc", 4, b = 1, b = "bcd") // error
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: Any*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
new Foo().applyDynamicNamed("abc")() // error
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def selectDynamic(name: String): String = ???
5+
}
6+
7+
object DynamicTest {
8+
implicit class Bar(foo: Foo) {
9+
def bazSelect: Int = ???
10+
}
11+
12+
def baz: String = new Foo().bazSelect // error
13+
}

0 commit comments

Comments
 (0)