Skip to content

Commit f37e45a

Browse files
authored
Merge pull request #1291 from nicolasstucki/implement-scala-dynamic
Add scala.Dynamic support.
2 parents 18a1c20 + 7e00c72 commit f37e45a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+626
-20
lines changed

src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,10 @@ class Definitions {
424424
def Product_productArity(implicit ctx: Context) = Product_productArityR.symbol
425425
lazy val Product_productPrefixR = ProductClass.requiredMethodRef(nme.productPrefix)
426426
def Product_productPrefix(implicit ctx: Context) = Product_productPrefixR.symbol
427-
lazy val LanguageModuleRef = ctx.requiredModule("dotty.language")
427+
lazy val LanguageModuleRef = ctx.requiredModule("dotty.language")
428428
def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass
429+
lazy val Scala2LanguageModuleRef = ctx.requiredModule("scala.language")
430+
def Scala2LanguageModuleClass(implicit ctx: Context) = Scala2LanguageModuleRef.symbol.moduleClass.asClass
429431
lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl")
430432

431433
lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag")

src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ object StdNames {
383383
val delayedInit: N = "delayedInit"
384384
val delayedInitArg: N = "delayedInit$body"
385385
val drop: N = "drop"
386+
val dynamics: N = "dynamics"
386387
val dummyApply: N = "<dummy-apply>"
387388
val elem: N = "elem"
388389
val emptyValDef: N = "emptyValDef"

src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -483,17 +483,19 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
483483
*/
484484
def featureEnabled(owner: ClassSymbol, feature: TermName): Boolean = {
485485
def toPrefix(sym: Symbol): String =
486-
if (sym eq defn.LanguageModuleClass) "" else toPrefix(sym.owner) + sym.name + "."
486+
if (!sym.exists || (sym eq defn.LanguageModuleClass) || (sym eq defn.Scala2LanguageModuleRef)) ""
487+
else toPrefix(sym.owner) + sym.name + "."
487488
def featureName = toPrefix(owner) + feature
488-
def hasImport(implicit ctx: Context): Boolean = (
489-
ctx.importInfo != null
490-
&& ( (ctx.importInfo.site.widen.typeSymbol eq owner)
491-
&& ctx.importInfo.originals.contains(feature)
492-
||
493-
{ var c = ctx.outer
494-
while (c.importInfo eq ctx.importInfo) c = c.outer
495-
hasImport(c)
496-
}))
489+
def hasImport(implicit ctx: Context): Boolean = {
490+
if (ctx.importInfo == null || (ctx.importInfo.site.widen.typeSymbol ne owner)) false
491+
else if (ctx.importInfo.excluded.contains(feature)) false
492+
else if (ctx.importInfo.originals.contains(feature)) true
493+
else {
494+
var c = ctx.outer
495+
while (c.importInfo eq ctx.importInfo) c = c.outer
496+
hasImport(c)
497+
}
498+
}
497499
def hasOption = ctx.base.settings.language.value exists (s => s == featureName || s == "_")
498500
hasImport(ctx.withPhase(ctx.typerPhase)) || hasOption
499501
}
@@ -505,6 +507,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
505507
def scala2Mode =
506508
featureEnabled(defn.LanguageModuleClass, nme.Scala2)
507509

510+
def dynamicsEnabled =
511+
featureEnabled(defn.Scala2LanguageModuleClass, nme.dynamics)
512+
508513
def testScala2Mode(msg: String, pos: Position) = {
509514
if (scala2Mode) migrationWarning(msg, pos)
510515
scala2Mode

src/dotty/tools/dotc/core/Types.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3221,6 +3221,9 @@ object Types {
32213221

32223222
object ErrorType extends ErrorType
32233223

3224+
/* Type used to track Select nodes that could not resolve a member and their qualifier is a scala.Dynamic. */
3225+
object TryDynamicCallType extends ErrorType
3226+
32243227
/** Wildcard type, possibly with bounds */
32253228
abstract case class WildcardType(optBounds: Type) extends CachedGroundType with TermType {
32263229
def derivedWildcardType(optBounds: Type)(implicit ctx: Context) =

src/dotty/tools/dotc/reporting/Reporter.scala

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import config.Printers
1212
import java.lang.System.currentTimeMillis
1313
import core.Mode
1414
import interfaces.Diagnostic.{ERROR, WARNING, INFO}
15+
import dotty.tools.dotc.core.Symbols.Symbol
1516

1617
object Reporter {
1718
class Error(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, ERROR)
@@ -68,6 +69,29 @@ trait Reporting { this: Context =>
6869
def featureWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit =
6970
reporter.report(new FeatureWarning(msg, pos))
7071

72+
def featureWarning(feature: String, featureDescription: String, isScala2Feature: Boolean,
73+
featureUseSite: Symbol, required: Boolean, pos: SourcePosition): Unit = {
74+
val req = if (required) "needs to" else "should"
75+
val prefix = if (isScala2Feature) "scala." else "dotty."
76+
val fqname = prefix + "language." + feature
77+
78+
val explain = {
79+
if (reporter.isReportedFeatureUseSite(featureUseSite)) ""
80+
else {
81+
reporter.reportNewFeatureUseSite(featureUseSite)
82+
s"""|
83+
|This can be achieved by adding the import clause 'import $fqname'
84+
|or by setting the compiler option -language:$feature.
85+
|See the Scala docs for value $fqname for a discussion
86+
|why the feature $req be explicitly enabled.""".stripMargin
87+
}
88+
}
89+
90+
val msg = s"$featureDescription $req be enabled\nby making the implicit value $fqname visible.$explain"
91+
if (required) error(msg, pos)
92+
else reporter.report(new FeatureWarning(msg, pos))
93+
}
94+
7195
def warning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit =
7296
reporter.report(new Warning(msg, pos))
7397

@@ -172,7 +196,7 @@ abstract class Reporter extends interfaces.ReporterResult {
172196
/** Report a diagnostic */
173197
def doReport(d: Diagnostic)(implicit ctx: Context): Unit
174198

175-
/** Whether very long lines can be truncated. This exists so important
199+
/** Whether very long lines can be truncated. This exists so important
176200
* debugging information (like printing the classpath) is not rendered
177201
* invisible due to the max message length.
178202
*/
@@ -206,6 +230,10 @@ abstract class Reporter extends interfaces.ReporterResult {
206230
*/
207231
def errorsReported = hasErrors
208232

233+
private[this] var reportedFeaturesUseSites = Set[Symbol]()
234+
def isReportedFeatureUseSite(featureTrait: Symbol): Boolean = reportedFeaturesUseSites.contains(featureTrait)
235+
def reportNewFeatureUseSite(featureTrait: Symbol): Unit = reportedFeaturesUseSites += featureTrait
236+
209237
val unreportedWarnings = new mutable.HashMap[String, Int] {
210238
override def default(key: String) = 0
211239
}

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,12 @@ object Applications {
8787

8888
import Applications._
8989

90-
trait Applications extends Compatibility { self: Typer =>
90+
trait Applications extends Compatibility { self: Typer with Dynamic =>
9191

9292
import Applications._
9393
import tpd.{ cpy => _, _ }
9494
import untpd.cpy
95+
import Dynamic.isDynamicMethod
9596

9697
/** @tparam Arg the type of arguments, could be tpd.Tree, untpd.Tree, or Type
9798
* @param methRef the reference to the method of the application
@@ -554,6 +555,13 @@ trait Applications extends Compatibility { self: Typer =>
554555

555556
fun1.tpe match {
556557
case ErrorType => tree.withType(ErrorType)
558+
case TryDynamicCallType =>
559+
tree match {
560+
case tree @ Apply(Select(qual, name), args) if !isDynamicMethod(name) =>
561+
typedDynamicApply(qual, name, args, pt)(tree)
562+
case _ =>
563+
handleUnexpectedFunType(tree, fun1)
564+
}
557565
case _ => methPart(fun1).tpe match {
558566
case funRef: TermRef =>
559567
tryEither { implicit ctx =>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package dotty.tools
2+
package dotc
3+
package typer
4+
5+
import dotty.tools.dotc.ast.Trees.NamedArg
6+
import dotty.tools.dotc.ast.tpd._
7+
import dotty.tools.dotc.ast.untpd
8+
import dotty.tools.dotc.core.Constants.Constant
9+
import dotty.tools.dotc.core.Contexts.Context
10+
import dotty.tools.dotc.core.Names.Name
11+
import dotty.tools.dotc.core.StdNames._
12+
import dotty.tools.dotc.core.Types._
13+
import dotty.tools.dotc.core.Mode
14+
import dotty.tools.dotc.core.Decorators._
15+
16+
object Dynamic {
17+
def isDynamicMethod(name: Name): Boolean =
18+
name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed
19+
}
20+
21+
/** Translates selection that does not typecheck according to the scala.Dynamic rules:
22+
* foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux)
23+
* foo.bar = baz ~~> foo.updateDynamic("bar")(baz)
24+
* foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
25+
* foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
26+
* foo.bar ~~> foo.selectDynamic(bar)
27+
*
28+
* The first matching rule of is applied.
29+
*/
30+
trait Dynamic { self: Typer with Applications =>
31+
32+
/** Translate selection that does not typecheck according to the normal rules into a applyDynamic/applyDynamicNamed.
33+
* foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
34+
* foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
35+
*/
36+
def typedDynamicApply(qual: untpd.Tree, name: Name, args: List[untpd.Tree], pt: Type)(original: untpd.Apply)(
37+
implicit ctx: Context): Tree = {
38+
def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false }
39+
val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic
40+
if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) {
41+
ctx.error("applyDynamicNamed does not support passing a vararg parameter", original.pos)
42+
original.withType(ErrorType)
43+
} else {
44+
def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg))
45+
def namedArgs = args.map {
46+
case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg)
47+
case arg => namedArgTuple("", arg)
48+
}
49+
val args1 = if (dynName == nme.applyDynamic) args else namedArgs
50+
typedApply(untpd.Apply(coreDynamic(qual, dynName, name), args1), pt)
51+
}
52+
}
53+
54+
/** Translate selection that does not typecheck according to the normal rules into a selectDynamic.
55+
* foo.bar ~~> foo.selectDynamic(bar)
56+
*
57+
* Note: inner part of translation foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) is achieved
58+
* through an existing transformation of in typedAssign [foo.bar(baz) = quux ~~> foo.bar.update(baz, quux)].
59+
*/
60+
def typedDynamicSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree =
61+
typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name), pt)
62+
63+
/** Translate selection that does not typecheck according to the normal rules into a updateDynamic.
64+
* foo.bar = baz ~~> foo.updateDynamic(bar)(baz)
65+
*/
66+
def typedDynamicAssign(qual: untpd.Tree, name: Name, rhs: untpd.Tree, pt: Type)(implicit ctx: Context): Tree =
67+
typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name), rhs), pt)
68+
69+
private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name)(implicit ctx: Context): untpd.Apply =
70+
untpd.Apply(untpd.Select(qual, dynName), Literal(Constant(name.toString)))
71+
}

src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,8 @@ object ProtoTypes {
438438
(if (theMap != null) theMap else new WildApproxMap).mapOver(tp)
439439
}
440440

441+
@sharable object AssignProto extends UncachedGroundType with MatchAlways
442+
441443
private[ProtoTypes] class WildApproxMap(implicit ctx: Context) extends TypeMap {
442444
def apply(tp: Type) = wildApprox(tp, this)
443445
}

src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,16 @@ trait TypeAssigner {
199199
def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = {
200200
val mbr = site.member(name)
201201
if (reallyExists(mbr)) site.select(name, mbr)
202-
else {
202+
else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) {
203+
TryDynamicCallType
204+
} else {
203205
if (!site.isErroneous) {
204206
ctx.error(
205207
if (name == nme.CONSTRUCTOR) d"$site does not have a constructor"
206-
else d"$name is not a member of $site", pos)
208+
else if (site.derivesFrom(defn.DynamicClass)) {
209+
d"$name is not a member of $site\n" +
210+
"possible cause: maybe a wrong Dynamic method signature?"
211+
} else d"$name is not a member of $site", pos)
207212
}
208213
ErrorType
209214
}

src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ object Typer {
5757
assert(tree.pos.exists, s"position not set for $tree # ${tree.uniqueId}")
5858
}
5959

60-
class Typer extends Namer with TypeAssigner with Applications with Implicits with Checking {
60+
class Typer extends Namer with TypeAssigner with Applications with Implicits with Dynamic with Checking {
6161

6262
import Typer._
6363
import tpd.{cpy => _, _}
6464
import untpd.cpy
65+
import Dynamic.isDynamicMethod
6566

6667
/** A temporary data item valid for a single typed ident:
6768
* 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
315316
def asSelect(implicit ctx: Context): Tree = {
316317
val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))
317318
if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos)
318-
typedSelect(tree, pt, qual1)
319+
val select = typedSelect(tree, pt, qual1)
320+
pt match {
321+
case _: FunProto | AssignProto => select
322+
case _ =>
323+
if (select.tpe eq TryDynamicCallType) typedDynamicSelect(tree, pt)
324+
else select
325+
}
319326
}
320327

321328
def asJavaSelectFromTypeTree(implicit ctx: Context): Tree = {
@@ -479,7 +486,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
479486
val appliedUpdate = cpy.Apply(fn)(wrappedUpdate, (args map untpd.TypedSplice) :+ tree.rhs)
480487
typed(appliedUpdate, pt)
481488
case lhs =>
482-
val lhsCore = typedUnadapted(lhs)
489+
val lhsCore = typedUnadapted(lhs, AssignProto)
483490
def lhs1 = typed(untpd.TypedSplice(lhsCore))
484491
def canAssign(sym: Symbol) = // allow assignments from the primary constructor to class fields
485492
sym.is(Mutable, butNot = Accessor) ||
@@ -507,6 +514,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
507514
case _ =>
508515
reassignmentToVal
509516
}
517+
case TryDynamicCallType =>
518+
tree match {
519+
case Assign(Select(qual, name), rhs) if !isDynamicMethod(name) =>
520+
typedDynamicAssign(qual, name, rhs, pt)
521+
case _ => reassignmentToVal
522+
}
510523
case tpe =>
511524
reassignmentToVal
512525
}
@@ -1091,7 +1104,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
10911104
.withType(dummy.nonMemberTermRef)
10921105
checkVariance(impl1)
10931106
if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.pos)
1094-
assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls)
1107+
val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls)
1108+
if (ctx.phase.isTyper && cdef1.tpe.derivesFrom(defn.DynamicClass) && !ctx.dynamicsEnabled) {
1109+
val isRequired = parents1.exists(_.tpe.isRef(defn.DynamicClass))
1110+
ctx.featureWarning(nme.dynamics.toString, "extension of type scala.Dynamic", isScala2Feature = true,
1111+
cls, isRequired, cdef.pos)
1112+
}
1113+
cdef1
10951114

10961115
// todo later: check that
10971116
// 1. If class is non-abstract, it is instantiatable:
@@ -1686,7 +1705,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
16861705
tree match {
16871706
case _: MemberDef | _: PackageDef | _: Import | _: WithoutTypeOrPos[_] => tree
16881707
case _ => tree.tpe.widen match {
1689-
case ErrorType =>
1708+
case _: ErrorType =>
16901709
tree
16911710
case ref: TermRef =>
16921711
pt match {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic
4+
5+
object DynamicTest {
6+
new Foo().bazApply() // error
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic
4+
5+
object DynamicTest {
6+
new Foo().bazApply("abc", 1) // error
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic
4+
5+
object DynamicTest {
6+
new Foo().bazApply _ // error // error
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def selectDynamic(name: String): String = ???
5+
def applyDynamicNamed(name: String)(args: Any*): String = ???
6+
}
7+
8+
object DynamicTest {
9+
new Foo().bazApply() // error
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def applyDynamic(name: String)(args: String*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
new Foo().bazApply(1, 2, 3) // error // error // error
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def applyDynamic(name: String)(args: String*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
def test: Int = new Foo().bazApply() // error
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.language.dynamics
2+
3+
class Foo extends scala.Dynamic {
4+
def applyDynamic(name: Int)(args: String*): String = ???
5+
}
6+
7+
object DynamicTest {
8+
def test: String = new Foo().bazApply() // error
9+
}

0 commit comments

Comments
 (0)