Skip to content

Commit

Permalink
Warn if implicit should have explicit type
Browse files Browse the repository at this point in the history
  • Loading branch information
som-snytt committed Jul 23, 2022
1 parent 2bd3302 commit 9522547
Show file tree
Hide file tree
Showing 84 changed files with 456 additions and 82 deletions.
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/PipelineMain.scala
Expand Up @@ -77,7 +77,7 @@ class PipelineMainClass(argFiles: Seq[Path], pipelineSettings: PipelineMain.Pipe
}
}

implicit val executor = ExecutionContext.fromExecutor(new java.util.concurrent.ForkJoinPool(parallelism), t => handler.uncaughtException(Thread.currentThread(), t))
implicit val executor: ExecutionContext = ExecutionContext.fromExecutor(new java.util.concurrent.ForkJoinPool(parallelism), t => handler.uncaughtException(Thread.currentThread(), t))
def changeExtension(p: Path, newExtension: String): Path = {
val fileName = p.getFileName.toString
val changedFileName = fileName.lastIndexOf('.') match {
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
Expand Up @@ -1077,7 +1077,7 @@ trait ContextErrors extends splain.SplainErrors {

object InferErrorGen {

implicit val contextInferErrorGen = getContext
implicit val contextInferErrorGen: Context = getContext

object PolyAlternativeErrorKind extends Enumeration {
type ErrorType = Value
Expand Down Expand Up @@ -1282,7 +1282,7 @@ trait ContextErrors extends splain.SplainErrors {

object NamerErrorGen {

implicit val contextNamerErrorGen = context
implicit val contextNamerErrorGen: Context = context

object SymValidateErrors extends Enumeration {
val ImplicitConstr, ImplicitNotTermOrClass, ImplicitAtToplevel,
Expand Down
14 changes: 13 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Namers.scala
Expand Up @@ -1128,7 +1128,19 @@ trait Namers extends MethodSynthesis {
}
tree.tpt.defineType {
if (currentRun.isScala3 && !pt.isWildcard && pt != NoType && !pt.isErroneous) pt
else dropIllegalStarTypes(widenIfNecessary(tree.symbol, rhsTpe, pt))
else {
val res = dropIllegalStarTypes(widenIfNecessary(tree.symbol, rhsTpe, pt))
if (tree.symbol.isField)
tree.symbol.updateAttachment(FieldTypeInferred)
else if (tree.symbol.isImplicit && !tree.symbol.isLocalToBlock) {
val addendum = if (!res.isErroneous) s" (inferred $res)" else ""
if (currentRun.isScala3)
ErrorUtils.issueNormalTypeError(tree, s"Implicit definition must have explicit type$addendum")
else
context.warning(tree.pos, s"Implicit definition should have explicit type$addendum", WarningCategory.Other)
}
res
}
}.setPos(tree.pos.focus)
tree.tpt.tpe
}
Expand Down
21 changes: 15 additions & 6 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -2357,7 +2357,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}

val tparams1 = ddef.tparams mapConserve typedTypeDef
val tparams1 = ddef.tparams.mapConserve(typedTypeDef)
val vparamss1 = ddef.vparamss.mapConserve(_.mapConserve(typedValDef))

warnTypeParameterShadow(tparams1, meth)
Expand Down Expand Up @@ -2426,13 +2426,22 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (meth.isStructuralRefinementMember)
checkMethodStructuralCompatible(ddef)

if (meth.isImplicit && !meth.isSynthetic) meth.paramss match {
case List(param) :: _ if !param.isImplicit =>
checkFeature(ddef.pos, currentRun.runDefinitions.ImplicitConversionsFeature, meth.toString)
case _ =>
if (meth.isImplicit) {
if (!meth.isSynthetic) meth.paramss match {
case List(param) :: _ if !param.isImplicit =>
checkFeature(ddef.pos, currentRun.runDefinitions.ImplicitConversionsFeature, meth.toString)
case _ =>
}
if (meth.isGetter && !meth.isLocalToBlock && meth.accessed.hasAttachment[FieldTypeInferred.type]) {
meth.accessed.removeAttachment[FieldTypeInferred.type]
val addendum = if (!meth.accessed.tpe.resultType.isErroneous) s" (inferred ${meth.accessed.tpe.resultType})" else ""
if (currentRun.isScala3)
ErrorUtils.issueNormalTypeError(ddef, s"Implicit definition must have explicit type$addendum")
else
context.warning(ddef.pos, s"Implicit definition should have explicit type$addendum", WarningCategory.Other)
}
}
}

treeCopy.DefDef(ddef, typedMods, ddef.name, tparams1, vparamss1, tpt1, rhs1) setType NoType
} finally {
currentRun.profiler.afterTypedImplDef(meth)
Expand Down
3 changes: 2 additions & 1 deletion src/partest/scala/tools/partest/CompilerTest.scala
Expand Up @@ -12,6 +12,7 @@

package scala.tools.partest

import scala.language.implicitConversions
import scala.reflect.runtime.{universe => ru}
import scala.tools.nsc._

Expand Down Expand Up @@ -44,7 +45,7 @@ abstract class CompilerTest extends DirectTest {
if (sym eq NoSymbol) NoType
else appliedType(sym, compilerTypeFromTag(t))
}
implicit def mkMkType(sym: Symbol) = new MkType(sym)
implicit def mkMkType(sym: Symbol): MkType = new MkType(sym)

def allMembers(root: Symbol): List[Symbol] = {
def loop(seen: Set[Symbol], roots: List[Symbol]): List[Symbol] = {
Expand Down
2 changes: 1 addition & 1 deletion src/partest/scala/tools/partest/nest/Instance.scala
Expand Up @@ -28,5 +28,5 @@ trait Instance extends Spec {
def residualArgs = parsed.residualArgs // only args which were not options or args to options

type OptionMagic = Opt.Instance
protected implicit def optionMagicAdditions(name: String) = new Opt.Instance(programInfo, parsed, name)
protected implicit def optionMagicAdditions(name: String): Opt.Instance = new Opt.Instance(programInfo, parsed, name)
}
2 changes: 1 addition & 1 deletion src/partest/scala/tools/partest/nest/Reference.scala
Expand Up @@ -45,7 +45,7 @@ trait Reference extends Spec {
final def apply(args: String*): ThisCommandLine = creator(propertyArgs ++ args flatMap expandArg)

type OptionMagic = Opt.Reference
protected implicit def optionMagicAdditions(name: String) = new Opt.Reference(programInfo, options, name)
protected implicit def optionMagicAdditions(name: String): Opt.Reference = new Opt.Reference(programInfo, options, name)
}

object Reference {
Expand Down
3 changes: 1 addition & 2 deletions src/partest/scala/tools/partest/package.scala
Expand Up @@ -18,6 +18,7 @@ import java.util.concurrent.{Callable, ExecutorService}
import scala.concurrent.duration.Duration
import scala.io.Codec
import scala.jdk.CollectionConverters._
import scala.language.implicitConversions
import scala.tools.nsc.util.Exceptional

package object partest {
Expand Down Expand Up @@ -123,8 +124,6 @@ package object partest {
implicit def temporaryPath2File(x: Path): File = x.jfile
implicit def stringPathToJavaFile(path: String): File = new File(path)

implicit lazy val implicitConversions = scala.language.implicitConversions

def fileSeparator = java.io.File.separator
def pathSeparator = java.io.File.pathSeparator

Expand Down
2 changes: 1 addition & 1 deletion src/reflect/scala/reflect/api/Internals.scala
Expand Up @@ -1098,7 +1098,7 @@ trait Internals { self: Universe =>
trait CompatApi {
/** @see [[CompatToken]] */
@deprecated("compatibility with Scala 2.10 EOL", "2.13.0")
implicit val token = new CompatToken
implicit val token: CompatToken = new CompatToken

/** Scala 2.10 compatibility enrichments for BuildApi. */
@deprecated("compatibility with Scala 2.10 EOL", "2.13.0")
Expand Down
3 changes: 3 additions & 0 deletions src/reflect/scala/reflect/internal/StdAttachments.scala
Expand Up @@ -145,4 +145,7 @@ trait StdAttachments {

// Use of _root_ is in correct leading position of selection
case object RootSelection extends PlainAttachment

/** For `val i = 42`, marks field as inferred so accessor (getter) can warn if implicit. */
case object FieldTypeInferred extends PlainAttachment
}
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
Expand Up @@ -77,6 +77,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
this.ChangeOwnerAttachment
this.InterpolatedString
this.RootSelection
this.FieldTypeInferred
this.noPrint
this.typeDebug
// inaccessible: this.posAssigner
Expand Down
2 changes: 1 addition & 1 deletion src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala
Expand Up @@ -34,7 +34,7 @@ trait MemberHandlers {
val front = if (leadingPlus) "+ " else ""
front + (xs map string2codeQuoted mkString " + ")
}
private implicit def name2string(name: Name) = name.toString
private implicit def name2string(name: Name): String = name.toString

/** A traverser that finds all mentioned identifiers, i.e. things
* that need to be imported. It might return extra names.
Expand Down
4 changes: 2 additions & 2 deletions src/repl/scala/tools/nsc/interpreter/Power.scala
Expand Up @@ -264,7 +264,7 @@ class Power[ReplValsImpl <: ReplVals : ru.TypeTag: ClassTag](val intp: IMain, re

trait Implicits1 {
// fallback
implicit def replPrinting[T](x: T)(implicit pretty: Prettifier[T] = Prettifier.default[T]) =
implicit def replPrinting[T](x: T)(implicit pretty: Prettifier[T] = Prettifier.default[T]): PrettifierClass[T] =
new SinglePrettifierClass[T](x)
}
trait Implicits2 extends Implicits1 {
Expand All @@ -288,7 +288,7 @@ class Power[ReplValsImpl <: ReplVals : ru.TypeTag: ClassTag](val intp: IMain, re
implicit def replPrettifier[T] : Prettifier[T] = Prettifier.default[T]
implicit def replTypeApplication(sym: Symbol): RichSymbol = new RichSymbol(sym)

implicit def replInputStream(in: InputStream)(implicit codec: Codec) = new RichInputStream(in)
implicit def replInputStream(in: InputStream)(implicit codec: Codec): RichInputStream = new RichInputStream(in)
implicit def replEnhancedURLs(url: URL)(implicit codec: Codec): RichReplURL = new RichReplURL(url)(codec)
}

Expand Down
8 changes: 6 additions & 2 deletions src/repl/scala/tools/nsc/interpreter/ReplVals.scala
Expand Up @@ -47,13 +47,17 @@ class StdReplVals(final val intp: IMain) extends ReplVals {
import intp.global.Symbol

private val tagFn = ReplVals.mkCompilerTypeFromTag[intp.global.type](global)
implicit def mkCompilerTypeFromTag(sym: Symbol) = tagFn(sym)
implicit def mkCompilerTypeFromTag(sym: Symbol): ATFT[global.type] = tagFn(sym)
}

final lazy val replImplicits = new ReplImplicits

def typed[T <: analyzer.global.Tree](tree: T): T = typer.typed(tree).asInstanceOf[T]
}
trait ATFT[G <: Global] {
def apply[M](implicit m1: ru.TypeTag[M]): G#Type
def apply[M1, M2](implicit m1: ru.TypeTag[M1], m2: ru.TypeTag[M2]): G#Type
}

object ReplVals {
/** Latest attempt to work around the challenge of foo.global.Type
Expand All @@ -72,7 +76,7 @@ object ReplVals {
def compilerTypeFromTag(t: ApiUniverse # WeakTypeTag[_]): Global#Type =
definitions.compilerTypeFromTag(t)

class AppliedTypeFromTags(sym: Symbol) {
class AppliedTypeFromTags(sym: Symbol) extends ATFT[T] {
def apply[M](implicit m1: ru.TypeTag[M]): Type =
if (sym eq NoSymbol) NoType
else appliedType(sym, compilerTypeFromTag(m1).asInstanceOf[Type])
Expand Down
2 changes: 1 addition & 1 deletion src/scaladoc/scala/tools/nsc/doc/Uncompilable.scala
Expand Up @@ -29,7 +29,7 @@ trait Uncompilable {
import global.definitions.{ AnyRefClass, AnyRefTpe }
import global.rootMirror.RootClass

private implicit def translateName(name: Global#Name) =
private implicit def translateName(name: Global#Name): global.Name =
if (name.isTypeName) newTypeName("" + name) else newTermName("" + name)

val WaitNames = List("scala", "AnyRef", "wait")
Expand Down
Expand Up @@ -114,7 +114,7 @@ case class ScalaSig(majorVersion: Int, minorVersion: Int, table: Seq[Int ~ ByteC

def parseEntry(index: Int) = applyRule(ScalaSigParsers.parseEntry(ScalaSigEntryParsers.entry)(index))

implicit def applyRule[A](parser: ScalaSigParsers.Parser[A]) = ScalaSigParsers.expect(parser)(this)
implicit def applyRule[A](parser: ScalaSigParsers.Parser[A]): A = ScalaSigParsers.expect(parser)(this)

override def toString = "ScalaSig version " + majorVersion + "." + minorVersion + {
for (i <- 0 until table.size) yield "" + i + ":\t" + parseEntry(i) // + "\n\t" + getEntry(i)
Expand Down Expand Up @@ -165,7 +165,8 @@ object ScalaSigEntryParsers extends RulesWithState with MemoisableRules {

def parseEntry[A](parser: EntryParser[A])(index: Int) = (toEntry(index) -~ parser)

implicit def entryType(code: Int) = key filter (_ == code)
type R = scala.tools.scalap.scalax.rules.Rule[ScalaSigEntryParsers.S, ScalaSigEntryParsers.S, Int, Nothing]
implicit def entryType(code: Int): R = key.filter(_ == code)

val index = read(_.index)
val key = read(_.entryType)
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/toughtype.scala
Expand Up @@ -199,7 +199,7 @@ package scala.async.run.toughtype {
}

object FunDep {
implicit def `Something to do with List`[W, S, R](implicit funDep: FunDep[W, S, R]) =
implicit def `Something to do with List`[W, S, R](implicit funDep: FunDep[W, S, R]): FunDep[W,List[S],W] =
new FunDep[W, List[S], W] {
def method(w: W, l: List[S]) = async {
val it = l.iterator
Expand Down
14 changes: 9 additions & 5 deletions test/files/jvm/future-spec/main.scala
Expand Up @@ -17,9 +17,10 @@ object Test {
}

trait Features {
implicit def implicitously = scala.language.implicitConversions
implicit def reflectively = scala.language.reflectiveCalls
implicit def postulously = scala.language.postfixOps
import languageFeature._
implicit def implicitously: implicitConversions = scala.language.implicitConversions
implicit def reflectively: reflectiveCalls = scala.language.reflectiveCalls
implicit def postulously: postfixOps = scala.language.postfixOps
}


Expand All @@ -40,7 +41,9 @@ trait MinimalScalaTest extends Output with Features with Vigil {
if (throwables.nonEmpty) println(buffer.toString)
}

implicit def stringops(s: String) = new {
type Ops = AnyRef{def should[U](snippets: => U): U; def in[U](snippet: => U): scala.collection.mutable.IndexedSeq[_ >: Char with Throwable] with scala.collection.mutable.AbstractSeq[_ >: Char with Throwable] with scala.collection.mutable.Growable[Char with Throwable] with java.io.Serializable}

implicit def stringops(s: String): Ops = new {

def should[U](snippets: => U) = {
bufferPrintln(s + " should:")
Expand All @@ -62,7 +65,8 @@ trait MinimalScalaTest extends Output with Features with Vigil {

}

implicit def objectops(obj: Any) = new {
type OOps = AnyRef{def mustBe(other: Any): Unit; def mustEqual(other: Any): Unit}
implicit def objectops(obj: Any): OOps = new {

def mustBe(other: Any) = assert(obj == other, s"$obj is not $other")
def mustEqual(other: Any) = mustBe(other)
Expand Down
2 changes: 2 additions & 0 deletions test/files/jvm/interpreter.check
Expand Up @@ -87,6 +87,8 @@ scala> case class Bar(n: Int)
class Bar

scala> implicit def foo2bar(foo: Foo) = Bar(foo.n)
^
warning: Implicit definition should have explicit type (inferred Bar)
warning: 1 feature warning; for details, enable `:setting -feature` or `:replay -feature`
def foo2bar(foo: Foo): Bar

Expand Down
8 changes: 4 additions & 4 deletions test/files/neg/implicit-log.scala
Expand Up @@ -37,8 +37,8 @@ object Test1579 {
class Query[E](val value: E)
class Invoker(q: Any) { val foo = null }

implicit def unwrap[C](q: Query[C]) = q.value
implicit def invoker(q: Query[Column]) = new Invoker(q)
implicit def unwrap[C](q: Query[C]): C = q.value
implicit def invoker(q: Query[Column]): Invoker = new Invoker(q)

val q = new Query(new Column)
q.foo
Expand All @@ -50,9 +50,9 @@ object Test1625 {
def unwrap() = x
}

implicit def byName[A](x: => A) = new Wrapped(x)
implicit def byName[A](x: => A): Wrapped = new Wrapped(x)

implicit def byVal[A](x: A) = x
implicit def byVal[A](x: A): A = x

def main(args: Array[String]) = {

Expand Down
7 changes: 7 additions & 0 deletions test/files/neg/implicits.check
Expand Up @@ -16,4 +16,11 @@ implicits.scala:47: error: type mismatch;
implicits.scala:59: error: could not find implicit value for parameter x: Nothing
foo {
^
implicits.scala:34: warning: Implicit definition should have explicit type (inferred T)
implicit def select[T](t: HSome[T,_]) = t.head
^
implicits.scala:35: warning: Implicit definition should have explicit type (inferred L)
implicit def selectTail[L](t: HSome[_,L]) = t.tail
^
2 warnings
4 errors
4 changes: 4 additions & 0 deletions test/files/neg/override-final-implicit.check
@@ -1,5 +1,9 @@
override-final-implicit.scala:6: warning: Implicit definition should have explicit type (inferred Test.this.FooExtender)
override implicit def FooExtender(foo: String) = super.FooExtender(foo)
^
override-final-implicit.scala:6: error: cannot override final member:
final implicit def FooExtender(foo: String): Test.this.FooExtender (defined in class Implicits)
override implicit def FooExtender(foo: String) = super.FooExtender(foo)
^
1 warning
1 error
4 changes: 4 additions & 0 deletions test/files/neg/private-implicit-class.check
@@ -1,4 +1,8 @@
private-implicit-class.scala:6: error: method BarExtender in class ImplicitsPrivate cannot be accessed as a member of ImplicitsPrivate from class TestPrivate
override implicit def BarExtender(bar: Int) = super.BarExtender(bar) // error
^
private-implicit-class.scala:6: warning: Implicit definition should have explicit type
override implicit def BarExtender(bar: Int) = super.BarExtender(bar) // error
^
1 warning
1 error
4 changes: 4 additions & 0 deletions test/files/neg/t2206.check
Expand Up @@ -2,4 +2,8 @@ t2206.scala:10: error: value f is not a member of o.A
Note: implicit method ax is not applicable here because it comes after the application point and it lacks an explicit result type
a.f()
^
t2206.scala:13: warning: Implicit definition should have explicit type (inferred o.AX)
implicit def ax(a: A) = new AX
^
1 warning
1 error
4 changes: 4 additions & 0 deletions test/files/neg/t2421b.check
@@ -1,4 +1,8 @@
t2421b.scala:12: error: could not find implicit value for parameter aa: Test.F[Test.A]
f
^
t2421b.scala:10: warning: Implicit definition should have explicit type (inferred Test.F[X])
implicit def b[X <: B] = new F[X]()
^
1 warning
1 error
4 changes: 4 additions & 0 deletions test/files/neg/t3006.check
Expand Up @@ -3,4 +3,8 @@ t3006.scala:8: error: type mismatch;
required: Int
println(A(3) + "H")
^
t3006.scala:6: warning: Implicit definition should have explicit type (inferred Test.Foo)
implicit def aToFoo(x: A) = new Foo(x);
^
1 warning
1 error
4 changes: 4 additions & 0 deletions test/files/neg/t3346c.check
Expand Up @@ -2,4 +2,8 @@ t3346c.scala:60: error: value bar is not a member of Either[Int,String]
did you mean map?
eii.bar
^
t3346c.scala:35: warning: Implicit definition should have explicit type (inferred Test.TCValue[M,A]{implicit val M: Test.TC[M]; val self: M[A]})
implicit def ToTCValue[M[_], A](ma: M[A])(implicit M0: TC[M]) = new TCValue[M, A] {
^
1 warning
1 error

0 comments on commit 9522547

Please sign in to comment.