Skip to content

Commit e4f7c47

Browse files
committed
WIP: Add scala.Dynamic support.
1 parent 5838fda commit e4f7c47

40 files changed

+502
-12
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+
/* We are currently inside a potential scala.Dynamic rewrite */
88+
val IgnoreNextDynamic = newMode(15, "IgnoreSelectDynamic")
89+
8790
val PatternOrType = Pattern | Type
8891
}

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

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

30173017
object ErrorType extends ErrorType
30183018

3019+
abstract class TryDynamicCallType extends UncachedGroundType with ValueType
3020+
3021+
object TryDynamicCallType extends TryDynamicCallType
3022+
30193023
/** Wildcard type, possibly with bounds */
30203024
abstract case class WildcardType(optBounds: Type) extends CachedGroundType with TermType {
30213025
def derivedWildcardType(optBounds: Type)(implicit ctx: Context) =

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,14 @@ trait Applications extends Compatibility { self: Typer =>
541541

542542
def realApply(implicit ctx: Context): Tree = track("realApply") {
543543
var proto = new FunProto(tree.args, IgnoredProto(pt), this)(argCtx(tree))
544-
val fun1 = typedExpr(tree.fun, proto)
544+
545+
val ctx1 = tree.fun match {
546+
case Select(_, name) if isDynamicMethod(name) ||
547+
(name == nme.update && ctx.mode.is(Mode.IgnoreNextDynamic)) =>
548+
ctx.retractMode(Mode.IgnoreNextDynamic)
549+
case _ => ctx.addMode(Mode.IgnoreNextDynamic)
550+
}
551+
val fun1 = typedExpr(tree.fun, proto)(ctx1)
545552

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

554561
fun1.tpe match {
555562
case ErrorType => tree.withType(ErrorType)
563+
case TryDynamicCallType =>
564+
tree match {
565+
case Apply(Select(qual, name), args) if !isDynamicMethod(name) =>
566+
if (untpd.isWildcardStarArgList(args)) {
567+
ctx.error(name.toString + " does not support passing a vararg parameter", tree.pos)
568+
tree.withType(ErrorType)
569+
} else {
570+
typedDynamicApply(qual, name, args, pt)
571+
}
572+
case _ =>
573+
handleUnexpectedFunType(tree, fun1)
574+
}
556575
case _ => methPart(fun1).tpe match {
557576
case funRef: TermRef =>
558577
tryEither { implicit ctx =>

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,20 @@ trait TypeAssigner {
194194
/** The type of a selection with `name` of a tree with type `site`.
195195
*/
196196
def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = {
197+
def isNotDynamicMethodName =
198+
name != nme.applyDynamic && name != nme.applyDynamicNamed && name != nme.selectDynamic && name != nme.updateDynamic
197199
val mbr = site.member(name)
198200
if (reallyExists(mbr)) site.select(name, mbr)
199-
else {
201+
else if (site <:< defn.DynamicType && isNotDynamicMethodName) {
202+
TryDynamicCallType
203+
} else {
200204
if (!site.isErroneous) {
201205
ctx.error(
202206
if (name == nme.CONSTRUCTOR) d"$site does not have a constructor"
203-
else d"$name is not a member of $site", pos)
207+
else if (site <:< defn.DynamicType) {
208+
d"$name is not a member of $site\n" +
209+
"possible cause: maybe a wrong Dynamic method signature?"
210+
} else d"$name is not a member of $site", pos)
204211
}
205212
ErrorType
206213
}

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

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,15 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
289289
checkValue(tree1, pt)
290290
}
291291

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)
292+
private def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(implicit ctx: Context): Select = {
293+
if (qual.tpe == TryDynamicCallType) {
294+
tree.withType(TryDynamicCallType)
295+
} else {
296+
val select = assignType(cpy.Select(tree)(qual, tree.name), qual)
297+
if (select.tpe == TryDynamicCallType) tree.withType(TryDynamicCallType)
298+
else healNonvariant(checkValue(select, pt), pt)
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,15 @@ 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+
if (qual1.tpe == TryDynamicCallType) {
324+
tree.withType(TryDynamicCallType)
325+
} else {
326+
if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos)
327+
val select = typedSelect(tree, pt, qual1)
328+
if (select.tpe == TryDynamicCallType && !isDynamicMethod(tree.name) && !ctx.mode.is(Mode.IgnoreNextDynamic)) {
329+
typedDynamicSelect(cpy.Select(tree)(qual1, tree.name), pt)
330+
} else select
331+
}
320332
}
321333

322334
def asJavaSelectFromTypeTree(implicit ctx: Context): Tree = {
@@ -467,7 +479,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
467479
def typedAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context) = track("typedAssign") {
468480
tree.lhs match {
469481
case lhs @ Apply(fn, args) =>
470-
typed(cpy.Apply(lhs)(untpd.Select(fn, nme.update), args :+ tree.rhs), pt)
482+
typed(cpy.Apply(lhs)(untpd.Select(fn, nme.update), args :+ tree.rhs), pt)(ctx.addMode(Mode.IgnoreNextDynamic))
471483
case untpd.TypedSplice(Apply(MaybePoly(Select(fn, app), targs), args)) if app == nme.apply =>
472484
val rawUpdate: untpd.Tree = untpd.Select(untpd.TypedSplice(fn), nme.update)
473485
val wrappedUpdate =
@@ -476,7 +488,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
476488
val appliedUpdate = cpy.Apply(fn)(wrappedUpdate, (args map untpd.TypedSplice) :+ tree.rhs)
477489
typed(appliedUpdate, pt)
478490
case lhs =>
479-
val lhsCore = typedUnadapted(lhs)
491+
val lhsCore = typedUnadapted(lhs)(ctx.addMode(Mode.IgnoreNextDynamic))
480492
def lhs1 = typed(untpd.TypedSplice(lhsCore))
481493
def canAssign(sym: Symbol) = // allow assignments from the primary constructor to class fields
482494
sym.is(Mutable, butNot = Accessor) ||
@@ -504,6 +516,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
504516
case _ =>
505517
reassignmentToVal
506518
}
519+
case TryDynamicCallType =>
520+
tree match {
521+
case Assign(Select(qual, name), rhs) if !isDynamicMethod(name) =>
522+
typedDynamicAssign(qual, name, rhs, pt)
523+
case _ => reassignmentToVal
524+
}
507525
case tpe =>
508526
reassignmentToVal
509527
}
@@ -1661,7 +1679,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
16611679
tree match {
16621680
case _: MemberDef | _: PackageDef | _: Import | _: WithoutTypeOrPos[_] => tree
16631681
case _ => tree.tpe.widen match {
1664-
case ErrorType =>
1682+
case ErrorType | TryDynamicCallType =>
16651683
tree
16661684
case ref: TermRef =>
16671685
pt match {
@@ -1699,4 +1717,38 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
16991717
}
17001718
}
17011719
}
1720+
1721+
/** Translate selection that does not typecheck according to the normal rules into a applyDynamic/applyDynamicNamed.
1722+
* foo.method("blah") ~~> foo.applyDynamic("method")("blah")
1723+
* foo.method(x = "blah") ~~> foo.applyDynamicNamed("method")(("x", "blah"))
1724+
*/
1725+
def typedDynamicApply(qual: untpd.Tree, name: Name, args: List[untpd.Tree], pt: Type)(implicit ctx: Context): Tree = {
1726+
def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true ; case _ => false }
1727+
def namedArgTupple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg))
1728+
def namedArgs = args.map {
1729+
case NamedArg(argName, arg) => namedArgTupple(argName.toString, arg)
1730+
case arg => namedArgTupple("", arg)
1731+
}
1732+
val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic
1733+
val args1 = if (dynName == nme.applyDynamic) args else namedArgs
1734+
typedApply(untpd.Apply(coreDynamic(qual, dynName, name), args1), pt)
1735+
}
1736+
1737+
/** Translate selection that does not typecheck according to the normal rules into a selectDynamic.
1738+
* foo.field ~~> foo.selectDynamic("field")
1739+
*/
1740+
def typedDynamicSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree =
1741+
typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name), pt)
1742+
1743+
/** Translate selection that does not typecheck according to the normal rules into a updateDynamic.
1744+
* foo.varia = 10 ~~> foo.updateDynamic("varia")(10)
1745+
*/
1746+
def typedDynamicAssign(qual: untpd.Tree, name: Name, rhs: untpd.Tree, pt: Type)(implicit ctx: Context): Tree =
1747+
typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name), rhs), pt)
1748+
1749+
def isDynamicMethod(name: Name): Boolean =
1750+
name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed
1751+
1752+
private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name)(implicit ctx: Context): untpd.Apply =
1753+
untpd.Apply(untpd.Select(qual, dynName), Literal(Constant(name.toString)))
17021754
}
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().bazApply(List(4): _*)
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().bazApply(1, List(4): _*)
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+
new Foo().bazApply(a = 1, List(4): _*)
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()
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+
}
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")
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")
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")
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")()
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
13+
}

0 commit comments

Comments
 (0)