Skip to content

Commit 60864b3

Browse files
authored
Disallow Scala 2 implicits under -source future (#23472)
- Offer a rewrite for implicit parameters to using clauses - Provide hints in the error message on how to rewrite the rest
2 parents 377faec + 3c93562 commit 60864b3

16 files changed

+217
-16
lines changed

compiler/src/dotty/tools/dotc/config/MigrationVersion.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ enum MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion)
3333
case XmlLiteral extends MigrationVersion(future, future)
3434
case GivenSyntax extends MigrationVersion(future, never)
3535
case ImplicitParamsWithoutUsing extends MigrationVersion(`3.7`, future)
36+
case Scala2Implicits extends MigrationVersion(future, future)
3637

3738
require(warnFrom.ordinal <= errorFrom.ordinal)
3839

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,6 +2377,9 @@ object Parsers {
23772377
val start = in.offset
23782378
in.token match
23792379
case IMPLICIT =>
2380+
report.errorOrMigrationWarning(
2381+
em"`implicit` lambdas are no longer supported, use a lambda with `?=>` instead",
2382+
in.sourcePos(), MigrationVersion.Scala2Implicits)
23802383
closure(start, location, modifiers(BitSet(IMPLICIT)))
23812384
case LBRACKET =>
23822385
val start = in.offset
@@ -3557,11 +3560,17 @@ object Parsers {
35573560

35583561
def paramMods() =
35593562
if in.token == IMPLICIT then
3563+
report.errorOrMigrationWarning(
3564+
em"`implicit` parameters are no longer supported, use a `using` clause instead${rewriteNotice(`future-migration`)}",
3565+
in.sourcePos(), MigrationVersion.Scala2Implicits)
3566+
val startImplicit = in.offset
35603567
addParamMod(() =>
35613568
if ctx.settings.YimplicitToGiven.value then
35623569
patch(Span(in.lastOffset - 8, in.lastOffset), "using")
35633570
Mod.Implicit()
35643571
)
3572+
if MigrationVersion.Scala2Implicits.needsPatch then
3573+
patch(source, Span(startImplicit, in.lastOffset), "using")
35653574
else if isIdent(nme.using) then
35663575
if initialMods.is(Given) then
35673576
syntaxError(em"`using` is already implied here, should not be given explicitly", in.offset)
@@ -4769,6 +4778,9 @@ object Parsers {
47694778
else if (isExprIntro)
47704779
stats += expr(Location.InBlock)
47714780
else if in.token == IMPLICIT && !in.inModifierPosition() then
4781+
report.errorOrMigrationWarning(
4782+
em"`implicit` lambdas are no longer supported, use a lambda with `?=>` instead",
4783+
in.sourcePos(), MigrationVersion.Scala2Implicits)
47724784
stats += closure(in.offset, Location.InBlock, modifiers(BitSet(IMPLICIT)))
47734785
else if isIdent(nme.extension) && followingIsExtension() then
47744786
stats += extension()

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
382382
protected def specialAnnotText(sym: ClassSymbol, tp: Type): Text =
383383
Str(s"@${sym.name} ").provided(tp.hasAnnotation(sym))
384384

385-
protected def paramsText(lam: LambdaType): Text = {
385+
def paramsText(lam: LambdaType): Text = {
386386
def paramText(ref: ParamRef) =
387387
val erased = ref.underlying.hasAnnotation(defn.ErasedParamAnnot)
388388
keywordText("erased ").provided(erased)

compiler/src/dotty/tools/dotc/printing/Printer.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package printing
44

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

150+
/** textual representation of parameters of function type */
151+
def paramsText(lam: LambdaType): Text
152+
150153
/** Textual representation of all symbols in given list,
151154
* using `dclText` for displaying each.
152155
*/

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

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,80 @@ object Checking {
512512
}
513513
}
514514

515+
def checkScala2Implicit(sym: Symbol)(using Context): Unit =
516+
def migration(msg: Message) =
517+
report.errorOrMigrationWarning(msg, sym.srcPos, MigrationVersion.Scala2Implicits)
518+
def info = sym match
519+
case sym: ClassSymbol => sym.primaryConstructor.info
520+
case _ => sym.info
521+
def paramName = info.firstParamNames match
522+
case pname :: _ => pname.show
523+
case _ => "x"
524+
def paramTypeStr = info.firstParamTypes match
525+
case pinfo :: _ => pinfo.show
526+
case _ => "T"
527+
def toFunctionStr(info: Type): String = info match
528+
case ExprType(resType) =>
529+
i"() => $resType"
530+
case info: MethodType =>
531+
i"(${ctx.printer.paramsText(info).mkString()}) => ${toFunctionStr(info.resType)}"
532+
case info: PolyType =>
533+
i"[${ctx.printer.paramsText(info).mkString()}] => ${toFunctionStr(info.resType)}"
534+
case _ =>
535+
info.show
536+
537+
if sym.isClass then
538+
migration(
539+
em"""`implicit` classes are no longer supported. They can usually be replaced
540+
|by extension methods. Example:
541+
|
542+
| extension ($paramName: $paramTypeStr)
543+
| // class methods go here, replace `this` by `$paramName`
544+
|
545+
|Alternatively, convert to a regular class and define
546+
|a given `Conversion` instance into that class. Example:
547+
|
548+
| class ${sym.name} ...
549+
| given Conversion[$paramTypeStr, ${sym.name}] = ${sym.name}($paramName)
550+
|
551+
|""")
552+
else if sym.isOldStyleImplicitConversion(directOnly = true) then
553+
migration(
554+
em"""`implicit` conversion methods are no longer supported. They can usually be
555+
|replaced by given instances of class `Conversion`. Example:
556+
|
557+
| given Conversion[$paramTypeStr, ${sym.info.finalResultType}] = $paramName => ...
558+
|
559+
|""")
560+
else if sym.is(Method) then
561+
if !sym.isOldStyleImplicitConversion(forImplicitClassOnly = true) then
562+
migration(
563+
em"""`implicit` defs are no longer supported, use a `given` clause instead. Example:
564+
|
565+
| given ${sym.name}: ${toFunctionStr(sym.info)} = ...
566+
|
567+
|""")
568+
else if sym.isTerm && !sym.isOneOf(TermParamOrAccessor) then
569+
def note =
570+
if sym.is(Lazy) then ""
571+
else
572+
i"""
573+
|
574+
|Note: given clauses are evaluated lazily unless the right hand side is
575+
|a simple reference. If eager evaluation of the value's right hand side
576+
|is important, you can define a regular val and a given instance like this:
577+
|
578+
| val ${sym.name} = ...
579+
| given ${sym.info} = ${sym.name}"""
580+
581+
migration(
582+
em"""`implicit` vals are no longer supported, use a `given` clause instead. Example:
583+
|
584+
| given ${sym.name}: ${sym.info} = ...$note
585+
|
586+
|""")
587+
end checkScala2Implicit
588+
515589
/** Check that symbol's definition is well-formed. */
516590
def checkWellFormed(sym: Symbol)(using Context): Unit = {
517591
def fail(msg: Message) = report.error(msg, sym.srcPos)
@@ -537,11 +611,11 @@ object Checking {
537611
fail(ParamsNoInline(sym.owner))
538612
if sym.isInlineMethod && !sym.is(Deferred) && sym.allOverriddenSymbols.nonEmpty then
539613
checkInlineOverrideParameters(sym)
540-
if (sym.is(Implicit)) {
614+
if sym.is(Implicit) then
541615
assert(!sym.owner.is(Package), s"top-level implicit $sym should be wrapped by a package after typer")
542616
if sym.isType && (!sym.isClass || sym.is(Trait)) then
543617
fail(TypesAndTraitsCantBeImplicit())
544-
}
618+
else checkScala2Implicit(sym)
545619
if sym.is(Transparent) then
546620
if sym.isType then
547621
if !sym.isExtensibleClass then fail(em"`transparent` can only be used for extensible classes and traits")

tests/neg/i22440.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
-- Error: tests/neg/i22440.scala:3:8 -----------------------------------------------------------------------------------
2+
3 |def foo(implicit x: Int) = x // error
3+
| ^
4+
| `implicit` parameters are no longer supported, use a `using` clause instead
5+
| This construct can be rewritten automatically under -rewrite -source future-migration.
16
-- Error: tests/neg/i22440.scala:4:12 ----------------------------------------------------------------------------------
27
4 |val _ = foo(1) // error
38
| ^

tests/neg/i22440.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
//> using options -source future
22

3-
def foo(implicit x: Int) = x
3+
def foo(implicit x: Int) = x // error
44
val _ = foo(1) // error

tests/neg/i8896-b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object Example {
77
given Foo[Int]()
88

99
def foo0[A: Foo]: A => A = identity
10-
def foo1[A](implicit foo: Foo[A]): A => A = identity
10+
def foo1[A](implicit foo: Foo[A]): A => A = identity // error
1111
def foo2[A](using Foo[A]): A => A = identity
1212

1313
def test(): Unit = {

tests/neg/implicit-migration.check

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
-- Error: tests/neg/implicit-migration.scala:16:21 ---------------------------------------------------------------------
2+
16 | implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() // error // error
3+
| ^
4+
| `implicit` parameters are no longer supported, use a `using` clause instead
5+
| This construct can be rewritten automatically under -rewrite -source future-migration.
6+
-- Error: tests/neg/implicit-migration.scala:9:15 ----------------------------------------------------------------------
7+
9 | implicit def convert(x: String): Int = x.length // error
8+
| ^
9+
| `implicit` conversion methods are no longer supported. They can usually be
10+
| replaced by given instances of class `Conversion`. Example:
11+
|
12+
| given Conversion[String, Int] = x => ...
13+
|
14+
-- Error: tests/neg/implicit-migration.scala:11:15 ---------------------------------------------------------------------
15+
11 | implicit val ob: Ord[Boolean] = Ord[Boolean]() // error
16+
| ^
17+
| `implicit` vals are no longer supported, use a `given` clause instead. Example:
18+
|
19+
| given ob: Ord[Boolean] = ...
20+
|
21+
| Note: given clauses are evaluated lazily unless the right hand side is
22+
| a simple reference. If eager evaluation of the value's right hand side
23+
| is important, you can define a regular val and a given instance like this:
24+
|
25+
| val ob = ...
26+
| given Ord[Boolean] = ob
27+
|
28+
-- Error: tests/neg/implicit-migration.scala:12:20 ---------------------------------------------------------------------
29+
12 | lazy implicit val oi: Ord[Int] = Ord[Int]() // error
30+
| ^
31+
| `implicit` vals are no longer supported, use a `given` clause instead. Example:
32+
|
33+
| given oi: Ord[Int] = ...
34+
|
35+
-- Error: tests/neg/implicit-migration.scala:14:15 ---------------------------------------------------------------------
36+
14 | implicit def of: Ord[Float] = Ord[Float]() // error
37+
| ^
38+
| `implicit` defs are no longer supported, use a `given` clause instead. Example:
39+
|
40+
| given of: () => Ord[Float] = ...
41+
|
42+
-- Error: tests/neg/implicit-migration.scala:16:15 ---------------------------------------------------------------------
43+
16 | implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() // error // error
44+
| ^
45+
| `implicit` defs are no longer supported, use a `given` clause instead. Example:
46+
|
47+
| given ol: [T] => (x: Ord[T]) => Ord[List[T]] = ...
48+
|
49+
-- Error: tests/neg/implicit-migration.scala:3:15 ----------------------------------------------------------------------
50+
3 |implicit class C(x: String): // error
51+
| ^
52+
| `implicit` classes are no longer supported. They can usually be replaced
53+
| by extension methods. Example:
54+
|
55+
| extension (x: String)
56+
| // class methods go here, replace `this` by `x`
57+
|
58+
| Alternatively, convert to a regular class and define
59+
| a given `Conversion` instance into that class. Example:
60+
|
61+
| class C ...
62+
| given Conversion[String, C] = C(x)
63+
|
64+
there was 1 feature warning; re-run with -feature for details

tests/neg/implicit-migration.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import language.future
2+
3+
implicit class C(x: String): // error
4+
def l: Int = x.length
5+
6+
class Ord[T]
7+
8+
object Test:
9+
implicit def convert(x: String): Int = x.length // error
10+
11+
implicit val ob: Ord[Boolean] = Ord[Boolean]() // error
12+
lazy implicit val oi: Ord[Int] = Ord[Int]() // error
13+
14+
implicit def of: Ord[Float] = Ord[Float]() // error
15+
16+
implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() // error // error
17+

0 commit comments

Comments
 (0)