Skip to content

Commit fd1ce4e

Browse files
committed
Add scala.Dynamic language feature check.
1 parent e33e20a commit fd1ce4e

File tree

7 files changed

+55
-4
lines changed

7 files changed

+55
-4
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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
455455
*/
456456
def featureEnabled(owner: ClassSymbol, feature: TermName): Boolean = {
457457
def toPrefix(sym: Symbol): String =
458-
if (sym eq defn.LanguageModuleClass) "" else toPrefix(sym.owner) + sym.name + "."
458+
if (!sym.exists || (sym eq defn.LanguageModuleClass) || (sym eq defn.Scala2LanguageModuleRef)) ""
459+
else toPrefix(sym.owner) + sym.name + "."
459460
def featureName = toPrefix(owner) + feature
460461
def hasImport(implicit ctx: Context): Boolean = (
461462
ctx.importInfo != null
@@ -477,6 +478,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
477478
def scala2Mode =
478479
featureEnabled(defn.LanguageModuleClass, nme.Scala2)
479480

481+
def dynamicsEnabled =
482+
featureEnabled(defn.Scala2LanguageModuleClass, nme.dynamics)
483+
480484
def testScala2Mode(msg: String, pos: Position) = {
481485
if (scala2Mode) migrationWarning(msg, pos)
482486
scala2Mode

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/Typer.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
10951095
.withType(dummy.nonMemberTermRef)
10961096
checkVariance(impl1)
10971097
if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.pos)
1098-
assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls)
1098+
val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls)
1099+
if (ctx.phase.isTyper && cdef1.tpe <:< defn.DynamicType && !ctx.dynamicsEnabled) {
1100+
val isRequired = parents1.exists(_.tpe =:= defn.DynamicType)
1101+
ctx.featureWarning(nme.dynamics.toString, "extension of type scala.Dynamic", isScala2Feature = true,
1102+
cls, isRequired, cdef.pos)
1103+
}
1104+
cdef1
10991105

11001106
// todo later: check that
11011107
// 1. If class is non-abstract, it is instantiatable:

tests/neg/dynamicNoImport.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Foo extends scala.Dynamic // error
2+
trait Bar extends scala.Dynamic // error
3+
object Baz extends scala.Dynamic // error
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
package foo {
3+
import scala.language.dynamics
4+
class Bar extends scala.Dynamic
5+
}
6+
7+
class Baz extends foo.Bar

0 commit comments

Comments
 (0)