summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@epfl.ch>2012-03-30 10:12:40 +0200
committerAdriaan Moors <adriaan.moors@epfl.ch>2012-04-13 19:08:08 +0200
commit2286d7b43180f3018a041163dc0cfa951c0397a4 (patch)
tree573575567b3889f4a8b57a6de8341bba6e0b4129
parent48b0a7360e8d0a7bbd7f8e1bbd3c3a239b5aaa16 (diff)
downloadscala-2286d7b43180f3018a041163dc0cfa951c0397a4.tar.gz
scala-2286d7b43180f3018a041163dc0cfa951c0397a4.tar.bz2
scala-2286d7b43180f3018a041163dc0cfa951c0397a4.zip
implement SIP Type Dynamic
-rw-r--r--src/compiler/scala/reflect/internal/StdNames.scala3
-rw-r--r--src/compiler/scala/reflect/internal/Symbols.scala2
-rw-r--r--src/compiler/scala/reflect/internal/TreeInfo.scala15
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala121
-rw-r--r--test/files/run/applydynamic_sip.check22
-rw-r--r--test/files/run/applydynamic_sip.scala58
6 files changed, 207 insertions, 14 deletions
diff --git a/src/compiler/scala/reflect/internal/StdNames.scala b/src/compiler/scala/reflect/internal/StdNames.scala
index 1666887133..696921237b 100644
--- a/src/compiler/scala/reflect/internal/StdNames.scala
+++ b/src/compiler/scala/reflect/internal/StdNames.scala
@@ -325,6 +325,7 @@ trait StdNames extends NameManglers { self: SymbolTable =>
val append: NameType = "append"
val apply: NameType = "apply"
val applyDynamic: NameType = "applyDynamic"
+ val applyDynamicNamed: NameType = "applyDynamicNamed"
val applyOrElse: NameType = "applyOrElse"
val args : NameType = "args"
val argv : NameType = "argv"
@@ -426,6 +427,7 @@ trait StdNames extends NameManglers { self: SymbolTable =>
val runtime: NameType = "runtime"
val sameElements: NameType = "sameElements"
val scala_ : NameType = "scala"
+ val selectDynamic: NameType = "selectDynamic"
val selectOverloadedMethod: NameType = "selectOverloadedMethod"
val selectTerm: NameType = "selectTerm"
val selectType: NameType = "selectType"
@@ -455,6 +457,7 @@ trait StdNames extends NameManglers { self: SymbolTable =>
val unapplySeq: NameType = "unapplySeq"
val unbox: NameType = "unbox"
val update: NameType = "update"
+ val updateDynamic: NameType = "updateDynamic"
val value: NameType = "value"
val valueOf : NameType = "valueOf"
val values : NameType = "values"
diff --git a/src/compiler/scala/reflect/internal/Symbols.scala b/src/compiler/scala/reflect/internal/Symbols.scala
index fc94e96acd..e29e267584 100644
--- a/src/compiler/scala/reflect/internal/Symbols.scala
+++ b/src/compiler/scala/reflect/internal/Symbols.scala
@@ -1874,7 +1874,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
site.nonPrivateMemberAdmitting(name, admit).filter(sym =>
!sym.isTerm || (site.memberType(this) matches site.memberType(sym)))
- /** The symbol overridden by this symbol in given class `ofclazz`.
+ /** The symbol, in class `ofclazz`, that is overridden by this symbol.
*
* @param ofclazz is a base class of this symbol's owner.
*/
diff --git a/src/compiler/scala/reflect/internal/TreeInfo.scala b/src/compiler/scala/reflect/internal/TreeInfo.scala
index ed22cad730..a8cca1625f 100644
--- a/src/compiler/scala/reflect/internal/TreeInfo.scala
+++ b/src/compiler/scala/reflect/internal/TreeInfo.scala
@@ -532,6 +532,21 @@ abstract class 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)
+
+
// domain-specific extractors for reification
import definitions._
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 2b7c8e8304..305e30aeee 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -675,7 +675,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
}
if (tree.tpe.isInstanceOf[MethodType] && pre.isStable && sym.tpe.params.isEmpty &&
(isStableContext(tree, mode, pt) || sym.isModule))
- tree.setType(MethodType(List(), singleType(pre, sym)))
+ tree.setType(MethodType(List(), singleType(pre, sym))) // TODO: should this be a NullaryMethodType?
else tree
}
@@ -2817,7 +2817,8 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
(args exists isNamed) || // uses a named argument
isNamedApplyBlock(fun)) { // fun was transformed to a named apply block =>
// integrate this application into the block
- tryNamesDefaults
+ if (dyna.isApplyDynamicNamed(fun)) dyna.typedNamedApply(tree, fun, args, mode, pt)
+ else tryNamesDefaults
} else {
val tparams = context.extractUndetparams()
if (tparams.isEmpty) { // all type params are defined
@@ -3438,7 +3439,100 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
case ErrorType =>
setError(treeCopy.TypeApply(tree, fun, args))
case _ =>
- TypedApplyDoesNotTakeTpeParametersError(tree, fun)
+ fun match {
+ // drop the application for an applyDynamic or selectDynamic call since it has been pushed down
+ case treeInfo.DynamicApplication(_, _) => fun
+ case _ => TypedApplyDoesNotTakeTpeParametersError(tree, fun)
+ }
+ }
+
+ object dyna {
+ import treeInfo.{isApplyDynamicName, DynamicUpdate, DynamicApplicationNamed}
+
+ def acceptsApplyDynamic(tp: Type) = tp.typeSymbol isNonBottomSubClass DynamicClass
+
+ /** Returns `Some(t)` if `name` can be selected dynamically on `qual`, `None` if not.
+ * `t` specifies the type to be passed to the applyDynamic/selectDynamic call (unless it is NoType)
+ * NOTE: currently either returns None or Some(NoType) (scala-virtualized extends this to Some(t) for selections on staged Structs)
+ */
+ def acceptsApplyDynamicWithType(qual: Tree, name: Name): Option[Type] =
+ // don't selectDynamic selectDynamic, do select dynamic at unknown type,
+ // in scala-virtualized, we may return a Some(tp) where tp ne NoType
+ if (!isApplyDynamicName(name) && acceptsApplyDynamic(qual.tpe.widen)) Some(NoType)
+ else None
+
+ def isDynamicallyUpdatable(tree: Tree) = tree match {
+ case DynamicUpdate(qual, name) =>
+ // if the qualifier is a Dynamic, that's all we need to know
+ acceptsApplyDynamic(qual.tpe)
+ case _ => false
+ }
+
+ def isApplyDynamicNamed(fun: Tree): Boolean = fun match {
+ case DynamicApplicationNamed(qual, _) if acceptsApplyDynamic(qual.tpe.widen) => true
+ case _ => false
+ // look deeper?
+ // val methPart = treeInfo.methPart(fun)
+ // println("methPart of "+ fun +" is "+ methPart)
+ // if (methPart ne fun) isApplyDynamicNamed(methPart)
+ // else false
+ }
+
+ def typedNamedApply(orig: Tree, fun: Tree, args: List[Tree], mode: Int, pt: Type): Tree = {
+ def argToBinding(arg: Tree): Tree = arg match {
+ case AssignOrNamedArg(Ident(name), rhs) => gen.mkTuple(List(CODE.LIT(name.toString), rhs))
+ case _ => gen.mkTuple(List(CODE.LIT(""), arg))
+ }
+ typed(treeCopy.Apply(orig, fun, args map argToBinding), mode, pt)
+ }
+
+ /** Translate selection that does not typecheck according to the normal rules into a selectDynamic/applyDynamic.
+ *
+ * foo.method("blah") ~~> foo.applyDynamic("method")("blah")
+ * foo.method(x = "blah") ~~> foo.applyDynamicNamed("method")(("x", "blah"))
+ * foo.varia = 10 ~~> foo.updateDynamic("varia")(10)
+ * foo.field ~~> foo.selectDynamic("field")
+ * foo.arr(10) = 13 ~~> foo.selectDynamic("arr").update(10, 13)
+ *
+ * what if we want foo.field == foo.selectDynamic("field") == 1, but `foo.field = 10` == `foo.selectDynamic("field").update(10)` == ()
+ * what would the signature for selectDynamic be? (hint: it needs to depend on whether an update call is coming or not)
+ *
+ * need to distinguish selectDynamic and applyDynamic somehow: the former must return the selected value, the latter must accept an apply or an update
+ * - could have only selectDynamic and pass it a boolean whether more is to come,
+ * so that it can either return the bare value or something that can handle the apply/update
+ * HOWEVER that makes it hard to return unrelated values for the two cases
+ * --> selectDynamic's return type is now dependent on the boolean flag whether more is to come
+ * - simplest solution: have two method calls
+ *
+ */
+ def mkInvoke(cxTree: Tree, tree: Tree, qual: Tree, name: Name): Option[Tree] =
+ acceptsApplyDynamicWithType(qual, name) map { tp =>
+ // tp eq NoType => can call xxxDynamic, but not passing any type args (unless specified explicitly by the user)
+ // in scala-virtualized, when not NoType, tp is passed as type argument (for selection on a staged Struct)
+
+ // strip off type application -- we're not doing much with outer, so don't bother preserving cxTree's attributes etc
+ val (outer, explicitTargs) = cxTree match {
+ case TypeApply(fun, targs) => (fun, targs)
+ case Apply(TypeApply(fun, targs), args) => (Apply(fun, args), targs)
+ case t => (t, Nil)
+ }
+
+ // note: context.tree includes at most one Apply node
+ // thus, we can't use it to detect we're going to receive named args in expressions such as:
+ // qual.sel(a)(a2, arg2 = "a2")
+ val oper = outer match {
+ case Apply(`tree`, as) => if (as collectFirst {case AssignOrNamedArg(lhs, rhs) =>} nonEmpty)
+ nme.applyDynamicNamed
+ else nme.applyDynamic
+ case Assign(`tree`, _) => nme.updateDynamic
+ case _ => nme.selectDynamic
+ }
+
+ val dynSel = Select(qual, oper)
+ val tappSel = if (explicitTargs nonEmpty) TypeApply(dynSel, explicitTargs) else dynSel
+
+ atPos(qual.pos)(Apply(tappSel, List(Literal(Constant(name.decode)))))
+ }
}
@inline final def deindentTyping() = context.typingIndentLevel -= 2
@@ -3612,10 +3706,17 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
case _ =>
}
}
+// if (varsym.isVariable ||
+// // setter-rewrite has been done above, so rule out methods here, but, wait a minute, why are we assigning to non-variables after erasure?!
+// (phase.erasedTypes && varsym.isValue && !varsym.isMethod)) {
if (varsym.isVariable || varsym.isValue && phase.erasedTypes) {
val rhs1 = typed(rhs, EXPRmode | BYVALmode, lhs1.tpe)
treeCopy.Assign(tree, lhs1, checkDead(rhs1)) setType UnitClass.tpe
}
+ else if(dyna.isDynamicallyUpdatable(lhs1)) {
+ val rhs1 = typed(rhs, EXPRmode | BYVALmode, WildcardType)
+ typed1(Apply(lhs1, List(rhs1)), mode, pt)
+ }
else fail()
}
@@ -4078,16 +4179,10 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser {
}
// try to expand according to Dynamic rules.
-
- if (settings.Xexperimental.value && (qual.tpe.widen.typeSymbol isNonBottomSubClass DynamicClass)) {
- var dynInvoke = Apply(Select(qual, nme.applyDynamic), List(Literal(Constant(name.decode))))
- context.tree match {
- case Apply(tree1, args) if tree1 eq tree =>
- ;
- case _ =>
- dynInvoke = Apply(dynInvoke, List())
- }
- return typed1(util.trace("dynatype: ")(dynInvoke), mode, pt)
+ dyna.mkInvoke(context.tree, tree, qual, name) match {
+ case Some(invocation) =>
+ return typed1(invocation, mode, pt)
+ case _ =>
}
if (settings.debug.value) {
diff --git a/test/files/run/applydynamic_sip.check b/test/files/run/applydynamic_sip.check
new file mode 100644
index 0000000000..d94db4417e
--- /dev/null
+++ b/test/files/run/applydynamic_sip.check
@@ -0,0 +1,22 @@
+qual.applyDynamic(sel)()
+qual.applyDynamic(sel)(a)
+qual.applyDynamic(sel)(a)
+.apply(a2)
+qual.applyDynamic(sel)(a)
+qual.applyDynamic(sel)(a)
+.apply(a2)
+qual.applyDynamicNamed(sel)((arg,a))
+qual.applyDynamicNamed(sel)((arg,a))
+qual.applyDynamicNamed(sel)((,a), (arg2,a2))
+qual.updateDynamic(sel)(expr)
+qual.selectDynamic(sel)
+qual.selectDynamic(sel)
+qual.selectDynamic(sel)
+.update(1, expr)
+qual.selectDynamic(sel)
+.update(expr)
+qual.selectDynamic(sel)
+.apply(1)
+qual.selectDynamic(sel)
+.apply
+.update(1, 1)
diff --git a/test/files/run/applydynamic_sip.scala b/test/files/run/applydynamic_sip.scala
new file mode 100644
index 0000000000..7150517530
--- /dev/null
+++ b/test/files/run/applydynamic_sip.scala
@@ -0,0 +1,58 @@
+object Test extends App {
+ object stubUpdate {
+ def update(as: Any*) = println(".update"+as.toList.mkString("(",", ", ")"))
+ }
+
+ object stub {
+ def apply = {println(".apply"); stubUpdate}
+ def apply(as: Any*) = println(".apply"+as.toList.mkString("(",", ", ")"))
+ def update(as: Any*) = println(".update"+as.toList.mkString("(",", ", ")"))
+ }
+ class MyDynamic extends Dynamic {
+ def applyDynamic[T](n: String)(as: Any*) = {println("qual.applyDynamic("+ n +")"+ as.toList.mkString("(",", ", ")")); stub}
+ def applyDynamicNamed[T](n: String)(as: (String, Any)*) = {println("qual.applyDynamicNamed("+ n +")"+ as.toList.mkString("(",", ", ")")); stub}
+ def selectDynamic[T](n: String) = {println("qual.selectDynamic("+ n +")"); stub}
+ def updateDynamic(n: String)(x: Any): Unit = {println("qual.updateDynamic("+ n +")("+ x +")")}
+ }
+ val qual = new MyDynamic
+ val expr = "expr"
+ val a = "a"
+ val a2 = "a2"
+ type T = String
+
+ // If qual.sel is followed by a potential type argument list [Ts] and an argument list (arg1, …, argn) where none of the arguments argi are named:
+ // qual.applyDynamic(“sel”)(arg1, …, argn)
+ qual.sel()
+ qual.sel(a)
+ // qual.sel(a, a2: _*) -- should not accept varargs?
+ qual.sel(a)(a2)
+ qual.sel[T](a)
+ qual.sel[T](a)(a2)
+
+ // If qual.sel is followed by a potential type argument list [Ts]
+ // and a non-empty named argument list (x1 = arg1, …, xn = argn) where some name prefixes xi = might be missing:
+ // qual.applyDynamicNamed(“sel”)(xs1 -> arg1, …, xsn -> argn)
+ qual.sel(arg = a)
+ qual.sel[T](arg = a)
+ qual.sel(a, arg2 = "a2")
+ // qual.sel(a)(a2, arg2 = "a2")
+ // qual.sel[T](a)(a2, arg2 = "a2")
+ // qual.sel(arg = a, a2: _*)
+ // qual.sel(arg, arg2 = "a2", a2: _*)
+
+ // If qual.sel appears immediately on the left-hand side of an assigment
+ // qual.updateDynamic(“sel”)(expr)
+ qual.sel = expr
+
+ // If qual.sel, possibly applied to type arguments, but is
+ // not applied to explicit value arguments,
+ // nor immediately followed by an assignment operator:
+ // qual.selectDynamic[Ts](“sel”)
+ qual.sel
+ qual.sel[T]
+
+ qual.sel(1) = expr // parser turns this into qual.sel.update(1, expr)
+ qual.sel() = expr // parser turns this into qual.sel.update(expr)
+ qual.sel.apply(1)
+ qual.sel.apply(1) = 1
+} \ No newline at end of file