-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add scala.Dynamic support. #1291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good to have a motivation why this is needed. In fact I am not yet sure we want to suppress feature warnings per use site. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently it does not suppress the warning. It is only there to make sure the explanation of how to avoid it is only printed once per site. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, OK, I had missed that. |
||
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 | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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))) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import scala.language.dynamics | ||
|
||
class Foo extends scala.Dynamic | ||
|
||
object DynamicTest { | ||
new Foo().bazApply() // error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import scala.language.dynamics | ||
|
||
class Foo extends scala.Dynamic | ||
|
||
object DynamicTest { | ||
new Foo().bazApply("abc", 1) // error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import scala.language.dynamics | ||
|
||
class Foo extends scala.Dynamic | ||
|
||
object DynamicTest { | ||
new Foo().bazApply _ // error // error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import scala.language.dynamics | ||
|
||
class Foo extends scala.Dynamic { | ||
def selectDynamic(name: String): String = ??? | ||
def applyDynamicNamed(name: String)(args: Any*): String = ??? | ||
} | ||
|
||
object DynamicTest { | ||
new Foo().bazApply() // error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import scala.language.dynamics | ||
|
||
class Foo extends scala.Dynamic { | ||
def applyDynamic(name: String)(args: String*): String = ??? | ||
} | ||
|
||
object DynamicTest { | ||
new Foo().bazApply(1, 2, 3) // error // error // error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import scala.language.dynamics | ||
|
||
class Foo extends scala.Dynamic { | ||
def applyDynamic(name: String)(args: String*): String = ??? | ||
} | ||
|
||
object DynamicTest { | ||
def test: Int = new Foo().bazApply() // error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import scala.language.dynamics | ||
|
||
class Foo extends scala.Dynamic { | ||
def applyDynamic(name: Int)(args: String*): String = ??? | ||
} | ||
|
||
object DynamicTest { | ||
def test: String = new Foo().bazApply() // error | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if we try to map or fold over such a type? Did we think of that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that it behaves like
ErrorType
on any operation until it is caught and handled. I considered making it a subtype ofErrorType
but this seamed a bit dangerous and with this alternative there was only one place where I had to add it to acase
to do the same asErrorType
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, now that I think about it, it should be
object TryDynamicCallType extends ErrorType
.