Skip to content

Disallow Scala 2 implicits under -source future #23472

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

Merged
merged 1 commit into from
Jul 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion)
case XmlLiteral extends MigrationVersion(future, future)
case GivenSyntax extends MigrationVersion(future, never)
case ImplicitParamsWithoutUsing extends MigrationVersion(`3.7`, future)
case Scala2Implicits extends MigrationVersion(future, future)

require(warnFrom.ordinal <= errorFrom.ordinal)

Expand Down
12 changes: 12 additions & 0 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2377,6 +2377,9 @@ object Parsers {
val start = in.offset
in.token match
case IMPLICIT =>
report.errorOrMigrationWarning(
em"`implicit` lambdas are no longer supported, use a lambda with `?=>` instead",
in.sourcePos(), MigrationVersion.Scala2Implicits)
closure(start, location, modifiers(BitSet(IMPLICIT)))
case LBRACKET =>
val start = in.offset
Expand Down Expand Up @@ -3557,11 +3560,17 @@ object Parsers {

def paramMods() =
if in.token == IMPLICIT then
report.errorOrMigrationWarning(
em"`implicit` parameters are no longer supported, use a `using` clause instead${rewriteNotice(`future-migration`)}",
in.sourcePos(), MigrationVersion.Scala2Implicits)
val startImplicit = in.offset
addParamMod(() =>
if ctx.settings.YimplicitToGiven.value then
patch(Span(in.lastOffset - 8, in.lastOffset), "using")
Mod.Implicit()
)
if MigrationVersion.Scala2Implicits.needsPatch then
patch(source, Span(startImplicit, in.lastOffset), "using")
else if isIdent(nme.using) then
if initialMods.is(Given) then
syntaxError(em"`using` is already implied here, should not be given explicitly", in.offset)
Expand Down Expand Up @@ -4769,6 +4778,9 @@ object Parsers {
else if (isExprIntro)
stats += expr(Location.InBlock)
else if in.token == IMPLICIT && !in.inModifierPosition() then
report.errorOrMigrationWarning(
em"`implicit` lambdas are no longer supported, use a lambda with `?=>` instead",
in.sourcePos(), MigrationVersion.Scala2Implicits)
stats += closure(in.offset, Location.InBlock, modifiers(BitSet(IMPLICIT)))
else if isIdent(nme.extension) && followingIsExtension() then
stats += extension()
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
protected def specialAnnotText(sym: ClassSymbol, tp: Type): Text =
Str(s"@${sym.name} ").provided(tp.hasAnnotation(sym))

protected def paramsText(lam: LambdaType): Text = {
def paramsText(lam: LambdaType): Text = {
def paramText(ref: ParamRef) =
val erased = ref.underlying.hasAnnotation(defn.ErasedParamAnnot)
keywordText("erased ").provided(erased)
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/Printer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package printing

import core.*
import Texts.*, ast.Trees.*
import Types.{Type, SingletonType, LambdaParam, NamedType, RefinedType},
import Types.{Type, SingletonType, LambdaParam, LambdaType, NamedType, RefinedType},
Symbols.Symbol, Scopes.Scope, Constants.Constant,
Names.Name, Denotations._, Annotations.Annotation, Contexts.Context
import typer.Implicits.*
Expand Down Expand Up @@ -147,6 +147,9 @@ abstract class Printer {
/** Textual representation of lambda param */
def toText(tree: LambdaParam): Text

/** textual representation of parameters of function type */
def paramsText(lam: LambdaType): Text

/** Textual representation of all symbols in given list,
* using `dclText` for displaying each.
*/
Expand Down
78 changes: 76 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,80 @@ object Checking {
}
}

def checkScala2Implicit(sym: Symbol)(using Context): Unit =
def migration(msg: Message) =
report.errorOrMigrationWarning(msg, sym.srcPos, MigrationVersion.Scala2Implicits)
def info = sym match
case sym: ClassSymbol => sym.primaryConstructor.info
case _ => sym.info
def paramName = info.firstParamNames match
case pname :: _ => pname.show
case _ => "x"
def paramTypeStr = info.firstParamTypes match
case pinfo :: _ => pinfo.show
case _ => "T"
def toFunctionStr(info: Type): String = info match
case ExprType(resType) =>
i"() => $resType"
case info: MethodType =>
i"(${ctx.printer.paramsText(info).mkString()}) => ${toFunctionStr(info.resType)}"
case info: PolyType =>
i"[${ctx.printer.paramsText(info).mkString()}] => ${toFunctionStr(info.resType)}"
case _ =>
info.show

if sym.isClass then
migration(
em"""`implicit` classes are no longer supported. They can usually be replaced
|by extension methods. Example:
|
| extension ($paramName: $paramTypeStr)
| // class methods go here, replace `this` by `$paramName`
|
|Alternatively, convert to a regular class and define
|a given `Conversion` instance into that class. Example:
|
| class ${sym.name} ...
| given Conversion[$paramTypeStr, ${sym.name}] = ${sym.name}($paramName)
|
|""")
else if sym.isOldStyleImplicitConversion(directOnly = true) then
migration(
em"""`implicit` conversion methods are no longer supported. They can usually be
|replaced by given instances of class `Conversion`. Example:
|
| given Conversion[$paramTypeStr, ${sym.info.finalResultType}] = $paramName => ...
|
|""")
else if sym.is(Method) then
if !sym.isOldStyleImplicitConversion(forImplicitClassOnly = true) then
migration(
em"""`implicit` defs are no longer supported, use a `given` clause instead. Example:
|
| given ${sym.name}: ${toFunctionStr(sym.info)} = ...
|
|""")
else if sym.isTerm && !sym.isOneOf(TermParamOrAccessor) then
def note =
if sym.is(Lazy) then ""
else
i"""
|
|Note: given clauses are evaluated lazily unless the right hand side is
|a simple reference. If eager evaluation of the value's right hand side
|is important, you can define a regular val and a given instance like this:
|
| val ${sym.name} = ...
| given ${sym.info} = ${sym.name}"""

migration(
em"""`implicit` vals are no longer supported, use a `given` clause instead. Example:
|
| given ${sym.name}: ${sym.info} = ...$note
|
|""")
end checkScala2Implicit

/** Check that symbol's definition is well-formed. */
def checkWellFormed(sym: Symbol)(using Context): Unit = {
def fail(msg: Message) = report.error(msg, sym.srcPos)
Expand All @@ -537,11 +611,11 @@ object Checking {
fail(ParamsNoInline(sym.owner))
if sym.isInlineMethod && !sym.is(Deferred) && sym.allOverriddenSymbols.nonEmpty then
checkInlineOverrideParameters(sym)
if (sym.is(Implicit)) {
if sym.is(Implicit) then
assert(!sym.owner.is(Package), s"top-level implicit $sym should be wrapped by a package after typer")
if sym.isType && (!sym.isClass || sym.is(Trait)) then
fail(TypesAndTraitsCantBeImplicit())
}
else checkScala2Implicit(sym)
if sym.is(Transparent) then
if sym.isType then
if !sym.isExtensibleClass then fail(em"`transparent` can only be used for extensible classes and traits")
Expand Down
5 changes: 5 additions & 0 deletions tests/neg/i22440.check
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
-- Error: tests/neg/i22440.scala:3:8 -----------------------------------------------------------------------------------
3 |def foo(implicit x: Int) = x // error
| ^
| `implicit` parameters are no longer supported, use a `using` clause instead
| This construct can be rewritten automatically under -rewrite -source future-migration.
-- Error: tests/neg/i22440.scala:4:12 ----------------------------------------------------------------------------------
4 |val _ = foo(1) // error
| ^
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i22440.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//> using options -source future

def foo(implicit x: Int) = x
def foo(implicit x: Int) = x // error
val _ = foo(1) // error
2 changes: 1 addition & 1 deletion tests/neg/i8896-b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object Example {
given Foo[Int]()

def foo0[A: Foo]: A => A = identity
def foo1[A](implicit foo: Foo[A]): A => A = identity
def foo1[A](implicit foo: Foo[A]): A => A = identity // error
def foo2[A](using Foo[A]): A => A = identity

def test(): Unit = {
Expand Down
64 changes: 64 additions & 0 deletions tests/neg/implicit-migration.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- Error: tests/neg/implicit-migration.scala:16:21 ---------------------------------------------------------------------
16 | implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() // error // error
| ^
| `implicit` parameters are no longer supported, use a `using` clause instead
| This construct can be rewritten automatically under -rewrite -source future-migration.
-- Error: tests/neg/implicit-migration.scala:9:15 ----------------------------------------------------------------------
9 | implicit def convert(x: String): Int = x.length // error
| ^
| `implicit` conversion methods are no longer supported. They can usually be
| replaced by given instances of class `Conversion`. Example:
|
| given Conversion[String, Int] = x => ...
|
-- Error: tests/neg/implicit-migration.scala:11:15 ---------------------------------------------------------------------
11 | implicit val ob: Ord[Boolean] = Ord[Boolean]() // error
| ^
| `implicit` vals are no longer supported, use a `given` clause instead. Example:
|
| given ob: Ord[Boolean] = ...
|
| Note: given clauses are evaluated lazily unless the right hand side is
| a simple reference. If eager evaluation of the value's right hand side
| is important, you can define a regular val and a given instance like this:
|
| val ob = ...
| given Ord[Boolean] = ob
|
-- Error: tests/neg/implicit-migration.scala:12:20 ---------------------------------------------------------------------
12 | lazy implicit val oi: Ord[Int] = Ord[Int]() // error
| ^
| `implicit` vals are no longer supported, use a `given` clause instead. Example:
|
| given oi: Ord[Int] = ...
|
-- Error: tests/neg/implicit-migration.scala:14:15 ---------------------------------------------------------------------
14 | implicit def of: Ord[Float] = Ord[Float]() // error
| ^
| `implicit` defs are no longer supported, use a `given` clause instead. Example:
|
| given of: () => Ord[Float] = ...
|
-- Error: tests/neg/implicit-migration.scala:16:15 ---------------------------------------------------------------------
16 | implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() // error // error
| ^
| `implicit` defs are no longer supported, use a `given` clause instead. Example:
|
| given ol: [T] => (x: Ord[T]) => Ord[List[T]] = ...
|
-- Error: tests/neg/implicit-migration.scala:3:15 ----------------------------------------------------------------------
3 |implicit class C(x: String): // error
| ^
| `implicit` classes are no longer supported. They can usually be replaced
| by extension methods. Example:
|
| extension (x: String)
| // class methods go here, replace `this` by `x`
|
| Alternatively, convert to a regular class and define
| a given `Conversion` instance into that class. Example:
|
| class C ...
| given Conversion[String, C] = C(x)
|
there was 1 feature warning; re-run with -feature for details
17 changes: 17 additions & 0 deletions tests/neg/implicit-migration.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import language.future

implicit class C(x: String): // error
def l: Int = x.length

class Ord[T]

object Test:
implicit def convert(x: String): Int = x.length // error

implicit val ob: Ord[Boolean] = Ord[Boolean]() // error
lazy implicit val oi: Ord[Int] = Ord[Int]() // error

implicit def of: Ord[Float] = Ord[Float]() // error

implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() // error // error

3 changes: 1 addition & 2 deletions tests/pos/i3920.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ class SetFunctor(tracked val ord: Ordering) {
type Set = List[ord.T]
def empty: Set = Nil

implicit class helper(s: Set) {
extension (s: Set)
def add(x: ord.T): Set = x :: remove(x)
def remove(x: ord.T): Set = s.filter(e => ord.compare(x, e) != 0)
def member(x: ord.T): Boolean = s.exists(e => ord.compare(x, e) == 0)
}
}

object Test {
Expand Down
3 changes: 1 addition & 2 deletions tests/pos/infer-tracked-1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ class SetFunctor(val ord: Ordering) {
type Set = List[ord.T]
def empty: Set = Nil

implicit class helper(s: Set) {
extension (s: Set)
def add(x: ord.T): Set = x :: remove(x)
def remove(x: ord.T): Set = s.filter(e => ord.compare(x, e) != 0)
def member(x: ord.T): Boolean = s.exists(e => ord.compare(x, e) == 0)
}
}

object Test {
Expand Down
9 changes: 9 additions & 0 deletions tests/rewrites/implicit-rewrite.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//> using options source `future-migration` -rewrite

class Ord[T]

object Test:

implicit def ol[T](using x: Ord[T]): Ord[List[T]] = foo[T] // error // error

def foo[T](using x: Ord[T]): Ord[List[T]] = new Ord[List[T]]()
9 changes: 9 additions & 0 deletions tests/rewrites/implicit-rewrite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//> using options source `future-migration` -rewrite

class Ord[T]

object Test:

implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = foo[T]

def foo[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]()
18 changes: 13 additions & 5 deletions tests/warn/i9266.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
-- Migration Warning: tests/warn/i9266.scala:5:22 ----------------------------------------------------------------------
5 |def test = { implicit x: Int => x + x } // warn
| ^
| This syntax is no longer supported; parameter needs to be enclosed in (...)
| This construct can be rewritten automatically under -rewrite -source future-migration.
-- Migration Warning: tests/warn/i9266.scala:5:14 ----------------------------------------------------------------------
5 |def test1 = { implicit (x: Int) => x + x } // warn
| ^
| `implicit` lambdas are no longer supported, use a lambda with `?=>` instead
-- Migration Warning: tests/warn/i9266.scala:7:14 ----------------------------------------------------------------------
7 |def test2 = { implicit x: Int => x + x } // warn // warn
| ^
| `implicit` lambdas are no longer supported, use a lambda with `?=>` instead
-- Migration Warning: tests/warn/i9266.scala:7:23 ----------------------------------------------------------------------
7 |def test2 = { implicit x: Int => x + x } // warn // warn
| ^
| This syntax is no longer supported; parameter needs to be enclosed in (...)
| This construct can be rewritten automatically under -rewrite -source future-migration.
3 changes: 2 additions & 1 deletion tests/warn/i9266.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

import language.`future-migration`

def test = { implicit x: Int => x + x } // warn
def test1 = { implicit (x: Int) => x + x } // warn

def test2 = { implicit x: Int => x + x } // warn // warn
Loading