Skip to content

Commit 6efd2bc

Browse files
committed
Add -Yimplicit-as-given flag allowing rewriting implicits to givens
1 parent d87bbb1 commit 6efd2bc

File tree

11 files changed

+121
-12
lines changed

11 files changed

+121
-12
lines changed

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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 16 additions & 7 deletions
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:
@@ -59,12 +61,19 @@ object NamerOps:
5961
* This is done by adding a () in front of a leading old style implicit parameter,
6062
* or by adding a () as last -- or only -- parameter list if the constructor has
6163
* only using clauses as parameters.
64+
*
65+
* implicitRewritePosition, if included, will point to where `()` should be added if rewriting
66+
* with -Yimplicit-to-given
6267
*/
63-
def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean)(using Context): List[List[Symbol]] =
68+
def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean, implicitRewritePosition: Option[Span] = None)(using Context): List[List[Symbol]] =
6469
if !isConstructor then paramss
6570
else paramss match
66-
case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor)
67-
case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) => Nil :: paramss
71+
case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor, implicitRewritePosition)
72+
case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) =>
73+
implicitRewritePosition match
74+
case Some(position) if ctx.settings.YimplicitToGiven.value => patch(position, "()")
75+
case _ => ()
76+
Nil :: paramss
6877
case _ =>
6978
if paramss.forall {
7079
case TermSymbols(vparams) => vparams.nonEmpty && vparams.head.is(Given)
@@ -79,10 +88,10 @@ object NamerOps:
7988
case Nil =>
8089
resultType
8190
case TermSymbols(params) :: paramss1 =>
82-
val (isContextual, isImplicit) =
83-
if params.isEmpty then (false, false)
84-
else (params.head.is(Given), params.head.is(Implicit))
85-
val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit)
91+
val (isContextual, isImplicit, implicitToGiven) =
92+
if params.isEmpty then (false, false, false)
93+
else (params.head.is(Given), params.head.is(Implicit), params.head.isImplicitRewrittenToGiven)
94+
val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit, implicitToGiven = implicitToGiven)
8695
if isJava then
8796
for param <- params do
8897
if param.info.isDirectRef(defn.ObjectClass) then param.info = defn.AnyType

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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 11 additions & 3 deletions
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

@@ -4154,13 +4157,16 @@ object Types extends TypeUtils {
41544157
def companion: MethodTypeCompanion
41554158

41564159
final override def isImplicitMethod: Boolean =
4157-
companion.eq(ImplicitMethodType) || isContextualMethod
4160+
companion.eq(ImplicitMethodType) || companion.eq(ImplicitRewrittenToContextualMethodType) || isContextualMethod
41584161
final override def hasErasedParams(using Context): Boolean =
41594162
paramInfos.exists(p => p.hasAnnotation(defn.ErasedParamAnnot))
41604163

41614164
final override def isContextualMethod: Boolean =
41624165
companion.eq(ContextualMethodType)
41634166

4167+
final override def isImplicitMethodRewrittenToContextual: Boolean =
4168+
companion.eq(ImplicitRewrittenToContextualMethodType)
4169+
41644170
def erasedParams(using Context): List[Boolean] =
41654171
paramInfos.map(p => p.hasAnnotation(defn.ErasedParamAnnot))
41664172

@@ -4292,14 +4298,16 @@ object Types extends TypeUtils {
42924298
}
42934299

42944300
object MethodType extends MethodTypeCompanion("MethodType") {
4295-
def companion(isContextual: Boolean = false, isImplicit: Boolean = false): MethodTypeCompanion =
4296-
if (isContextual) ContextualMethodType
4301+
def companion(isContextual: Boolean = false, isImplicit: Boolean = false, implicitToGiven: Boolean = false): MethodTypeCompanion =
4302+
if (implicitToGiven) ImplicitRewrittenToContextualMethodType
4303+
else if (isContextual) ContextualMethodType
42974304
else if (isImplicit) ImplicitMethodType
42984305
else MethodType
42994306
}
43004307

43014308
object ContextualMethodType extends MethodTypeCompanion("ContextualMethodType")
43024309
object ImplicitMethodType extends MethodTypeCompanion("ImplicitMethodType")
4310+
object ImplicitRewrittenToContextualMethodType extends MethodTypeCompanion("ImplicitRewrittenToContextualMethodType")
43034311

43044312
/** A ternary extractor for MethodType */
43054313
object MethodTpe {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3579,7 +3579,11 @@ object Parsers {
35793579

35803580
def paramMods() =
35813581
if in.token == IMPLICIT then
3582-
addParamMod(() => Mod.Implicit())
3582+
addParamMod(() =>
3583+
if ctx.settings.YimplicitToGiven.value then
3584+
patch(Span(in.lastOffset - 8, in.lastOffset), "using")
3585+
Mod.Implicit()
3586+
)
35833587
else if isIdent(nme.using) then
35843588
if initialMods.is(Given) then
35853589
syntaxError(em"`using` is already implied here, should not be given explicitly", in.offset)

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 41 additions & 0 deletions
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

@@ -2883,6 +2884,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28832884
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
28842885
postProcessInfo(vdef1, sym)
28852886
vdef1.setDefTree
2887+
2888+
if ctx.isTyper && vdef1.symbol.isImplicitRewrittenToGiven && !vdef1.symbol.isParamOrAccessor then
2889+
val implicitSpan =
2890+
vdef1.mods.mods.collectFirst {
2891+
case mod: Mod.Implicit => mod.span
2892+
}.get
2893+
patch(
2894+
Span(implicitSpan.start, implicitSpan.end + 1),
2895+
""
2896+
)
2897+
patch(
2898+
Span(vdef1.mods.mods.last.span.end + 1, vdef1.namePos.span.start), "given "
2899+
)
2900+
28862901
val nnInfo = rhs1.notNullInfo
28872902
vdef1.withNotNullInfo(if sym.is(Lazy) then nnInfo.retractedInfo else nnInfo)
28882903
}
@@ -2995,6 +3010,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
29953010

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

3013+
if
3014+
ctx.isTyper
3015+
&& ddef2.symbol.isImplicitRewrittenToGiven
3016+
&& !ddef2.symbol.isParamOrAccessor
3017+
&& !ddef2.symbol.isOldStyleImplicitConversion()
3018+
then
3019+
val implicitSpan =
3020+
ddef2.mods.mods.collectFirst {
3021+
case mod: Mod.Implicit => mod.span
3022+
}.get
3023+
patch(
3024+
Span(implicitSpan.start, implicitSpan.end + 1), ""
3025+
)
3026+
patch(
3027+
Span(ddef2.mods.mods.last.span.end + 1, ddef2.namePos.span.start), "given "
3028+
)
3029+
ddef.tpt match
3030+
case refinedType: untpd.RefinedTypeTree =>
3031+
patch(refinedType.span.startPos, "(")
3032+
patch(refinedType.span.endPos, ")")
3033+
case _ =>
3034+
29983035
postProcessInfo(ddef2, sym)
29993036
//todo: make sure dependent method types do not depend on implicits or by-name params
30003037
}
@@ -4192,6 +4229,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
41924229
case wtp: MethodOrPoly =>
41934230
def methodStr = methPart(tree).symbol.showLocated
41944231
if matchingApply(wtp, pt) then
4232+
val isUsingApply = pt.applyKind == ApplyKind.Using
4233+
val notSynthetic = tree.span.exists && tree.span.start != tree.span.end
4234+
if ctx.isTyper && wtp.isImplicitMethodRewrittenToContextual && notSynthetic && !isUsingApply then
4235+
patch(Span(tree.span.end, tree.span.end + 1), "(using ")
41954236
migrate(contextBoundParams(tree, wtp, pt))
41964237
migrate(implicitParams(tree, wtp, pt))
41974238
if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked)

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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 20 additions & 0 deletions
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}) = ???
Lines changed: 20 additions & 0 deletions
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)