From 6efd2bc7eb4a6e9f4e4e7537fda3d7ec68c1dd94 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Mon, 5 May 2025 19:19:17 +0200 Subject: [PATCH 1/2] Add -Yimplicit-as-given flag allowing rewriting implicits to givens --- .../tools/dotc/config/ScalaSettings.scala | 1 + .../src/dotty/tools/dotc/core/NamerOps.scala | 23 +++++++---- .../src/dotty/tools/dotc/core/Symbols.scala | 4 ++ .../src/dotty/tools/dotc/core/Types.scala | 14 +++++-- .../dotty/tools/dotc/parsing/Parsers.scala | 6 ++- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 41 +++++++++++++++++++ .../quoted/runtime/impl/QuotesImpl.scala | 1 + .../dotty/tools/dotc/CompilationTests.scala | 1 + tests/rewrites/implicit-as-given.check | 20 +++++++++ tests/rewrites/implicit-as-given.scala | 20 +++++++++ 11 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 tests/rewrites/implicit-as-given.check create mode 100644 tests/rewrites/implicit-as-given.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 0517d5b46b9e..acfbd7a1571c 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -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") diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index d6db7348ddc1..a967e36c9e9f 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -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: @@ -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) @@ -79,10 +88,10 @@ object NamerOps: case Nil => resultType case TermSymbols(params) :: paramss1 => - val (isContextual, isImplicit) = - if params.isEmpty then (false, false) - else (params.head.is(Given), params.head.is(Implicit)) - val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit) + val (isContextual, isImplicit, implicitToGiven) = + if params.isEmpty then (false, false, false) + else (params.head.is(Given), params.head.is(Implicit), params.head.isImplicitRewrittenToGiven) + val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit, implicitToGiven = implicitToGiven) if isJava then for param <- params do if param.info.isDirectRef(defn.ObjectClass) then param.info = defn.AnyType diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index c8ede8bfdec2..3d5d187c075f 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -404,6 +404,10 @@ object Symbols extends SymUtils { def paramVariance(using Context): Variance = denot.variance def paramRef(using Context): TypeRef = denot.typeRef + /** Was it an implicit, currently rewritten into a given with `-Yimplicit-to-given` */ + def isImplicitRewrittenToGiven(using Context): Boolean = + ctx.settings.YimplicitToGiven.value && denot.flags.is(Implicit) && this.isDefinedInSource + /** Copy a symbol, overriding selective fields. * Note that `coord` and `compilationUnitInfo` will be set from the fields in `owner`, not * the fields in `sym`. */ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2cd66464da3c..8087af1f2296 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -478,6 +478,9 @@ object Types extends TypeUtils { /** Is this a Method or PolyType which has implicit or contextual parameters? */ def isImplicitMethod: Boolean = false + /** Is this method parameter list implicit, currently rewritten into a given with `-Yimplicit-to-given`? */ + def isImplicitMethodRewrittenToContextual: Boolean = false + /** Is this a Method or PolyType which has contextual parameters as first value parameter list? */ def isContextualMethod: Boolean = false @@ -4154,13 +4157,16 @@ object Types extends TypeUtils { def companion: MethodTypeCompanion final override def isImplicitMethod: Boolean = - companion.eq(ImplicitMethodType) || isContextualMethod + companion.eq(ImplicitMethodType) || companion.eq(ImplicitRewrittenToContextualMethodType) || isContextualMethod final override def hasErasedParams(using Context): Boolean = paramInfos.exists(p => p.hasAnnotation(defn.ErasedParamAnnot)) final override def isContextualMethod: Boolean = companion.eq(ContextualMethodType) + final override def isImplicitMethodRewrittenToContextual: Boolean = + companion.eq(ImplicitRewrittenToContextualMethodType) + def erasedParams(using Context): List[Boolean] = paramInfos.map(p => p.hasAnnotation(defn.ErasedParamAnnot)) @@ -4292,14 +4298,16 @@ object Types extends TypeUtils { } object MethodType extends MethodTypeCompanion("MethodType") { - def companion(isContextual: Boolean = false, isImplicit: Boolean = false): MethodTypeCompanion = - if (isContextual) ContextualMethodType + def companion(isContextual: Boolean = false, isImplicit: Boolean = false, implicitToGiven: Boolean = false): MethodTypeCompanion = + if (implicitToGiven) ImplicitRewrittenToContextualMethodType + else if (isContextual) ContextualMethodType else if (isImplicit) ImplicitMethodType else MethodType } object ContextualMethodType extends MethodTypeCompanion("ContextualMethodType") object ImplicitMethodType extends MethodTypeCompanion("ImplicitMethodType") + object ImplicitRewrittenToContextualMethodType extends MethodTypeCompanion("ImplicitRewrittenToContextualMethodType") /** A ternary extractor for MethodType */ object MethodTpe { diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index e396839c972e..c3d9657e7755 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 408018daf82c..76328aabb573 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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 = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 428568ecc795..326895887c7a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 { @@ -2883,6 +2884,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) postProcessInfo(vdef1, sym) vdef1.setDefTree + + if ctx.isTyper && vdef1.symbol.isImplicitRewrittenToGiven && !vdef1.symbol.isParamOrAccessor then + val implicitSpan = + vdef1.mods.mods.collectFirst { + case mod: Mod.Implicit => mod.span + }.get + patch( + Span(implicitSpan.start, implicitSpan.end + 1), + "" + ) + patch( + Span(vdef1.mods.mods.last.span.end + 1, vdef1.namePos.span.start), "given " + ) + val nnInfo = rhs1.notNullInfo vdef1.withNotNullInfo(if sym.is(Lazy) then nnInfo.retractedInfo else nnInfo) } @@ -2995,6 +3010,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val ddef2 = assignType(cpy.DefDef(ddef)(name, paramss1, tpt1, rhs1), sym) + if + ctx.isTyper + && ddef2.symbol.isImplicitRewrittenToGiven + && !ddef2.symbol.isParamOrAccessor + && !ddef2.symbol.isOldStyleImplicitConversion() + then + val implicitSpan = + ddef2.mods.mods.collectFirst { + case mod: Mod.Implicit => mod.span + }.get + patch( + Span(implicitSpan.start, implicitSpan.end + 1), "" + ) + patch( + Span(ddef2.mods.mods.last.span.end + 1, ddef2.namePos.span.start), "given " + ) + ddef.tpt match + case refinedType: untpd.RefinedTypeTree => + patch(refinedType.span.startPos, "(") + patch(refinedType.span.endPos, ")") + case _ => + postProcessInfo(ddef2, sym) //todo: make sure dependent method types do not depend on implicits or by-name params } @@ -4192,6 +4229,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case wtp: MethodOrPoly => def methodStr = methPart(tree).symbol.showLocated if matchingApply(wtp, pt) then + val isUsingApply = pt.applyKind == ApplyKind.Using + val notSynthetic = tree.span.exists && tree.span.start != tree.span.end + if ctx.isTyper && wtp.isImplicitMethodRewrittenToContextual && notSynthetic && !isUsingApply then + patch(Span(tree.span.end, tree.span.end + 1), "(using ") migrate(contextBoundParams(tree, wtp, pt)) migrate(implicitParams(tree, wtp, pt)) if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index a93e010ddc34..4c427789ea0e 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2284,6 +2284,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler self.companion match case Types.ContextualMethodType => MethodTypeKind.Contextual case Types.ImplicitMethodType => MethodTypeKind.Implicit + case Types.ImplicitRewrittenToContextualMethodType => MethodTypeKind.Implicit case _ => MethodTypeKind.Plain def param(idx: Int): TypeRepr = self.newParamRef(idx) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 93238647d1a3..1de638407e24 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -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-as-given.scala", defaultOptions.and("-rewrite", "-Yimplicit-to-given")) ).checkRewrites() } diff --git a/tests/rewrites/implicit-as-given.check b/tests/rewrites/implicit-as-given.check new file mode 100644 index 000000000000..d36b87fea4a7 --- /dev/null +++ b/tests/rewrites/implicit-as-given.check @@ -0,0 +1,20 @@ +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}) = ??? diff --git a/tests/rewrites/implicit-as-given.scala b/tests/rewrites/implicit-as-given.scala new file mode 100644 index 000000000000..8098898082cf --- /dev/null +++ b/tests/rewrites/implicit-as-given.scala @@ -0,0 +1,20 @@ +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} = ??? From cb31976b3c771bbca98c8c03064c707afb975af3 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 6 May 2025 23:50:55 +0200 Subject: [PATCH 2/2] Simplify and reuse using migration rewrites --- .../src/dotty/tools/dotc/core/NamerOps.scala | 8 +-- .../src/dotty/tools/dotc/core/Symbols.scala | 4 -- .../src/dotty/tools/dotc/core/Types.scala | 14 +--- .../dotty/tools/dotc/typer/Migrations.scala | 68 +++++++++++++++++-- .../src/dotty/tools/dotc/typer/Typer.scala | 40 +---------- .../quoted/runtime/impl/QuotesImpl.scala | 1 - .../dotty/tools/dotc/CompilationTests.scala | 2 +- ...as-given.check => implicit-to-given.check} | 3 + ...as-given.scala => implicit-to-given.scala} | 3 + 9 files changed, 80 insertions(+), 63 deletions(-) rename tests/rewrites/{implicit-as-given.check => implicit-to-given.check} (85%) rename tests/rewrites/{implicit-as-given.scala => implicit-to-given.scala} (87%) diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index a967e36c9e9f..96276be45914 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -88,10 +88,10 @@ object NamerOps: case Nil => resultType case TermSymbols(params) :: paramss1 => - val (isContextual, isImplicit, implicitToGiven) = - if params.isEmpty then (false, false, false) - else (params.head.is(Given), params.head.is(Implicit), params.head.isImplicitRewrittenToGiven) - val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit, implicitToGiven = implicitToGiven) + val (isContextual, isImplicit) = + if params.isEmpty then (false, false) + else (params.head.is(Given), params.head.is(Implicit)) + val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit) if isJava then for param <- params do if param.info.isDirectRef(defn.ObjectClass) then param.info = defn.AnyType diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 3d5d187c075f..c8ede8bfdec2 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -404,10 +404,6 @@ object Symbols extends SymUtils { def paramVariance(using Context): Variance = denot.variance def paramRef(using Context): TypeRef = denot.typeRef - /** Was it an implicit, currently rewritten into a given with `-Yimplicit-to-given` */ - def isImplicitRewrittenToGiven(using Context): Boolean = - ctx.settings.YimplicitToGiven.value && denot.flags.is(Implicit) && this.isDefinedInSource - /** Copy a symbol, overriding selective fields. * Note that `coord` and `compilationUnitInfo` will be set from the fields in `owner`, not * the fields in `sym`. */ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8087af1f2296..2cd66464da3c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -478,9 +478,6 @@ object Types extends TypeUtils { /** Is this a Method or PolyType which has implicit or contextual parameters? */ def isImplicitMethod: Boolean = false - /** Is this method parameter list implicit, currently rewritten into a given with `-Yimplicit-to-given`? */ - def isImplicitMethodRewrittenToContextual: Boolean = false - /** Is this a Method or PolyType which has contextual parameters as first value parameter list? */ def isContextualMethod: Boolean = false @@ -4157,16 +4154,13 @@ object Types extends TypeUtils { def companion: MethodTypeCompanion final override def isImplicitMethod: Boolean = - companion.eq(ImplicitMethodType) || companion.eq(ImplicitRewrittenToContextualMethodType) || isContextualMethod + companion.eq(ImplicitMethodType) || isContextualMethod final override def hasErasedParams(using Context): Boolean = paramInfos.exists(p => p.hasAnnotation(defn.ErasedParamAnnot)) final override def isContextualMethod: Boolean = companion.eq(ContextualMethodType) - final override def isImplicitMethodRewrittenToContextual: Boolean = - companion.eq(ImplicitRewrittenToContextualMethodType) - def erasedParams(using Context): List[Boolean] = paramInfos.map(p => p.hasAnnotation(defn.ErasedParamAnnot)) @@ -4298,16 +4292,14 @@ object Types extends TypeUtils { } object MethodType extends MethodTypeCompanion("MethodType") { - def companion(isContextual: Boolean = false, isImplicit: Boolean = false, implicitToGiven: Boolean = false): MethodTypeCompanion = - if (implicitToGiven) ImplicitRewrittenToContextualMethodType - else if (isContextual) ContextualMethodType + def companion(isContextual: Boolean = false, isImplicit: Boolean = false): MethodTypeCompanion = + if (isContextual) ContextualMethodType else if (isImplicit) ImplicitMethodType else MethodType } object ContextualMethodType extends MethodTypeCompanion("ContextualMethodType") object ImplicitMethodType extends MethodTypeCompanion("ImplicitMethodType") - object ImplicitRewrittenToContextualMethodType extends MethodTypeCompanion("ImplicitRewrittenToContextualMethodType") /** A ternary extractor for MethodType */ object MethodTpe { diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index acf9e7668917..5311199aad24 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -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) @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 326895887c7a..c757c0a339da 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2885,18 +2885,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer postProcessInfo(vdef1, sym) vdef1.setDefTree - if ctx.isTyper && vdef1.symbol.isImplicitRewrittenToGiven && !vdef1.symbol.isParamOrAccessor then - val implicitSpan = - vdef1.mods.mods.collectFirst { - case mod: Mod.Implicit => mod.span - }.get - patch( - Span(implicitSpan.start, implicitSpan.end + 1), - "" - ) - patch( - Span(vdef1.mods.mods.last.span.end + 1, vdef1.namePos.span.start), "given " - ) + migrate(ImplicitToGiven.valDef(vdef1)) val nnInfo = rhs1.notNullInfo vdef1.withNotNullInfo(if sym.is(Lazy) then nnInfo.retractedInfo else nnInfo) @@ -3010,27 +2999,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val ddef2 = assignType(cpy.DefDef(ddef)(name, paramss1, tpt1, rhs1), sym) - if - ctx.isTyper - && ddef2.symbol.isImplicitRewrittenToGiven - && !ddef2.symbol.isParamOrAccessor - && !ddef2.symbol.isOldStyleImplicitConversion() - then - val implicitSpan = - ddef2.mods.mods.collectFirst { - case mod: Mod.Implicit => mod.span - }.get - patch( - Span(implicitSpan.start, implicitSpan.end + 1), "" - ) - patch( - Span(ddef2.mods.mods.last.span.end + 1, ddef2.namePos.span.start), "given " - ) - ddef.tpt match - case refinedType: untpd.RefinedTypeTree => - patch(refinedType.span.startPos, "(") - patch(refinedType.span.endPos, ")") - case _ => + migrate(ImplicitToGiven.defDef(ddef2)) postProcessInfo(ddef2, sym) //todo: make sure dependent method types do not depend on implicits or by-name params @@ -4229,10 +4198,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case wtp: MethodOrPoly => def methodStr = methPart(tree).symbol.showLocated if matchingApply(wtp, pt) then - val isUsingApply = pt.applyKind == ApplyKind.Using - val notSynthetic = tree.span.exists && tree.span.start != tree.span.end - if ctx.isTyper && wtp.isImplicitMethodRewrittenToContextual && notSynthetic && !isUsingApply then - patch(Span(tree.span.end, tree.span.end + 1), "(using ") + 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) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 4c427789ea0e..a93e010ddc34 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2284,7 +2284,6 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler self.companion match case Types.ContextualMethodType => MethodTypeKind.Contextual case Types.ImplicitMethodType => MethodTypeKind.Implicit - case Types.ImplicitRewrittenToContextualMethodType => MethodTypeKind.Implicit case _ => MethodTypeKind.Plain def param(idx: Int): TypeRepr = self.newParamRef(idx) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 1de638407e24..234da4802212 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -86,7 +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-as-given.scala", defaultOptions.and("-rewrite", "-Yimplicit-to-given")) + compileFile("tests/rewrites/implicit-to-given.scala", defaultOptions.and("-rewrite", "-Yimplicit-to-given")) ).checkRewrites() } diff --git a/tests/rewrites/implicit-as-given.check b/tests/rewrites/implicit-to-given.check similarity index 85% rename from tests/rewrites/implicit-as-given.check rename to tests/rewrites/implicit-to-given.check index d36b87fea4a7..6375d4f303ee 100644 --- a/tests/rewrites/implicit-as-given.check +++ b/tests/rewrites/implicit-to-given.check @@ -18,3 +18,6 @@ object Def: 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) diff --git a/tests/rewrites/implicit-as-given.scala b/tests/rewrites/implicit-to-given.scala similarity index 87% rename from tests/rewrites/implicit-as-given.scala rename to tests/rewrites/implicit-to-given.scala index 8098898082cf..b4ef5927ab0e 100644 --- a/tests/rewrites/implicit-as-given.scala +++ b/tests/rewrites/implicit-to-given.scala @@ -18,3 +18,6 @@ object Def: 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)