Skip to content

Fixes for scala.Dynamic #1469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -630,20 +630,6 @@ object TreeInfo {
}
}

def isApplyDynamicName(name: Name) = (name == nme.updateDynamic) || (name == nme.selectDynamic) || (name == nme.applyDynamic) || (name == nme.applyDynamicNamed)

class DynamicApplicationExtractor(nameTest: Name => Boolean) {
def unapply(tree: Tree) = tree match {
case Apply(TypeApply(Select(qual, oper), _), List(Literal(Constant(name)))) if nameTest(oper) => Some((qual, name))
case Apply(Select(qual, oper), List(Literal(Constant(name)))) if nameTest(oper) => Some((qual, name))
case Apply(Ident(oper), List(Literal(Constant(name)))) if nameTest(oper) => Some((EmptyTree(), name))
case _ => None
}
}
object DynamicUpdate extends DynamicApplicationExtractor(_ == nme.updateDynamic)
object DynamicApplication extends DynamicApplicationExtractor(isApplyDynamicName)
object DynamicApplicationNamed extends DynamicApplicationExtractor(_ == nme.applyDynamicNamed)

object MacroImplReference {
private def refPart(tree: Tree): Tree = tree match {
case TypeApply(fun, _) => refPart(fun)
Expand Down
15 changes: 7 additions & 8 deletions src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -591,13 +591,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>

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 TryDynamicCallType => typedDynamicApply(tree, pt)
case _ =>
tryEither {
implicit ctx => simpleApply(fun1, proto)
Expand Down Expand Up @@ -679,7 +673,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
}
case _ =>
}
assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs)
def tryDynamicTypeApply(): Tree = typedFn match {
case typedFn: Select if !pt.isInstanceOf[FunProto] => typedDynamicSelect(typedFn, typedArgs, pt)
case _ => tree.withType(TryDynamicCallType)
}
if (typedFn.tpe eq TryDynamicCallType) tryDynamicTypeApply()
else assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs)
}

/** Rewrite `new Array[T](....)` if T is an unbounded generic to calls to newGenericArray.
Expand Down
85 changes: 59 additions & 26 deletions src/dotty/tools/dotc/typer/Dynamic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ 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.Trees._
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 {
Expand All @@ -28,44 +27,78 @@ object Dynamic {
* The first matching rule of is applied.
*/
trait Dynamic { self: Typer with Applications =>
import Dynamic._
import tpd._

/** 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), ...)
* foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
* foo.bar[T0, ...](baz0, baz1, ...) ~~> foo.applyDynamic[T0, ...](bar)(baz0, baz1, ...)
* foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
* foo.bar[T0, ...](x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed[T0, ...]("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)
def typedDynamicApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {
def typedDynamicApply(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree = {
def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false }
val args = tree.args
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", tree.pos)
tree.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, targs), args1), pt)
}
val args1 = if (dynName == nme.applyDynamic) args else namedArgs
typedApply(untpd.Apply(coreDynamic(qual, dynName, name), args1), pt)
}

tree.fun match {
case Select(qual, name) if !isDynamicMethod(name) =>
typedDynamicApply(qual, name, Nil)
case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) =>
typedDynamicApply(qual, name, targs)
case TypeApply(fun, targs) =>
typedDynamicApply(fun, nme.apply, targs)
case fun =>
typedDynamicApply(fun, nme.apply, Nil)
}
}

/** Translate selection that does not typecheck according to the normal rules into a selectDynamic.
* foo.bar ~~> foo.selectDynamic(bar)
* foo.bar ~~> foo.selectDynamic(bar)
* foo.bar[T0, ...] ~~> foo.selectDynamic[T0, ...](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)
def typedDynamicSelect(tree: untpd.Select, targs: List[Tree], pt: Type)(implicit ctx: Context): Tree =
typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name, targs), 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)
def typedDynamicAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context): Tree = {
def typedDynamicAssign(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree =
typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, targs), tree.rhs), pt)
tree.lhs match {
case Select(qual, name) if !isDynamicMethod(name) =>
typedDynamicAssign(qual, name, Nil)
case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) =>
typedDynamicAssign(qual, name, targs)
case _ =>
ctx.error("reassignment to val", tree.pos)
tree.withType(ErrorType)
}
}

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)))
private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name, targs: List[untpd.Tree])(implicit ctx: Context): untpd.Apply = {
val select = untpd.Select(qual, dynName)
val selectWithTypes =
if (targs.isEmpty) select
else untpd.TypeApply(select, targs)
untpd.Apply(selectWithTypes, Literal(Constant(name.toString)))
}
}
4 changes: 3 additions & 1 deletion src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ trait TypeAssigner {
val d2 = pre.nonPrivateMember(name)
if (reallyExists(d2) && firstTry)
test(tpe.shadowed.withDenot(d2), false)
else {
else if (pre.derivesFrom(defn.DynamicClass)) {
TryDynamicCallType
} else {
val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists)
val what = alts match {
case Nil =>
Expand Down
15 changes: 4 additions & 11 deletions src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))
if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos)
val select = typedSelect(tree, pt, qual1)
pt match {
case _: FunProto | AssignProto => select
case _ =>
if (select.tpe eq TryDynamicCallType) typedDynamicSelect(tree, pt)
else select
}
if (select.tpe ne TryDynamicCallType) select
else if (pt.isInstanceOf[PolyProto] || pt.isInstanceOf[FunProto] || pt == AssignProto) select
else typedDynamicSelect(tree, Nil, pt)
}

def asJavaSelectFromTypeTree(implicit ctx: Context): Tree = {
Expand Down Expand Up @@ -518,11 +515,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
reassignmentToVal
}
case TryDynamicCallType =>
tree match {
case Assign(Select(qual, name), rhs) if !isDynamicMethod(name) =>
typedDynamicAssign(qual, name, rhs, pt)
case _ => reassignmentToVal
}
typedDynamicAssign(tree, pt)
case tpe =>
reassignmentToVal
}
Expand Down
52 changes: 52 additions & 0 deletions tests/neg/applydynamic_sip.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
tests/neg/applydynamic_sip.scala:8: error: value applyDynamic is not a member of Dynamic(Test.qual)
possible cause: maybe a wrong Dynamic method signature?
qual.sel(a, a2: _*) // error
^
tests/neg/applydynamic_sip.scala:9: error: applyDynamicNamed does not support passing a vararg parameter
qual.sel(arg = a, a2: _*) // error
^
tests/neg/applydynamic_sip.scala:10: error: applyDynamicNamed does not support passing a vararg parameter
qual.sel(arg, arg2 = "a2", a2: _*) // error
^
tests/neg/applydynamic_sip.scala:20: error: type mismatch:
found : String("sel")
required: Int
bad1.sel // error
^
tests/neg/applydynamic_sip.scala:21: error: type mismatch:
found : String("sel")
required: Int
bad1.sel(1) // error // error
^
tests/neg/applydynamic_sip.scala:21: error: method applyDynamic in class Bad1 does not take more parameters
bad1.sel(1) // error // error
^
tests/neg/applydynamic_sip.scala:22: error: type mismatch:
found : String("sel")
required: Int
bad1.sel(a = 1) // error // error
^
tests/neg/applydynamic_sip.scala:22: error: method applyDynamicNamed in class Bad1 does not take more parameters
bad1.sel(a = 1) // error // error
^
tests/neg/applydynamic_sip.scala:23: error: type mismatch:
found : String("sel")
required: Int
bad1.sel = 1 // error // error
^
tests/neg/applydynamic_sip.scala:23: error: method updateDynamic in class Bad1 does not take more parameters
bad1.sel = 1 // error // error
^
tests/neg/applydynamic_sip.scala:32: error: method selectDynamic in class Bad2 does not take parameters
bad2.sel // error
^
tests/neg/applydynamic_sip.scala:33: error: method applyDynamic in class Bad2 does not take parameters
bad2.sel(1) // error
^
tests/neg/applydynamic_sip.scala:34: error: method applyDynamicNamed in class Bad2 does not take parameters
bad2.sel(a = 1) // error
^
tests/neg/applydynamic_sip.scala:35: error: method updateDynamic in class Bad2 does not take parameters
bad2.sel = 1 // error
^
14 errors found
36 changes: 36 additions & 0 deletions tests/neg/applydynamic_sip.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import scala.language.dynamics
object Test extends App {
val qual: Dynamic = ???
val expr = "expr"
val a = "a"
val a2 = "a2"

qual.sel(a, a2: _*) // error
qual.sel(arg = a, a2: _*) // error
qual.sel(arg, arg2 = "a2", a2: _*) // error

class Bad1 extends Dynamic {
def selectDynamic(n: Int) = n
def applyDynamic(n: Int) = n
def applyDynamicNamed(n: Int) = n
def updateDynamic(n: Int) = n

}
val bad1 = new Bad1
bad1.sel // error
bad1.sel(1) // error // error
bad1.sel(a = 1) // error // error
bad1.sel = 1 // error // error

class Bad2 extends Dynamic {
def selectDynamic = 1
def applyDynamic = 1
def applyDynamicNamed = 1
def updateDynamic = 1
}
val bad2 = new Bad2
bad2.sel // error
bad2.sel(1) // error
bad2.sel(a = 1) // error
bad2.sel = 1 // error
}
4 changes: 2 additions & 2 deletions tests/untried/neg/t6355b.check → tests/neg/t6355b.check
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
t6355b.scala:14: error: value applyDynamic is not a member of A
error after rewriting to x.<applyDynamic: error>("bippy")
possible cause: maybe a wrong Dynamic method signature?
println(x.bippy(42))
println(x.bippy(42)) // error
^
t6355b.scala:15: error: value applyDynamic is not a member of A
error after rewriting to x.<applyDynamic: error>("bippy")
possible cause: maybe a wrong Dynamic method signature?
println(x.bippy("42"))
println(x.bippy("42")) // error
^
two errors found
4 changes: 2 additions & 2 deletions tests/untried/neg/t6355b.scala → tests/neg/t6355b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class B(method: String) {
object Test {
def main(args: Array[String]): Unit = {
val x = new A
println(x.bippy(42))
println(x.bippy("42"))
println(x.bippy(42)) // error
println(x.bippy("42")) // error
}
}
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/untried/neg/t6663.scala → tests/neg/t6663.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object Test extends App {
// but, before fixing SI-6663, became
// C(42).selectDynamic("foo").get, ignoring
// the [String] type parameter
var v = new C(42).foo[String].get :Int
var v = new C(42).foo[String].get :Int // error
println(v)
}

2 changes: 1 addition & 1 deletion tests/untried/neg/t6920.check → tests/neg/t6920.check
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
t6920.scala:9: error: too many arguments for method applyDynamicNamed: (values: Seq[(String, Any)])String
error after rewriting to CompilerError.this.test.applyDynamicNamed("crushTheCompiler")(scala.Tuple2("a", 1), scala.Tuple2("b", 2))
possible cause: maybe a wrong Dynamic method signature?
test.crushTheCompiler(a = 1, b = 2)
test.crushTheCompiler(a = 1, b = 2) // error
^
one error found
2 changes: 1 addition & 1 deletion tests/untried/neg/t6920.scala → tests/neg/t6920.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ class DynTest extends Dynamic {

class CompilerError {
val test = new DynTest
test.crushTheCompiler(a = 1, b = 2)
test.crushTheCompiler(a = 1, b = 2) // error
}
2 changes: 1 addition & 1 deletion tests/untried/neg/t8006.check → tests/neg/t8006.check
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
t8006.scala:3: error: too many arguments for method applyDynamicNamed: (value: (String, Any))String
error after rewriting to X.this.d.applyDynamicNamed("meth")(scala.Tuple2("value1", 10), scala.Tuple2("value2", 100))
possible cause: maybe a wrong Dynamic method signature?
d.meth(value1 = 10, value2 = 100) // two arguments here, but only one is allowed
d.meth(value1 = 10, value2 = 100) // error: two arguments here, but only one is allowed
^
one error found
2 changes: 1 addition & 1 deletion tests/untried/neg/t8006.scala → tests/neg/t8006.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
object X {
val d = new D
d.meth(value1 = 10, value2 = 100) // two arguments here, but only one is allowed
d.meth(value1 = 10, value2 = 100) // error: two arguments here, but only one is allowed
}
import language.dynamics
class D extends Dynamic {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import scala.language.dynamics
object Test extends dotty.runtime.LegacyApp {
object stubUpdate {
def update(as: Any*) = println(".update"+as.toList.mkString("(",", ", ")"))
Expand Down
File renamed without changes.
File renamed without changes.
Loading