Skip to content

Add an -Yimplicit-to-given flag for rewrites to easily test changes in the ecosystem #22580

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ private sealed trait YSettings:

val Yinstrument: Setting[Boolean] = BooleanSetting(ForkSetting, "Yinstrument", "Add instrumentation code that counts allocations and closure creations.")
val YinstrumentDefs: Setting[Boolean] = BooleanSetting(ForkSetting, "Yinstrument-defs", "Add instrumentation code that counts method calls; needs -Yinstrument to be set, too.")
val YimplicitToGiven: Setting[Boolean] = BooleanSetting(ForkSetting, "Yimplicit-to-given", "Allows to rewrite the implicit keywords to their scala-3 given counterparts. Does not adjust imports. Use in conjunction with --rewrite.")

// Deprecated: lifted from -Y to -X
@deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0")
Expand Down
15 changes: 12 additions & 3 deletions compiler/src/dotty/tools/dotc/core/NamerOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import ContextOps.enter
import TypeApplications.EtaExpansion
import collection.mutable
import config.Printers.typr
import rewrites.Rewrites.patch
import util.Spans.Span

/** Operations that are shared between Namer and TreeUnpickler */
object NamerOps:
Expand Down Expand Up @@ -59,12 +61,19 @@ object NamerOps:
* This is done by adding a () in front of a leading old style implicit parameter,
* or by adding a () as last -- or only -- parameter list if the constructor has
* only using clauses as parameters.
*
* implicitRewritePosition, if included, will point to where `()` should be added if rewriting
* with -Yimplicit-to-given
*/
def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean)(using Context): List[List[Symbol]] =
def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean, implicitRewritePosition: Option[Span] = None)(using Context): List[List[Symbol]] =
if !isConstructor then paramss
else paramss match
case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor)
case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) => Nil :: paramss
case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor, implicitRewritePosition)
case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) =>
implicitRewritePosition match
case Some(position) if ctx.settings.YimplicitToGiven.value => patch(position, "()")
case _ => ()
Nil :: paramss
case _ =>
if paramss.forall {
case TermSymbols(vparams) => vparams.nonEmpty && vparams.head.is(Given)
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3579,7 +3579,11 @@ object Parsers {

def paramMods() =
if in.token == IMPLICIT then
addParamMod(() => Mod.Implicit())
addParamMod(() =>
if ctx.settings.YimplicitToGiven.value then
patch(Span(in.lastOffset - 8, in.lastOffset), "using")
Mod.Implicit()
)
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
68 changes: 63 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Migrations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,7 @@ trait Migrations:
if tp.companion == ImplicitMethodType && pt.applyKind != ApplyKind.Using && pt.args.nonEmpty then
// The application can only be rewritten if it uses parentheses syntax.
// See issue #22927 and related tests.
val hasParentheses =
ctx.source.content
.slice(tree.span.end, pt.args.head.span.start)
.exists(_ == '(')
val hasParentheses = checkParentheses(tree, pt)
val rewriteMsg =
if hasParentheses then
Message.rewriteNotice("This code", mversion.patchFrom)
Expand All @@ -148,7 +145,68 @@ trait Migrations:
|""",
pt.args.head.srcPos, mversion)
if hasParentheses && mversion.needsPatch then
patch(Span(pt.args.head.span.start), "using ")
patchImplicitParams(tree, pt)
end implicitParams

private def checkParentheses(tree: Tree, pt: FunProto)(using Context): Boolean =
ctx.source.content
.slice(tree.span.end, pt.args.head.span.start)
.exists(_ == '(')

private def patchImplicitParams(tree: Tree, pt: FunProto)(using Context): Unit =
patch(Span(pt.args.head.span.start), "using ")

object ImplicitToGiven:
def valDef(vdef: ValDef)(using Context): Unit =
if ctx.settings.YimplicitToGiven.value
&& vdef.symbol.is(Implicit)
&& !vdef.symbol.isParamOrAccessor
then
val implicitSpan =
vdef.mods.mods.collectFirst {
case mod: untpd.Mod.Implicit => mod.span
}.get
patch(
Span(implicitSpan.start, implicitSpan.end + 1),
""
)
patch(
Span(vdef.mods.mods.last.span.end + 1, vdef.namePos.span.start), "given "
)

def defDef(ddef: DefDef)(using Context): Unit =
if
ctx.settings.YimplicitToGiven.value
&& ddef.symbol.is(Implicit)
&& !ddef.symbol.isParamOrAccessor
&& !ddef.symbol.isOldStyleImplicitConversion()
then
val implicitSpan =
ddef.mods.mods.collectFirst {
case mod: untpd.Mod.Implicit => mod.span
}.get
patch(
Span(implicitSpan.start, implicitSpan.end + 1), ""
)
patch(
Span(ddef.mods.mods.last.span.end + 1, ddef.namePos.span.start), "given "
)
ddef.tpt match
case refinedType: untpd.RefinedTypeTree =>
patch(refinedType.span.startPos, "(")
patch(refinedType.span.endPos, ")")
case _ =>

def implicitParams(tree: Tree, tp: MethodOrPoly, pt: FunProto)(using Context): Unit =
if
ctx.settings.YimplicitToGiven.value
&& !mv.ExplicitContextBoundArgument.needsPatch // let's not needlessly repeat the patch
&& tp.companion == ImplicitMethodType
&& pt.applyKind != ApplyKind.Using
&& pt.args.nonEmpty
&& checkParentheses(tree, pt)
then
patchImplicitParams(tree, pt)


end Migrations
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,7 @@ class Namer { typer: Typer =>
if completedTypeParams.forall(_.isType) then
completer.setCompletedTypeParams(completedTypeParams.asInstanceOf[List[TypeSymbol]])
completeTrailingParamss(ddef, sym, indexingCtor = false)
val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor)
val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor, Some(ddef.nameSpan.startPos))
sym.setParamss(paramSymss)

def wrapMethType(restpe: Type): Type =
Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import transform.CheckUnused.OriginalName

import scala.annotation.{unchecked as _, *}
import dotty.tools.dotc.util.chaining.*
import dotty.tools.dotc.ast.untpd.Mod

object Typer {

Expand Down Expand Up @@ -2883,6 +2884,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
postProcessInfo(vdef1, sym)
vdef1.setDefTree

migrate(ImplicitToGiven.valDef(vdef1))

val nnInfo = rhs1.notNullInfo
vdef1.withNotNullInfo(if sym.is(Lazy) then nnInfo.retractedInfo else nnInfo)
}
Expand Down Expand Up @@ -2995,6 +2999,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer

val ddef2 = assignType(cpy.DefDef(ddef)(name, paramss1, tpt1, rhs1), sym)

migrate(ImplicitToGiven.defDef(ddef2))

postProcessInfo(ddef2, sym)
//todo: make sure dependent method types do not depend on implicits or by-name params
}
Expand Down Expand Up @@ -4192,6 +4198,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case wtp: MethodOrPoly =>
def methodStr = methPart(tree).symbol.showLocated
if matchingApply(wtp, pt) then
migrate(ImplicitToGiven.implicitParams(tree, wtp, pt))
migrate(contextBoundParams(tree, wtp, pt))
migrate(implicitParams(tree, wtp, pt))
if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked)
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class CompilationTests {
compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")),
compileFile("tests/rewrites/i22731.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")),
compileFile("tests/rewrites/i22731b.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")),
compileFile("tests/rewrites/implicit-to-given.scala", defaultOptions.and("-rewrite", "-Yimplicit-to-given"))
).checkRewrites()
}

Expand Down
23 changes: 23 additions & 0 deletions tests/rewrites/implicit-to-given.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
object Def:
trait A
trait B
implicit def conv1(a: A): B = ??? // should not change
given conv2: A => B = ???
final given nonConv: A = ???
given conv3: A => B = ???
final given nonConv2: A = ???

implicit class Extension(a: Int): // should not change
def addedMethod(): A = ???
implicit class ExtensionWithImplicit(t: String)(using a: Int):
def addedMethod(): String = ???
class NoNonimplicitParams()(using a: Int)

def applicationTest(using a: Int): Unit = ???
val application = applicationTest(using 0)
val implicitArg: Int => Unit = (implicit a => applicationTest) // should not change

given refined(): (A {type B = Int}) = ???

class EmptyParamListClass()(using a: Int)
def emptyParamListTest() = new EmptyParamListClass()(using 0)
23 changes: 23 additions & 0 deletions tests/rewrites/implicit-to-given.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
object Def:
trait A
trait B
implicit def conv1(a: A): B = ??? // should not change
implicit def conv2: A => B = ???
final implicit def nonConv: A = ???
implicit val conv3: A => B = ???
final implicit val nonConv2: A = ???

implicit class Extension(a: Int): // should not change
def addedMethod(): A = ???
implicit class ExtensionWithImplicit(t: String)(implicit a: Int):
def addedMethod(): String = ???
class NoNonimplicitParams(implicit a: Int)

def applicationTest(implicit a: Int): Unit = ???
val application = applicationTest(0)
val implicitArg: Int => Unit = (implicit a => applicationTest) // should not change

implicit def refined(): A {type B = Int} = ???

class EmptyParamListClass(implicit a: Int)
def emptyParamListTest() = new EmptyParamListClass()(0)