diff options
author | Dmitry Petrashko <dark@d-d.me> | 2016-07-15 13:22:06 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-07-15 13:22:06 +0200 |
commit | f37e45a516ca97a27fed279c5da26574d2fe77db (patch) | |
tree | 268a2283e96ce363bf4fd9bca9d7a6be6e90ef7f /src | |
parent | 18a1c206295fc85c795da48b3e737b88a2bb56e5 (diff) | |
parent | 7e00c724273d432c8900c0e8ec852bb77357958e (diff) | |
download | dotty-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.scala | 4 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/StdNames.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/TypeOps.scala | 25 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Types.scala | 3 | ||||
-rw-r--r-- | src/dotty/tools/dotc/reporting/Reporter.scala | 30 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Applications.scala | 10 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Dynamic.scala | 71 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/TypeAssigner.scala | 9 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Typer.scala | 29 |
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 { |