Skip to content

Commit 71a4ae8

Browse files
committed
Add -Yimplicit-as-given flag allowing rewriting implicits to givens
1 parent 05b102a commit 71a4ae8

File tree

11 files changed

+121
-12
lines changed

11 files changed

+121
-12
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ private sealed trait YSettings:
465465

466466
val Yinstrument: Setting[Boolean] = BooleanSetting(ForkSetting, "Yinstrument", "Add instrumentation code that counts allocations and closure creations.")
467467
val YinstrumentDefs: Setting[Boolean] = BooleanSetting(ForkSetting, "Yinstrument-defs", "Add instrumentation code that counts method calls; needs -Yinstrument to be set, too.")
468+
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.")
468469

469470
// Deprecated: lifted from -Y to -X
470471
@deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0")

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

+16-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import ContextOps.enter
88
import TypeApplications.EtaExpansion
99
import collection.mutable
1010
import config.Printers.typr
11+
import rewrites.Rewrites.patch
12+
import util.Spans.Span
1113

1214
/** Operations that are shared between Namer and TreeUnpickler */
1315
object NamerOps:
@@ -61,12 +63,19 @@ object NamerOps:
6163
* This is done by adding a () in front of a leading old style implicit parameter,
6264
* or by adding a () as last -- or only -- parameter list if the constructor has
6365
* only using clauses as parameters.
66+
*
67+
* implicitRewritePosition, if included, will point to where `()` should be added if rewriting
68+
* with -Yimplicit-to-given
6469
*/
65-
def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean)(using Context): List[List[Symbol]] =
70+
def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean, implicitRewritePosition: Option[Span] = None)(using Context): List[List[Symbol]] =
6671
if !isConstructor then paramss
6772
else paramss match
68-
case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor)
69-
case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) => Nil :: paramss
73+
case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor, implicitRewritePosition)
74+
case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) =>
75+
implicitRewritePosition match
76+
case Some(position) if ctx.settings.YimplicitToGiven.value => patch(position, "()")
77+
case _ => ()
78+
Nil :: paramss
7079
case _ =>
7180
if paramss.forall {
7281
case TermSymbols(vparams) => vparams.nonEmpty && vparams.head.is(Given)
@@ -81,10 +90,10 @@ object NamerOps:
8190
case Nil =>
8291
resultType
8392
case TermSymbols(params) :: paramss1 =>
84-
val (isContextual, isImplicit) =
85-
if params.isEmpty then (false, false)
86-
else (params.head.is(Given), params.head.is(Implicit))
87-
val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit)
93+
val (isContextual, isImplicit, implicitToGiven) =
94+
if params.isEmpty then (false, false, false)
95+
else (params.head.is(Given), params.head.is(Implicit), params.head.isImplicitRewrittenToGiven)
96+
val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit, implicitToGiven = implicitToGiven)
8897
if isJava then
8998
for param <- params do
9099
if param.info.isDirectRef(defn.ObjectClass) then param.info = defn.AnyType

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

+4
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,10 @@ object Symbols extends SymUtils {
404404
def paramVariance(using Context): Variance = denot.variance
405405
def paramRef(using Context): TypeRef = denot.typeRef
406406

407+
/** Was it an implicit, currently rewritten into a given with `-Yimplicit-to-given` */
408+
def isImplicitRewrittenToGiven(using Context): Boolean =
409+
ctx.settings.YimplicitToGiven.value && denot.flags.is(Implicit) && this.isDefinedInSource
410+
407411
/** Copy a symbol, overriding selective fields.
408412
* Note that `coord` and `compilationUnitInfo` will be set from the fields in `owner`, not
409413
* the fields in `sym`. */

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,9 @@ object Types extends TypeUtils {
478478
/** Is this a Method or PolyType which has implicit or contextual parameters? */
479479
def isImplicitMethod: Boolean = false
480480

481+
/** Is this method parameter list implicit, currently rewritten into a given with `-Yimplicit-to-given`? */
482+
def isImplicitMethodRewrittenToContextual: Boolean = false
483+
481484
/** Is this a Method or PolyType which has contextual parameters as first value parameter list? */
482485
def isContextualMethod: Boolean = false
483486

@@ -4112,13 +4115,16 @@ object Types extends TypeUtils {
41124115
def companion: MethodTypeCompanion
41134116

41144117
final override def isImplicitMethod: Boolean =
4115-
companion.eq(ImplicitMethodType) || isContextualMethod
4118+
companion.eq(ImplicitMethodType) || companion.eq(ImplicitRewrittenToContextualMethodType) || isContextualMethod
41164119
final override def hasErasedParams(using Context): Boolean =
41174120
paramInfos.exists(p => p.hasAnnotation(defn.ErasedParamAnnot))
41184121

41194122
final override def isContextualMethod: Boolean =
41204123
companion.eq(ContextualMethodType)
41214124

4125+
final override def isImplicitMethodRewrittenToContextual: Boolean =
4126+
companion.eq(ImplicitRewrittenToContextualMethodType)
4127+
41224128
def erasedParams(using Context): List[Boolean] =
41234129
paramInfos.map(p => p.hasAnnotation(defn.ErasedParamAnnot))
41244130

@@ -4250,14 +4256,16 @@ object Types extends TypeUtils {
42504256
}
42514257

42524258
object MethodType extends MethodTypeCompanion("MethodType") {
4253-
def companion(isContextual: Boolean = false, isImplicit: Boolean = false): MethodTypeCompanion =
4254-
if (isContextual) ContextualMethodType
4259+
def companion(isContextual: Boolean = false, isImplicit: Boolean = false, implicitToGiven: Boolean = false): MethodTypeCompanion =
4260+
if (implicitToGiven) ImplicitRewrittenToContextualMethodType
4261+
else if (isContextual) ContextualMethodType
42554262
else if (isImplicit) ImplicitMethodType
42564263
else MethodType
42574264
}
42584265

42594266
object ContextualMethodType extends MethodTypeCompanion("ContextualMethodType")
42604267
object ImplicitMethodType extends MethodTypeCompanion("ImplicitMethodType")
4268+
object ImplicitRewrittenToContextualMethodType extends MethodTypeCompanion("ImplicitRewrittenToContextualMethodType")
42614269

42624270
/** A ternary extractor for MethodType */
42634271
object MethodTpe {

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -3588,7 +3588,11 @@ object Parsers {
35883588

35893589
def paramMods() =
35903590
if in.token == IMPLICIT then
3591-
addParamMod(() => Mod.Implicit())
3591+
addParamMod(() =>
3592+
if ctx.settings.YimplicitToGiven.value then
3593+
patch(Span(in.lastOffset - 8, in.lastOffset), "using")
3594+
Mod.Implicit()
3595+
)
35923596
else if isIdent(nme.using) then
35933597
if initialMods.is(Given) then
35943598
syntaxError(em"`using` is already implied here, should not be given explicitly", in.offset)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1941,7 +1941,7 @@ class Namer { typer: Typer =>
19411941
if completedTypeParams.forall(_.isType) then
19421942
completer.setCompletedTypeParams(completedTypeParams.asInstanceOf[List[TypeSymbol]])
19431943
completeTrailingParamss(ddef, sym, indexingCtor = false)
1944-
val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor)
1944+
val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor, Some(ddef.nameSpan.startPos))
19451945
sym.setParamss(paramSymss)
19461946

19471947
def wrapMethType(restpe: Type): Type =

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

+41
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import transform.CheckUnused.OriginalName
5353

5454
import scala.annotation.{unchecked as _, *}
5555
import dotty.tools.dotc.util.chaining.*
56+
import dotty.tools.dotc.ast.untpd.Mod
5657

5758
object Typer {
5859

@@ -2891,6 +2892,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28912892
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
28922893
postProcessInfo(vdef1, sym)
28932894
vdef1.setDefTree
2895+
2896+
if ctx.isTyper && vdef1.symbol.isImplicitRewrittenToGiven && !vdef1.symbol.isParamOrAccessor then
2897+
val implicitSpan =
2898+
vdef1.mods.mods.collectFirst {
2899+
case mod: Mod.Implicit => mod.span
2900+
}.get
2901+
patch(
2902+
Span(implicitSpan.start, implicitSpan.end + 1),
2903+
""
2904+
)
2905+
patch(
2906+
Span(vdef1.mods.mods.last.span.end + 1, vdef1.namePos.span.start), "given "
2907+
)
2908+
28942909
val nnInfo = rhs1.notNullInfo
28952910
vdef1.withNotNullInfo(if sym.is(Lazy) then nnInfo.retractedInfo else nnInfo)
28962911
}
@@ -3003,6 +3018,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
30033018

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

3021+
if
3022+
ctx.isTyper
3023+
&& ddef2.symbol.isImplicitRewrittenToGiven
3024+
&& !ddef2.symbol.isParamOrAccessor
3025+
&& !ddef2.symbol.isOldStyleImplicitConversion()
3026+
then
3027+
val implicitSpan =
3028+
ddef2.mods.mods.collectFirst {
3029+
case mod: Mod.Implicit => mod.span
3030+
}.get
3031+
patch(
3032+
Span(implicitSpan.start, implicitSpan.end + 1), ""
3033+
)
3034+
patch(
3035+
Span(ddef2.mods.mods.last.span.end + 1, ddef2.namePos.span.start), "given "
3036+
)
3037+
ddef.tpt match
3038+
case refinedType: untpd.RefinedTypeTree =>
3039+
patch(refinedType.span.startPos, "(")
3040+
patch(refinedType.span.endPos, ")")
3041+
case _ =>
3042+
30063043
postProcessInfo(ddef2, sym)
30073044
//todo: make sure dependent method types do not depend on implicits or by-name params
30083045
}
@@ -4200,6 +4237,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42004237
case wtp: MethodOrPoly =>
42014238
def methodStr = methPart(tree).symbol.showLocated
42024239
if matchingApply(wtp, pt) then
4240+
val isUsingApply = pt.applyKind == ApplyKind.Using
4241+
val notSynthetic = tree.span.exists && tree.span.start != tree.span.end
4242+
if ctx.isTyper && wtp.isImplicitMethodRewrittenToContextual && notSynthetic && !isUsingApply then
4243+
patch(Span(tree.span.end, tree.span.end + 1), "(using ")
42034244
migrate(contextBoundParams(tree, wtp, pt))
42044245
migrate(implicitParams(tree, wtp, pt))
42054246
if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked)

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

+1
Original file line numberDiff line numberDiff line change
@@ -2284,6 +2284,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
22842284
self.companion match
22852285
case Types.ContextualMethodType => MethodTypeKind.Contextual
22862286
case Types.ImplicitMethodType => MethodTypeKind.Implicit
2287+
case Types.ImplicitRewrittenToContextualMethodType => MethodTypeKind.Implicit
22872288
case _ => MethodTypeKind.Plain
22882289
def param(idx: Int): TypeRepr = self.newParamRef(idx)
22892290

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

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class CompilationTests {
8686
compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")),
8787
compileFile("tests/rewrites/i22731.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")),
8888
compileFile("tests/rewrites/i22731b.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")),
89+
compileFile("tests/rewrites/implicit-as-given.scala", defaultOptions.and("-rewrite", "-Yimplicit-to-given"))
8990
).checkRewrites()
9091
}
9192

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
object Def:
2+
trait A
3+
trait B
4+
implicit def conv1(a: A): B = ??? // should not change
5+
given conv2: A => B = ???
6+
final given nonConv: A = ???
7+
given conv3: A => B = ???
8+
final given nonConv2: A = ???
9+
10+
implicit class Extension(a: Int): // should not change
11+
def addedMethod(): A = ???
12+
implicit class ExtensionWithImplicit(t: String)(using a: Int):
13+
def addedMethod(): String = ???
14+
class NoNonimplicitParams()(using a: Int)
15+
16+
def applicationTest(using a: Int): Unit = ???
17+
val application = applicationTest(using 0)
18+
val implicitArg: Int => Unit = (implicit a => applicationTest) // should not change
19+
20+
given refined(): (A {type B = Int}) = ???
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
object Def:
2+
trait A
3+
trait B
4+
implicit def conv1(a: A): B = ??? // should not change
5+
implicit def conv2: A => B = ???
6+
final implicit def nonConv: A = ???
7+
implicit val conv3: A => B = ???
8+
final implicit val nonConv2: A = ???
9+
10+
implicit class Extension(a: Int): // should not change
11+
def addedMethod(): A = ???
12+
implicit class ExtensionWithImplicit(t: String)(implicit a: Int):
13+
def addedMethod(): String = ???
14+
class NoNonimplicitParams(implicit a: Int)
15+
16+
def applicationTest(implicit a: Int): Unit = ???
17+
val application = applicationTest(0)
18+
val implicitArg: Int => Unit = (implicit a => applicationTest) // should not change
19+
20+
implicit def refined(): A {type B = Int} = ???

0 commit comments

Comments
 (0)