From 75da0358fd7866f3dccdfcf4fbeae9af8ccc69f3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 30 May 2016 09:20:21 +0200 Subject: Fix #657: Add scala.Dynamic support. --- src/dotty/tools/dotc/core/Types.scala | 3 ++ src/dotty/tools/dotc/typer/Applications.scala | 10 +++- src/dotty/tools/dotc/typer/Dynamic.scala | 71 +++++++++++++++++++++++++++ src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 + src/dotty/tools/dotc/typer/TypeAssigner.scala | 9 +++- src/dotty/tools/dotc/typer/Typer.scala | 21 ++++++-- 6 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 src/dotty/tools/dotc/typer/Dynamic.scala (limited to 'src') 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 { -- cgit v1.2.3