diff options
author | Nicolas Stucki <nicolas.stucki@gmail.com> | 2016-05-30 09:20:21 +0200 |
---|---|---|
committer | Nicolas Stucki <nicolas.stucki@gmail.com> | 2016-07-07 11:10:44 +0200 |
commit | 75da0358fd7866f3dccdfcf4fbeae9af8ccc69f3 (patch) | |
tree | 142087645d00858f20eaead2f805afb333d2bddc | |
parent | 07fd8a357ed660ef15163efb2788928fec290fdd (diff) | |
download | dotty-75da0358fd7866f3dccdfcf4fbeae9af8ccc69f3.tar.gz dotty-75da0358fd7866f3dccdfcf4fbeae9af8ccc69f3.tar.bz2 dotty-75da0358fd7866f3dccdfcf4fbeae9af8ccc69f3.zip |
Fix #657: Add scala.Dynamic support.
38 files changed, 551 insertions, 7 deletions
diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index f514a329e..8a4bf727b 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -3021,6 +3021,9 @@ object Types { object ErrorType extends ErrorType + /* Type used to track Select nodes that could not resolve a member and their qualifier is a scala.Dynamic. */ + object TryDynamicCallType extends ErrorType + /** Wildcard type, possibly with bounds */ abstract case class WildcardType(optBounds: Type) extends CachedGroundType with TermType { def derivedWildcardType(optBounds: Type)(implicit ctx: Context) = diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index a9184c7e5..bab85f2a4 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -86,11 +86,12 @@ object Applications { import Applications._ -trait Applications extends Compatibility { self: Typer => +trait Applications extends Compatibility { self: Typer with Dynamic => import Applications._ import tpd.{ cpy => _, _ } import untpd.cpy + import Dynamic.isDynamicMethod /** @tparam Arg the type of arguments, could be tpd.Tree, untpd.Tree, or Type * @param methRef the reference to the method of the application @@ -553,6 +554,13 @@ trait Applications extends Compatibility { self: Typer => fun1.tpe match { case ErrorType => tree.withType(ErrorType) + case TryDynamicCallType => + tree match { + case tree @ Apply(Select(qual, name), args) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, args, pt)(tree) + case _ => + handleUnexpectedFunType(tree, fun1) + } case _ => methPart(fun1).tpe match { case funRef: TermRef => tryEither { implicit ctx => diff --git a/src/dotty/tools/dotc/typer/Dynamic.scala b/src/dotty/tools/dotc/typer/Dynamic.scala new file mode 100644 index 000000000..aeb3cca8c --- /dev/null +++ b/src/dotty/tools/dotc/typer/Dynamic.scala @@ -0,0 +1,71 @@ +package dotty.tools +package dotc +package typer + +import dotty.tools.dotc.ast.Trees.NamedArg +import dotty.tools.dotc.ast.tpd._ +import dotty.tools.dotc.ast.untpd +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Mode +import dotty.tools.dotc.core.Decorators._ + +object Dynamic { + def isDynamicMethod(name: Name): Boolean = + name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed +} + +/** Translates selection that does not typecheck according to the scala.Dynamic rules: + * foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) + * foo.bar = baz ~~> foo.updateDynamic("bar")(baz) + * foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...) + * foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...) + * foo.bar ~~> foo.selectDynamic(bar) + * + * The first matching rule of is applied. + */ +trait Dynamic { self: Typer with Applications => + + /** Translate selection that does not typecheck according to the normal rules into a applyDynamic/applyDynamicNamed. + * foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...) + * foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...) + */ + def typedDynamicApply(qual: untpd.Tree, name: Name, args: List[untpd.Tree], pt: Type)(original: untpd.Apply)( + implicit ctx: Context): Tree = { + def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false } + val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic + if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) { + ctx.error("applyDynamicNamed does not support passing a vararg parameter", original.pos) + original.withType(ErrorType) + } else { + def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg)) + def namedArgs = args.map { + case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg) + case arg => namedArgTuple("", arg) + } + val args1 = if (dynName == nme.applyDynamic) args else namedArgs + typedApply(untpd.Apply(coreDynamic(qual, dynName, name), args1), pt) + } + } + + /** Translate selection that does not typecheck according to the normal rules into a selectDynamic. + * foo.bar ~~> foo.selectDynamic(bar) + * + * Note: inner part of translation foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) is achieved + * through an existing transformation of in typedAssign [foo.bar(baz) = quux ~~> foo.bar.update(baz, quux)]. + */ + def typedDynamicSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = + typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name), pt) + + /** Translate selection that does not typecheck according to the normal rules into a updateDynamic. + * foo.bar = baz ~~> foo.updateDynamic(bar)(baz) + */ + def typedDynamicAssign(qual: untpd.Tree, name: Name, rhs: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = + typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name), rhs), pt) + + private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name)(implicit ctx: Context): untpd.Apply = + untpd.Apply(untpd.Select(qual, dynName), Literal(Constant(name.toString))) +} diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 740258821..622e3b9d8 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -430,6 +430,8 @@ object ProtoTypes { (if (theMap != null) theMap else new WildApproxMap).mapOver(tp) } + @sharable object AssignProto extends UncachedGroundType with MatchAlways + private[ProtoTypes] class WildApproxMap(implicit ctx: Context) extends TypeMap { def apply(tp: Type) = wildApprox(tp, this) } diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 995fa43ca..5e65df75e 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -196,11 +196,16 @@ trait TypeAssigner { def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = { val mbr = site.member(name) if (reallyExists(mbr)) site.select(name, mbr) - else { + else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { + TryDynamicCallType + } else { if (!site.isErroneous) { ctx.error( if (name == nme.CONSTRUCTOR) d"$site does not have a constructor" - else d"$name is not a member of $site", pos) + else if (site.derivesFrom(defn.DynamicClass)) { + d"$name is not a member of $site\n" + + "possible cause: maybe a wrong Dynamic method signature?" + } else d"$name is not a member of $site", pos) } ErrorType } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 268020ec5..019c460e8 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -58,11 +58,12 @@ object Typer { assert(tree.pos.exists, s"position not set for $tree # ${tree.uniqueId}") } -class Typer extends Namer with TypeAssigner with Applications with Implicits with Checking { +class Typer extends Namer with TypeAssigner with Applications with Implicits with Dynamic with Checking { import Typer._ import tpd.{cpy => _, _} import untpd.cpy + import Dynamic.isDynamicMethod /** A temporary data item valid for a single typed ident: * The set of all root import symbols that have been @@ -316,7 +317,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def asSelect(implicit ctx: Context): Tree = { val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) - typedSelect(tree, pt, qual1) + val select = typedSelect(tree, pt, qual1) + pt match { + case _: FunProto | AssignProto => select + case _ => + if (select.tpe eq TryDynamicCallType) typedDynamicSelect(tree, pt) + else select + } } def asJavaSelectFromTypeTree(implicit ctx: Context): Tree = { @@ -480,7 +487,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val appliedUpdate = cpy.Apply(fn)(wrappedUpdate, (args map untpd.TypedSplice) :+ tree.rhs) typed(appliedUpdate, pt) case lhs => - val lhsCore = typedUnadapted(lhs) + val lhsCore = typedUnadapted(lhs, AssignProto) def lhs1 = typed(untpd.TypedSplice(lhsCore)) def canAssign(sym: Symbol) = // allow assignments from the primary constructor to class fields sym.is(Mutable, butNot = Accessor) || @@ -508,6 +515,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case _ => reassignmentToVal } + case TryDynamicCallType => + tree match { + case Assign(Select(qual, name), rhs) if !isDynamicMethod(name) => + typedDynamicAssign(qual, name, rhs, pt) + case _ => reassignmentToVal + } case tpe => reassignmentToVal } @@ -1665,7 +1678,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree match { case _: MemberDef | _: PackageDef | _: Import | _: WithoutTypeOrPos[_] => tree case _ => tree.tpe.widen match { - case ErrorType => + case _: ErrorType => tree case ref: TermRef => pt match { diff --git a/tests/neg/dynamicApplyDynamicTest1.scala b/tests/neg/dynamicApplyDynamicTest1.scala new file mode 100644 index 000000000..90f4493eb --- /dev/null +++ b/tests/neg/dynamicApplyDynamicTest1.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + new Foo().bazApply() // error +} diff --git a/tests/neg/dynamicApplyDynamicTest2.scala b/tests/neg/dynamicApplyDynamicTest2.scala new file mode 100644 index 000000000..aa0e44405 --- /dev/null +++ b/tests/neg/dynamicApplyDynamicTest2.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + new Foo().bazApply("abc", 1) // error +} diff --git a/tests/neg/dynamicApplyDynamicTest3.scala b/tests/neg/dynamicApplyDynamicTest3.scala new file mode 100644 index 000000000..61d3c9677 --- /dev/null +++ b/tests/neg/dynamicApplyDynamicTest3.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + new Foo().bazApply _ // error // error +} diff --git a/tests/neg/dynamicApplyDynamicTest4.scala b/tests/neg/dynamicApplyDynamicTest4.scala new file mode 100644 index 000000000..4573bd08d --- /dev/null +++ b/tests/neg/dynamicApplyDynamicTest4.scala @@ -0,0 +1,10 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def selectDynamic(name: String): String = ??? + def applyDynamicNamed(name: String)(args: Any*): String = ??? +} + +object DynamicTest { + new Foo().bazApply() // error +} diff --git a/tests/neg/dynamicApplyDynamicTest5.scala b/tests/neg/dynamicApplyDynamicTest5.scala new file mode 100644 index 000000000..d1d9cdd69 --- /dev/null +++ b/tests/neg/dynamicApplyDynamicTest5.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamic(name: String)(args: String*): String = ??? +} + +object DynamicTest { + new Foo().bazApply(1, 2, 3) // error // error // error +} diff --git a/tests/neg/dynamicApplyDynamicTest6.scala b/tests/neg/dynamicApplyDynamicTest6.scala new file mode 100644 index 000000000..865858ff3 --- /dev/null +++ b/tests/neg/dynamicApplyDynamicTest6.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamic(name: String)(args: String*): String = ??? +} + +object DynamicTest { + def test: Int = new Foo().bazApply() // error +} diff --git a/tests/neg/dynamicApplyDynamicTest7.scala b/tests/neg/dynamicApplyDynamicTest7.scala new file mode 100644 index 000000000..7985dd09e --- /dev/null +++ b/tests/neg/dynamicApplyDynamicTest7.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamic(name: Int)(args: String*): String = ??? +} + +object DynamicTest { + def test: String = new Foo().bazApply() // error +} diff --git a/tests/neg/dynamicApplyDynamicTest8.scala b/tests/neg/dynamicApplyDynamicTest8.scala new file mode 100644 index 000000000..ee8192d76 --- /dev/null +++ b/tests/neg/dynamicApplyDynamicTest8.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamicNamed(name: String)(args: (String, Int)*): String = ??? +} + +object DynamicTest { + def test: String = new Foo().bazApply("1" -> 2) // error +} diff --git a/tests/neg/dynamicApplyDynamicTest9.scala b/tests/neg/dynamicApplyDynamicTest9.scala new file mode 100644 index 000000000..bc4f94d5e --- /dev/null +++ b/tests/neg/dynamicApplyDynamicTest9.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamicNamed(name: String)(args: (String, Int)*): String = ??? +} + +object DynamicTest { + new Foo().applyDynamic("bar")("1" -> 2) // error +} diff --git a/tests/neg/dynamicApplyNamedDynamicTest1.scala b/tests/neg/dynamicApplyNamedDynamicTest1.scala new file mode 100644 index 000000000..a51cd2c7b --- /dev/null +++ b/tests/neg/dynamicApplyNamedDynamicTest1.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + new Foo().bazApply(a = "abc", b = 1) // error +} diff --git a/tests/neg/dynamicApplyNamedDynamicTest2.scala b/tests/neg/dynamicApplyNamedDynamicTest2.scala new file mode 100644 index 000000000..ca801e77c --- /dev/null +++ b/tests/neg/dynamicApplyNamedDynamicTest2.scala @@ -0,0 +1,10 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def selectDynamic(name: String): Array[String] = ??? + def applyDynamic(name: String)(args: Any*): String = ??? +} + +object DynamicTest { + new Foo().bazApply(a = "abc", b = 1) // error +} diff --git a/tests/neg/dynamicApplyNamedDynamicTest3.scala b/tests/neg/dynamicApplyNamedDynamicTest3.scala new file mode 100644 index 000000000..b8ec41c6d --- /dev/null +++ b/tests/neg/dynamicApplyNamedDynamicTest3.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + new Foo().bazApply("abc", b = 1) // error +} diff --git a/tests/neg/dynamicApplyNamedDynamicTest4.scala b/tests/neg/dynamicApplyNamedDynamicTest4.scala new file mode 100644 index 000000000..983cbc599 --- /dev/null +++ b/tests/neg/dynamicApplyNamedDynamicTest4.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + new Foo().bazApply("abc", 4, b = 1, b = "bcd") // error +} diff --git a/tests/neg/dynamicApplyNamedDynamicTest5.scala b/tests/neg/dynamicApplyNamedDynamicTest5.scala new file mode 100644 index 000000000..3a48d38d8 --- /dev/null +++ b/tests/neg/dynamicApplyNamedDynamicTest5.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamicNamed(name: String)(args: String*): String = ??? +} + +object DynamicTest { + new Foo().bazApply("abc", 4, b = 1, b = "bcd") // error // error // error // error +} diff --git a/tests/neg/dynamicApplyNamedDynamicTest6.scala b/tests/neg/dynamicApplyNamedDynamicTest6.scala new file mode 100644 index 000000000..4c39842af --- /dev/null +++ b/tests/neg/dynamicApplyNamedDynamicTest6.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamicNamed(name: Int)(args: Any*): String = ??? +} + +object DynamicTest { + new Foo().bazApply("abc", 4, b = 1, b = "bcd") // error +} diff --git a/tests/neg/dynamicApplyNamedDynamicTest7.scala b/tests/neg/dynamicApplyNamedDynamicTest7.scala new file mode 100644 index 000000000..c29339f6d --- /dev/null +++ b/tests/neg/dynamicApplyNamedDynamicTest7.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamicNamed(name: String)(args: Any*): String = ??? +} + +object DynamicTest { + def test: Int = new Foo().bazApply("abc", 4, b = 1, b = "bcd") // error +} diff --git a/tests/neg/dynamicApplyNamedDynamicTest8.scala b/tests/neg/dynamicApplyNamedDynamicTest8.scala new file mode 100644 index 000000000..e07b04ca1 --- /dev/null +++ b/tests/neg/dynamicApplyNamedDynamicTest8.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamic(name: String)(args: Any*): String = ??? +} + +object DynamicTest { + new Foo().applyDynamicNamed("abc")() // error +} diff --git a/tests/neg/dynamicDynamicImplicitsTest1.scala b/tests/neg/dynamicDynamicImplicitsTest1.scala new file mode 100644 index 000000000..b044336aa --- /dev/null +++ b/tests/neg/dynamicDynamicImplicitsTest1.scala @@ -0,0 +1,13 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def selectDynamic(name: String): String = ??? +} + +object DynamicTest { + implicit class Bar(foo: Foo) { + def bazSelect: Int = ??? + } + + def baz: String = new Foo().bazSelect // error +} diff --git a/tests/neg/dynamicDynamicImplicitsTest2.scala b/tests/neg/dynamicDynamicImplicitsTest2.scala new file mode 100644 index 000000000..3229d80e6 --- /dev/null +++ b/tests/neg/dynamicDynamicImplicitsTest2.scala @@ -0,0 +1,13 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamic(name: String)(args: Any*): String = ??? +} + +object DynamicTest { + implicit class Bar(foo: Foo) { + def bazApply: Int = ??? + } + + def baz: String = new Foo().bazApply("") // error +} diff --git a/tests/neg/dynamicDynamicImplicitsTest3.scala b/tests/neg/dynamicDynamicImplicitsTest3.scala new file mode 100644 index 000000000..6c26b9500 --- /dev/null +++ b/tests/neg/dynamicDynamicImplicitsTest3.scala @@ -0,0 +1,13 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applytDynamicNamed(name: String)(args: Any*): String = ??? +} + +object DynamicTest { + implicit class Bar(foo: Foo) { + def bazApply: Int = ??? + } + + def baz: String = new Foo().bazApply(a = "") // error +} diff --git a/tests/neg/dynamicSelectDynamicTest1.scala b/tests/neg/dynamicSelectDynamicTest1.scala new file mode 100644 index 000000000..a45671ad1 --- /dev/null +++ b/tests/neg/dynamicSelectDynamicTest1.scala @@ -0,0 +1,10 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + def testSelect = { + new Foo().bazSelect // error + () + } +} diff --git a/tests/neg/dynamicSelectDynamicTest2.scala b/tests/neg/dynamicSelectDynamicTest2.scala new file mode 100644 index 000000000..9ba19a14d --- /dev/null +++ b/tests/neg/dynamicSelectDynamicTest2.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + def testSelect = new Foo().bazSelect // error +} diff --git a/tests/neg/dynamicSelectDynamicTest3.scala b/tests/neg/dynamicSelectDynamicTest3.scala new file mode 100644 index 000000000..acdad7033 --- /dev/null +++ b/tests/neg/dynamicSelectDynamicTest3.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + new Foo().bazSelect // error +} diff --git a/tests/neg/dynamicSelectDynamicTest4.scala b/tests/neg/dynamicSelectDynamicTest4.scala new file mode 100644 index 000000000..7295a43fd --- /dev/null +++ b/tests/neg/dynamicSelectDynamicTest4.scala @@ -0,0 +1,11 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def selectDynamic(name: String): String = ??? +} + +object DynamicTest { + def testSelect: Int = { + new Foo().bazSelect // error + } +} diff --git a/tests/neg/dynamicSelectDynamicTest5.scala b/tests/neg/dynamicSelectDynamicTest5.scala new file mode 100644 index 000000000..17836efe4 --- /dev/null +++ b/tests/neg/dynamicSelectDynamicTest5.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + new Foo().bazSelectUpdate(6) = "a" // error +} diff --git a/tests/neg/dynamicSelectDynamicTest6.scala b/tests/neg/dynamicSelectDynamicTest6.scala new file mode 100644 index 000000000..8df9bf492 --- /dev/null +++ b/tests/neg/dynamicSelectDynamicTest6.scala @@ -0,0 +1,11 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def selectDynamic(name: Int): String = ??? +} + +object DynamicTest { + def testSelect: String = { + new Foo().bazSelect // error + } +} diff --git a/tests/neg/dynamicSelectDynamicTest7.scala b/tests/neg/dynamicSelectDynamicTest7.scala new file mode 100644 index 000000000..4b4207844 --- /dev/null +++ b/tests/neg/dynamicSelectDynamicTest7.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def applyDynamic(name: String)(args: Any*): String = ??? +} + +object DynamicTest { + new Foo().selectDynamic("bar") // error +} diff --git a/tests/neg/dynamicUpdateDynamicTest1.scala b/tests/neg/dynamicUpdateDynamicTest1.scala new file mode 100644 index 000000000..8eba9fe60 --- /dev/null +++ b/tests/neg/dynamicUpdateDynamicTest1.scala @@ -0,0 +1,7 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic + +object DynamicTest { + new Foo().bazUpdate = 42 // error +} diff --git a/tests/neg/dynamicUpdateDynamicTest2.scala b/tests/neg/dynamicUpdateDynamicTest2.scala new file mode 100644 index 000000000..8bb8a9537 --- /dev/null +++ b/tests/neg/dynamicUpdateDynamicTest2.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def updateDynamic(name: String)(value: String): Unit = ??? +} + +object DynamicTest { + new Foo().bazUpdate = 42 // error +} diff --git a/tests/neg/dynamicUpdateDynamicTest3.scala b/tests/neg/dynamicUpdateDynamicTest3.scala new file mode 100644 index 000000000..22008dda4 --- /dev/null +++ b/tests/neg/dynamicUpdateDynamicTest3.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def updateDynamic(name: String)(value: Int): Unit = ??? +} + +object DynamicTest { + def test: Int = new Foo().bazUpdate = 42 // error +} diff --git a/tests/neg/dynamicUpdateDynamicTest4.scala b/tests/neg/dynamicUpdateDynamicTest4.scala new file mode 100644 index 000000000..3cdd32b3c --- /dev/null +++ b/tests/neg/dynamicUpdateDynamicTest4.scala @@ -0,0 +1,9 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def updateDynamic(name: Int)(value: Int): Unit = ??? +} + +object DynamicTest { + new Foo().bazUpdate = 42 // error +} diff --git a/tests/run/dynamicDynamicTests.scala b/tests/run/dynamicDynamicTests.scala new file mode 100644 index 000000000..3f8da8298 --- /dev/null +++ b/tests/run/dynamicDynamicTests.scala @@ -0,0 +1,164 @@ +import scala.language.dynamics + +class Foo extends scala.Dynamic { + def selectDynamic(name: String): String = "selectDynamic(" + name + ")" + def applyDynamic(name: String)(args: Any*): String = "applyDynamic(" + name + ")" + args.mkString("(", ", ", ")") + def applyDynamicNamed(name: String)(args: (String, Any)*): String = "applyDynamicNamed(" + name + ")" + args.mkString("(", ", ", ")") + def updateDynamic(name: String)(value: Any): String = "updateDynamic(" + name + ")(" + value + ")" +} + +class Bar(self: String) extends scala.Dynamic { + def selectDynamic(name: String): Bar = new Bar(self + ".selectDynamic(" + name + ")") + def applyDynamic(name: String)(args: Any*): Bar = new Bar(self + ".applyDynamic(" + name + ")" + args.mkString("(", ", ", ")")) + def applyDynamicNamed(name: String)(args: (String, Any)*): Bar = new Bar(self + ".applyDynamicNamed(" + name + ")" + args.mkString("(", ", ", ")")) + def updateDynamic(name: String)(value: Any): Bar = new Bar(self + ".updateDynamic(" + name + ")(" + value + ")") + def update(key: Int, value: Int): Bar = new Bar(self + ".update(" + key + ", " + value + ")") + override def toString = self +} + +class Baz extends scala.Dynamic { + def selectDynamic(name: String): String = "selectDynamic(" + name + ")" + def applyDynamic(name: String)(args: String*): String = "applyDynamic(" + name + ")" + args.mkString("(", ", ", ")") + def applyDynamicNamed(name: String)(args: (String, Any)*): String = "applyDynamicNamed(" + name + ")" + args.mkString("(", ", ", ")") + def updateDynamic(name: String)(value: String): String = "updateDynamic(" + name + ")(" + value + ")" +} + +object Test { + implicit class StringUpdater(str: String) { + def update(name: String, v: String) = s"$str.update(" + name + ", " + v + ")" + } + + var failed = false + + def assertEquals(expected: String, actual: Any): Unit = { + if (expected != actual.toString) { + println("Error: expected <" + expected + "> but was <" + actual.toString + ">") + failed = true + } + } + + def main(args: Array[String]): Unit = { + runFooTests1() + runFooTests2() + runBarTests() + runBazTests() + assert(!failed) + } + + /** Test the basics of the transfomation rules. */ + def runFooTests1() = { + val foo = new Foo + + assertEquals("selectDynamic(bazSelect)", foo.bazSelect) + + assertEquals("applyDynamic(bazApply)()", foo.bazApply()) + assertEquals("applyDynamic(bazApply)(1)", foo.bazApply(1)) + assertEquals("applyDynamic(bazApply)(1, 2, 3)", foo.bazApply(1, 2, 3)) + assertEquals("applyDynamic(bazApply)(1, 2, a)", foo.bazApply(1, 2, "a")) + assertEquals("applyDynamic(bazApply)(1, 2, a)", foo.bazApply(List(1, 2, "a"): _*)) + + assertEquals("applyDynamicNamed(bazApply)((a,1))", foo.bazApply(a = 1)) + assertEquals("applyDynamicNamed(bazApply)((a,1), (b,2))", foo.bazApply(a = 1, b = 2)) + assertEquals("applyDynamicNamed(bazApply)((a,1), (,0))", foo.bazApply(a = 1, 0)) + assertEquals("applyDynamicNamed(bazApply)((a,1), (a,5))", foo.bazApply(a = 1, a = 5)) + assertEquals("applyDynamicNamed(bazApply)((,d), (a,1), (,5), (a,c))", foo.bazApply("d", a = 1, 5, a = 'c')) + + assertEquals("updateDynamic(bazUpdate)(abc)", foo.bazUpdate = "abc") + + assertEquals("selectDynamic(bazSelectUpdate).update(key, value)", foo.bazSelectUpdate("key") = "value") + } + + /** Test implicit conversions kick in before dynamic calls. */ + def runFooTests2() = { + implicit class Bar(foo: Foo) { + def bazSelect: String = "Bar.bazSelect" + def bazApply(args: Any*): String = args.mkString("Bar.bazApply(", ", ", ")") + def bazApplyNamed(a: Int = -1, b: String = "-1"): String = "Bar.bazApplyNamed(" + a + ", " + b + ")" + } + + val foo = new Foo + + assertEquals("Bar.bazSelect", foo.bazSelect) + + assertEquals("Bar.bazApply()", foo.bazApply()) + assertEquals("Bar.bazApply(1)", foo.bazApply(1)) + assertEquals("Bar.bazApply(1, 2, 3)", foo.bazApply(1, 2, 3)) + assertEquals("Bar.bazApply(1, 2, a)", foo.bazApply(1, 2, "a")) + + assertEquals("Bar.bazApplyNamed(1, -1)", foo.bazApplyNamed(a = 1)) + assertEquals("Bar.bazApplyNamed(1, 2)", foo.bazApplyNamed(a = 1, b = "2")) + assertEquals("Bar.bazApplyNamed(1, abc)", foo.bazApplyNamed(a = 1, "abc")) + + assertEquals("selectDynamic(bazSelectUpdate).update(key, value)", foo.bazSelectUpdate("key") = "value") + } + + /** Test cains of dynamic calls. */ + def runBarTests() = { + val bar = new Bar("bar") + + // dynamics combined with themselfs + assertEquals("bar.selectDynamic(select1).selectDynamic(select2).selectDynamic(select3)", + bar.select1.select2.select3) + assertEquals("bar.applyDynamic(apply1)().applyDynamic(apply2)().applyDynamic(apply3)()", + bar.apply1().apply2().apply3()) + assertEquals("bar.applyDynamic(apply1)(1).applyDynamic(apply2)(1, 2).applyDynamic(apply3)(1, 2, 3)", + bar.apply1(1).apply2(1, 2).apply3(1, 2, 3)) + assertEquals("bar.applyDynamicNamed(apply1)((a,1)).applyDynamicNamed(apply2)((,1), (b,2))", + bar.apply1(a = 1).apply2(1, b = 2)) + assertEquals("bar.updateDynamic(update1)(1).updateDynamic(update2)(4)", (bar.update1 = 1).update2 = 4) + assertEquals("bar.selectDynamic(update1).update(1, 2).selectDynamic(update2).update(3, 4)", + (bar.update1(1) = 2).update2(3) = 4) + + // selectDynamic combined with every other dynamic + assertEquals("bar.applyDynamic(apply1)().selectDynamic(select1)", bar.apply1().select1) + assertEquals("bar.selectDynamic(select1).applyDynamic(apply2)()", bar.select1.apply2()) + assertEquals("bar.applyDynamicNamed(apply1)((a,1)).selectDynamic(select1)", bar.apply1(a = 1).select1) + assertEquals("bar.selectDynamic(select1).applyDynamicNamed(apply1)((a,1))", bar.select1.apply1(a = 1)) + assertEquals("bar.updateDynamic(update1)(1).selectDynamic(select1)", (bar.update1 = 1).select1) + assertEquals("bar.selectDynamic(select1).updateDynamic(update1)(1)", bar.select1.update1 = 1) + assertEquals("bar.selectDynamic(update1).update(0, 1).selectDynamic(select1)", (bar.update1(0) = 1).select1) + assertEquals("bar.selectDynamic(select1).selectDynamic(update1).update(0, 1)", bar.select1.update1(0) = 1) + + // applyDynamic combined with every remaninig dynamic + assertEquals("bar.applyDynamic(apply1)().applyDynamicNamed(apply2)((a,1))", bar.apply1().apply2(a = 1)) + assertEquals("bar.applyDynamicNamed(apply1)((a,1)).applyDynamic(apply2)()", bar.apply1(a = 1).apply2()) + assertEquals("bar.applyDynamic(apply1)().updateDynamic(update1)(1)", bar.apply1().update1 = 1) + assertEquals("bar.updateDynamic(update1)(1).applyDynamic(apply1)()", (bar.update1 = 1).apply1()) + assertEquals("bar.applyDynamic(apply1)().selectDynamic(update1).update(0, 1)", bar.apply1().update1(0) = 1) + assertEquals("bar.selectDynamic(update1).update(0, 1).applyDynamic(apply1)()", (bar.update1(0) = 1).apply1()) + + // applyDynamicNamed combined with every remaninig dynamic + assertEquals("bar.applyDynamicNamed(apply1)((a,1)).updateDynamic(update1)(1)", bar.apply1(a = 1).update1 = 1) + assertEquals("bar.updateDynamic(update1)(1).applyDynamicNamed(apply1)((a,1))", (bar.update1 = 1).apply1(a = 1)) + assertEquals("bar.applyDynamicNamed(apply1)((a,1)).selectDynamic(update1).update(0, 1)", bar.apply1(a = 1).update1(0) = 1) + assertEquals("bar.selectDynamic(update1).update(0, 1).applyDynamicNamed(apply1)((a,1))", (bar.update1(0) = 1).apply1(a = 1)) + + // updateDynamic combined selectDynamic(_).update(_, _) + assertEquals("bar.updateDynamic(update1)(1).selectDynamic(update2).update(0, 1)", (bar.update1 = 1).update2(0) = 1) + assertEquals("bar.selectDynamic(update1).update(0, 1).updateDynamic(update2)(1)", (bar.update1(0) = 1).update2 = 1) + } + + /** Test implicit conversion in the arguments of the dynamic call. */ + def runBazTests() = { + implicit def intToString(n: Int): String = n.toString + + val baz = new Baz + + assertEquals("applyDynamic(bazApply)()", baz.bazApply()) + assertEquals("applyDynamic(bazApply)(1)", baz.bazApply(1)) + assertEquals("applyDynamic(bazApply)(1, 2, 3)", baz.bazApply(1, 2, 3)) + assertEquals("applyDynamic(bazApply)(1, 2, a)", baz.bazApply(1, 2, "a")) + + assertEquals("applyDynamicNamed(bazApply)((a,1))", baz.bazApply(a = 1)) + assertEquals("applyDynamicNamed(bazApply)((a,1), (b,2))", baz.bazApply(a = 1, b = 2)) + assertEquals("applyDynamicNamed(bazApply)((a,1), (,0))", baz.bazApply(a = 1, 0)) + assertEquals("applyDynamicNamed(bazApply)((a,1), (a,5))", baz.bazApply(a = 1, a = 5)) + assertEquals("applyDynamicNamed(bazApply)((,4), (a,1), (,5), (a,9))", baz.bazApply(4, a = 1, 5, a = 9)) + + assertEquals("updateDynamic(bazUpdate)(10)", baz.bazUpdate = 10) + + assertEquals("selectDynamic(bazSelectUpdate).update(key, 10)", baz.bazSelectUpdate("key") = 10) + assertEquals("selectDynamic(bazSelectUpdate).update(7, value)", baz.bazSelectUpdate(7) = "value") + assertEquals("selectDynamic(bazSelectUpdate).update(7, 10)", baz.bazSelectUpdate(7) = 10) + } +} |