Skip to content

Commit 7ec4fea

Browse files
committed
Emit feature warnings for implicit conversions.
We now emit feature warnings when encountering an implicit conversion def, just like Scala 2 does. In addition, we also emit a feature warning when an implicit conversion is used, unless the conversion is an implicit class, or otherwise co-defined with the type to which it converts, or the conversion is predefined. Predefined are - all members of `Predef`, - the `scala.reflect.Selectable.reflectiveSelect` conversion. We might need to extend this to more conversions. The motivation for going down this route is the following: Implicit conversions are easily the most mis-used feature in Scala. If I should venture a guess I'd say that more than 90% of the uses are mis-guided. The danger of implicit conversions is that they seem simple. It's easy to grok what they are and in particular new users see the possibilities before realizing the downsides. So far we put definitions of implicit conversions under a feature flag but this is not enough. Library writers tend not to be deterred by it, they just add the language import. (Or, worse, IntelliJ does it for them. Dear IntelliJ Scala team: please change things so that language imports are not inserted automatically, They are required for a reason, and automatic insertion does not help!) Either way, it's the users of a library with implicit conversions that need to be warned by the compiler, since it is them who will run into troubles if implicit conversions have surprising behavior. In what concerns the precise rules, I believe we will have to try this out in the wild for a while to see how it shapes up and whether we should tweak the rules.
1 parent ce7246f commit 7ec4fea

File tree

9 files changed

+90
-6
lines changed

9 files changed

+90
-6
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ object StdNames {
436436
val head: N = "head"
437437
val higherKinds: N = "higherKinds"
438438
val identity: N = "identity"
439+
val implicitConversions: N = "implicitConversions"
439440
val implicitly: N = "implicitly"
440441
val in: N = "in"
441442
val info: N = "info"
@@ -488,6 +489,7 @@ object StdNames {
488489
val productPrefix: N = "productPrefix"
489490
val readResolve: N = "readResolve"
490491
val reflect : N = "reflect"
492+
val reflectiveSelectable: N = "reflectiveSelectable"
491493
val reify : N = "reify"
492494
val rootMirror : N = "rootMirror"
493495
val run: N = "run"

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import core.Decorators.PhaseListDecorator
88
import collection.mutable
99
import java.lang.System.currentTimeMillis
1010
import core.Mode
11-
import dotty.tools.dotc.core.Symbols.Symbol
11+
import dotty.tools.dotc.core.Symbols.{Symbol, NoSymbol}
1212
import diagnostic.messages._
1313
import diagnostic._
1414
import Message._
@@ -39,7 +39,7 @@ trait Reporting { this: Context =>
3939
def reportWarning(warning: Warning): Unit =
4040
if (!this.settings.silentWarnings.value) {
4141
if (this.settings.XfatalWarnings.value) reporter.report(warning.toError)
42-
else reporter.report(warning)
42+
else reporter.report(warning)
4343
}
4444

4545
def deprecationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit =
@@ -170,7 +170,10 @@ abstract class Reporter extends interfaces.ReporterResult {
170170
def errorsReported = hasErrors
171171

172172
private[this] var reportedFeaturesUseSites = Set[Symbol]()
173-
def isReportedFeatureUseSite(featureTrait: Symbol): Boolean = reportedFeaturesUseSites.contains(featureTrait)
173+
174+
def isReportedFeatureUseSite(featureTrait: Symbol): Boolean =
175+
featureTrait.ne(NoSymbol) && reportedFeaturesUseSites.contains(featureTrait)
176+
174177
def reportNewFeatureUseSite(featureTrait: Symbol): Unit = reportedFeaturesUseSites += featureTrait
175178

176179
val unreportedWarnings = new mutable.HashMap[String, Int] {
@@ -227,7 +230,7 @@ abstract class Reporter extends interfaces.ReporterResult {
227230
ctx.mode.is(Mode.Printing)
228231

229232
/** Does this reporter contain not yet reported errors or warnings? */
230-
def hasPending: Boolean = false
233+
def hasPending(implicit ctx: Context): Boolean = false
231234

232235
/** If this reporter buffers messages, remove and return all buffered messages. */
233236
def removeBufferedMessages(implicit ctx: Context): List[MessageContainer] = Nil

compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ class StoreReporter(outer: Reporter) extends Reporter {
3030
infos += m
3131
}
3232

33-
override def hasPending: Boolean = infos != null && {
33+
override def hasPending(implicit ctx: Context): Boolean = infos != null && {
3434
infos exists {
3535
case _: Error => true
36+
case m: ConditionalWarning => m.enablingOption.value
3637
case _: Warning => true
3738
case _ => false
3839
}

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,49 @@ trait Checking {
576576
case _ =>
577577
}
578578

579+
/** If `sym` is an implicit conversion, check that implicit conversions are enabled.
580+
* @pre sym.is(Implicit)
581+
*/
582+
def checkImplicitConversionDefOK(sym: Symbol)(implicit ctx: Context): Unit = sym.info.stripPoly match {
583+
case mt @ MethodType(_ :: Nil)
584+
if !mt.isImplicitMethod && !sym.is(Synthetic) => // it's a conversion
585+
checkFeature(
586+
defn.LanguageModuleClass, nme.implicitConversions,
587+
i"Definition of implicit conversion $sym",
588+
ctx.owner.topLevelClass,
589+
sym.pos)
590+
case _ =>
591+
}
592+
593+
/** If `sym` is an implicit conversion, check that that implicit conversions are enabled, unless
594+
* - it is synthetic
595+
* - it is has the same owner as one of the classes it converts to (modulo companions)
596+
* - it is defined in Predef
597+
* - it is the scala.reflect.Selectable.reflectiveSelectable conversion
598+
*/
599+
def checkImplicitConversionUseOK(sym: Symbol, pos: Position)(implicit ctx: Context): Unit = {
600+
val conversionOK =
601+
!sym.exists ||
602+
sym.is(Synthetic) ||
603+
sym.info.finalResultType.classSymbols.exists(_.owner.isLinkedWith(sym.owner)) ||
604+
defn.isPredefClass(sym.owner) ||
605+
sym.name == nme.reflectiveSelectable && sym.maybeOwner.maybeOwner.maybeOwner == defn.ScalaPackageClass
606+
if (!conversionOK)
607+
checkFeature(defn.LanguageModuleClass, nme.implicitConversions,
608+
i"Use of implicit conversion ${sym.showLocated}", NoSymbol, pos)
609+
}
610+
611+
/** Issue a feature warning if feature is not enabled */
612+
def checkFeature(base: ClassSymbol,
613+
name: TermName,
614+
description: => String,
615+
featureUseSite: Symbol,
616+
pos: Position)(implicit ctx: Context): Unit =
617+
if (!ctx.featureEnabled(base, name))
618+
ctx.featureWarning(name.toString, description,
619+
isScala2Feature = base.isContainedIn(defn.LanguageModuleClass),
620+
featureUseSite, required = false, pos)
621+
579622
/** Check that `tp` is a class type and that any top-level type arguments in this type
580623
* are feasible, i.e. that their lower bound conforms to their upper bound. If a type
581624
* argument is infeasible, issue and error and continue with upper bound.
@@ -866,6 +909,8 @@ trait NoChecking extends ReChecking {
866909
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
867910
override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp
868911
override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = ()
912+
override def checkImplicitConversionDefOK(sym: Symbol)(implicit ctx: Context): Unit = ()
913+
override def checkImplicitConversionUseOK(sym: Symbol, pos: Position)(implicit ctx: Context): Unit = ()
869914
override def checkFeasibleParent(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp
870915
override def checkInlineConformant(tree: Tree, what: => String)(implicit ctx: Context) = ()
871916
override def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = ()
@@ -877,4 +922,5 @@ trait NoChecking extends ReChecking {
877922
override def checkCaseInheritance(parentSym: Symbol, caseCls: ClassSymbol, pos: Position)(implicit ctx: Context) = ()
878923
override def checkNoForwardDependencies(vparams: List[ValDef])(implicit ctx: Context): Unit = ()
879924
override def checkMembersOK(tp: Type, pos: Position)(implicit ctx: Context): Type = tp
925+
override def checkFeature(base: ClassSymbol, name: TermName, description: => String, featureUseSite: Symbol, pos: Position)(implicit ctx: Context): Unit = ()
880926
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,12 @@ object ProtoTypes {
250250
else {
251251
targ = typerFn(arg)
252252
if (!ctx.reporter.hasPending) {
253+
// There is something fishy going on. Run pos/t1756.scala with -feature.
254+
// You will get an orphan type parameter for CI when pickling.
255+
// The difference is that with -feature an `implicitConversions` warning
256+
// is issued, which means the next two statements are not executed.
257+
// It seems we are missing then some constraint instantiations because `evalState`
258+
// is not updated.
253259
myTypedArg = myTypedArg.updated(arg, targ)
254260
evalState = evalState.updated(arg, (ctx.typerState, ctx.typerState.constraint))
255261
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1413,7 +1413,10 @@ class Typer extends Namer
14131413
val tparams1 = tparams mapconserve (typed(_).asInstanceOf[TypeDef])
14141414
val vparamss1 = vparamss nestedMapconserve (typed(_).asInstanceOf[ValDef])
14151415
vparamss1.foreach(checkNoForwardDependencies)
1416-
if (sym is Implicit) checkImplicitParamsNotSingletons(vparamss1)
1416+
if (sym is Implicit) {
1417+
checkImplicitParamsNotSingletons(vparamss1)
1418+
checkImplicitConversionDefOK(sym)
1419+
}
14171420
val tpt1 = checkSimpleKinded(typedType(tpt))
14181421

14191422
var rhsCtx = ctx
@@ -2450,6 +2453,7 @@ class Typer extends Namer
24502453
if (ctx.mode.is(Mode.ImplicitsEnabled))
24512454
inferView(tree, pt) match {
24522455
case SearchSuccess(inferred, _, _) =>
2456+
checkImplicitConversionUseOK(inferred.symbol, tree.pos)
24532457
readapt(inferred)(ctx.retractMode(Mode.ImplicitsEnabled))
24542458
case failure: SearchFailure =>
24552459
if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous)

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ class CompilationTests extends ParallelTesting {
181181
compileFilesInDir("tests/neg-kind-polymorphism", defaultOptions and "-Ykind-polymorphism") +
182182
compileFilesInDir("tests/neg-custom-args/fatal-warnings", defaultOptions.and("-Xfatal-warnings")) +
183183
compileFilesInDir("tests/neg-custom-args/allow-double-bindings", allowDoubleBindings) +
184+
compileDir("tests/neg-custom-args/impl-conv", defaultOptions.and("-Xfatal-warnings", "-feature")) +
184185
compileFile("tests/neg-custom-args/i3246.scala", scala2Mode) +
185186
compileFile("tests/neg-custom-args/overrideClass.scala", scala2Mode) +
186187
compileFile("tests/neg-custom-args/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")) +
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package implConv
2+
3+
object A {
4+
5+
implicit def s2i(x: String): Int = Integer.parseInt(x) // error: feature
6+
7+
implicit class Foo(x: String) {
8+
def foo = x.length
9+
}
10+
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package implConv
2+
3+
object B {
4+
import A._
5+
6+
"".foo
7+
8+
val x: Int = "" // error: feature
9+
10+
}

0 commit comments

Comments
 (0)