aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDmitry Petrashko <dark@d-d.me>2016-07-15 13:22:06 +0200
committerGitHub <noreply@github.com>2016-07-15 13:22:06 +0200
commitf37e45a516ca97a27fed279c5da26574d2fe77db (patch)
tree268a2283e96ce363bf4fd9bca9d7a6be6e90ef7f /src
parent18a1c206295fc85c795da48b3e737b88a2bb56e5 (diff)
parent7e00c724273d432c8900c0e8ec852bb77357958e (diff)
downloaddotty-f37e45a516ca97a27fed279c5da26574d2fe77db.tar.gz
dotty-f37e45a516ca97a27fed279c5da26574d2fe77db.tar.bz2
dotty-f37e45a516ca97a27fed279c5da26574d2fe77db.zip
Merge pull request #1291 from nicolasstucki/implement-scala-dynamic
Add scala.Dynamic support.
Diffstat (limited to 'src')
-rw-r--r--src/dotty/tools/dotc/core/Definitions.scala4
-rw-r--r--src/dotty/tools/dotc/core/StdNames.scala1
-rw-r--r--src/dotty/tools/dotc/core/TypeOps.scala25
-rw-r--r--src/dotty/tools/dotc/core/Types.scala3
-rw-r--r--src/dotty/tools/dotc/reporting/Reporter.scala30
-rw-r--r--src/dotty/tools/dotc/typer/Applications.scala10
-rw-r--r--src/dotty/tools/dotc/typer/Dynamic.scala71
-rw-r--r--src/dotty/tools/dotc/typer/ProtoTypes.scala2
-rw-r--r--src/dotty/tools/dotc/typer/TypeAssigner.scala9
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala29
10 files changed, 164 insertions, 20 deletions
diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala
index 8cd810889..6d183fe40 100644
--- a/src/dotty/tools/dotc/core/Definitions.scala
+++ b/src/dotty/tools/dotc/core/Definitions.scala
@@ -424,8 +424,10 @@ class Definitions {
def Product_productArity(implicit ctx: Context) = Product_productArityR.symbol
lazy val Product_productPrefixR = ProductClass.requiredMethodRef(nme.productPrefix)
def Product_productPrefix(implicit ctx: Context) = Product_productPrefixR.symbol
- lazy val LanguageModuleRef = ctx.requiredModule("dotty.language")
+ lazy val LanguageModuleRef = ctx.requiredModule("dotty.language")
def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass
+ lazy val Scala2LanguageModuleRef = ctx.requiredModule("scala.language")
+ def Scala2LanguageModuleClass(implicit ctx: Context) = Scala2LanguageModuleRef.symbol.moduleClass.asClass
lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl")
lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag")
diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala
index f9ede23c5..f47ab1744 100644
--- a/src/dotty/tools/dotc/core/StdNames.scala
+++ b/src/dotty/tools/dotc/core/StdNames.scala
@@ -383,6 +383,7 @@ object StdNames {
val delayedInit: N = "delayedInit"
val delayedInitArg: N = "delayedInit$body"
val drop: N = "drop"
+ val dynamics: N = "dynamics"
val dummyApply: N = "<dummy-apply>"
val elem: N = "elem"
val emptyValDef: N = "emptyValDef"
diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala
index 80e0fc6f1..10b0f5615 100644
--- a/src/dotty/tools/dotc/core/TypeOps.scala
+++ b/src/dotty/tools/dotc/core/TypeOps.scala
@@ -483,17 +483,19 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
*/
def featureEnabled(owner: ClassSymbol, feature: TermName): Boolean = {
def toPrefix(sym: Symbol): String =
- if (sym eq defn.LanguageModuleClass) "" else toPrefix(sym.owner) + sym.name + "."
+ if (!sym.exists || (sym eq defn.LanguageModuleClass) || (sym eq defn.Scala2LanguageModuleRef)) ""
+ else toPrefix(sym.owner) + sym.name + "."
def featureName = toPrefix(owner) + feature
- def hasImport(implicit ctx: Context): Boolean = (
- ctx.importInfo != null
- && ( (ctx.importInfo.site.widen.typeSymbol eq owner)
- && ctx.importInfo.originals.contains(feature)
- ||
- { var c = ctx.outer
- while (c.importInfo eq ctx.importInfo) c = c.outer
- hasImport(c)
- }))
+ def hasImport(implicit ctx: Context): Boolean = {
+ if (ctx.importInfo == null || (ctx.importInfo.site.widen.typeSymbol ne owner)) false
+ else if (ctx.importInfo.excluded.contains(feature)) false
+ else if (ctx.importInfo.originals.contains(feature)) true
+ else {
+ var c = ctx.outer
+ while (c.importInfo eq ctx.importInfo) c = c.outer
+ hasImport(c)
+ }
+ }
def hasOption = ctx.base.settings.language.value exists (s => s == featureName || s == "_")
hasImport(ctx.withPhase(ctx.typerPhase)) || hasOption
}
@@ -505,6 +507,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
def scala2Mode =
featureEnabled(defn.LanguageModuleClass, nme.Scala2)
+ def dynamicsEnabled =
+ featureEnabled(defn.Scala2LanguageModuleClass, nme.dynamics)
+
def testScala2Mode(msg: String, pos: Position) = {
if (scala2Mode) migrationWarning(msg, pos)
scala2Mode
diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala
index fa402f9fc..11da27265 100644
--- a/src/dotty/tools/dotc/core/Types.scala
+++ b/src/dotty/tools/dotc/core/Types.scala
@@ -3221,6 +3221,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/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala
index e4169b1fd..bddfd2f68 100644
--- a/src/dotty/tools/dotc/reporting/Reporter.scala
+++ b/src/dotty/tools/dotc/reporting/Reporter.scala
@@ -12,6 +12,7 @@ import config.Printers
import java.lang.System.currentTimeMillis
import core.Mode
import interfaces.Diagnostic.{ERROR, WARNING, INFO}
+import dotty.tools.dotc.core.Symbols.Symbol
object Reporter {
class Error(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, ERROR)
@@ -68,6 +69,29 @@ trait Reporting { this: Context =>
def featureWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit =
reporter.report(new FeatureWarning(msg, pos))
+ def featureWarning(feature: String, featureDescription: String, isScala2Feature: Boolean,
+ featureUseSite: Symbol, required: Boolean, pos: SourcePosition): Unit = {
+ val req = if (required) "needs to" else "should"
+ val prefix = if (isScala2Feature) "scala." else "dotty."
+ val fqname = prefix + "language." + feature
+
+ val explain = {
+ if (reporter.isReportedFeatureUseSite(featureUseSite)) ""
+ else {
+ reporter.reportNewFeatureUseSite(featureUseSite)
+ s"""|
+ |This can be achieved by adding the import clause 'import $fqname'
+ |or by setting the compiler option -language:$feature.
+ |See the Scala docs for value $fqname for a discussion
+ |why the feature $req be explicitly enabled.""".stripMargin
+ }
+ }
+
+ val msg = s"$featureDescription $req be enabled\nby making the implicit value $fqname visible.$explain"
+ if (required) error(msg, pos)
+ else reporter.report(new FeatureWarning(msg, pos))
+ }
+
def warning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit =
reporter.report(new Warning(msg, pos))
@@ -172,7 +196,7 @@ abstract class Reporter extends interfaces.ReporterResult {
/** Report a diagnostic */
def doReport(d: Diagnostic)(implicit ctx: Context): Unit
- /** Whether very long lines can be truncated. This exists so important
+ /** Whether very long lines can be truncated. This exists so important
* debugging information (like printing the classpath) is not rendered
* invisible due to the max message length.
*/
@@ -206,6 +230,10 @@ abstract class Reporter extends interfaces.ReporterResult {
*/
def errorsReported = hasErrors
+ private[this] var reportedFeaturesUseSites = Set[Symbol]()
+ def isReportedFeatureUseSite(featureTrait: Symbol): Boolean = reportedFeaturesUseSites.contains(featureTrait)
+ def reportNewFeatureUseSite(featureTrait: Symbol): Unit = reportedFeaturesUseSites += featureTrait
+
val unreportedWarnings = new mutable.HashMap[String, Int] {
override def default(key: String) = 0
}
diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala
index aba073f3d..6e78a570d 100644
--- a/src/dotty/tools/dotc/typer/Applications.scala
+++ b/src/dotty/tools/dotc/typer/Applications.scala
@@ -87,11 +87,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
@@ -554,6 +555,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 3a13212a3..a430d5f75 100644
--- a/src/dotty/tools/dotc/typer/ProtoTypes.scala
+++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala
@@ -438,6 +438,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 0344ae6c6..1394d2e3e 100644
--- a/src/dotty/tools/dotc/typer/TypeAssigner.scala
+++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala
@@ -199,11 +199,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 4e2842da7..b9d5e3817 100644
--- a/src/dotty/tools/dotc/typer/Typer.scala
+++ b/src/dotty/tools/dotc/typer/Typer.scala
@@ -57,11 +57,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
@@ -315,7 +316,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 = {
@@ -479,7 +486,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) ||
@@ -507,6 +514,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
}
@@ -1091,7 +1104,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
.withType(dummy.nonMemberTermRef)
checkVariance(impl1)
if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.pos)
- assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls)
+ val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls)
+ if (ctx.phase.isTyper && cdef1.tpe.derivesFrom(defn.DynamicClass) && !ctx.dynamicsEnabled) {
+ val isRequired = parents1.exists(_.tpe.isRef(defn.DynamicClass))
+ ctx.featureWarning(nme.dynamics.toString, "extension of type scala.Dynamic", isScala2Feature = true,
+ cls, isRequired, cdef.pos)
+ }
+ cdef1
// todo later: check that
// 1. If class is non-abstract, it is instantiatable:
@@ -1686,7 +1705,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 {