From f7dc85bb6bcda5f81ba52926e02e3f130d7f033c Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 11 Feb 2025 12:04:36 +0100 Subject: [PATCH 1/2] Add an -Yimplicit-as-given flag to easily test changes in the ecosystem. It replaces every implicit definition with `given` (including classes, which I was surprised to see working), and every implicit parameter clause with `using`, all in the parser phase. Symbols and trees unpickled from tasty are unaffected by this flag. Trees generated using the reflection api are unaffected as well. --- .../tools/dotc/config/ScalaSettings.scala | 1 + .../dotty/tools/dotc/parsing/Parsers.scala | 3 ++- tests/pos-macros/i22482/Macro_1.scala | 22 +++++++++++++++++++ tests/pos-macros/i22482/Test_2.scala | 3 +++ tests/run/i22482.check | 1 + tests/run/i22482.scala | 15 +++++++++++++ 6 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/pos-macros/i22482/Macro_1.scala create mode 100644 tests/pos-macros/i22482/Test_2.scala create mode 100644 tests/run/i22482.check create mode 100644 tests/run/i22482.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 7058a9c4ab6d..b9ef3c95b675 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -458,6 +458,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 YimplicitAsGiven: Setting[Boolean] = BooleanSetting(ForkSetting, "Yimplicit-as-given", "Interpret the compiled implicit keywords as their scala-3 given counterparts.") // 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/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 968dcccc3d00..1e419d88be7b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3283,6 +3283,7 @@ object Parsers { private def modOfToken(tok: Int, name: Name): Mod = tok match { case ABSTRACT => Mod.Abstract() case FINAL => Mod.Final() + case IMPLICIT if ctx.settings.YimplicitAsGiven.value => Mod.Given() case IMPLICIT => Mod.Implicit() case GIVEN => Mod.Given() case LAZY => Mod.Lazy() @@ -3552,7 +3553,7 @@ object Parsers { def paramMods() = if in.token == IMPLICIT then - addParamMod(() => Mod.Implicit()) + addParamMod(() => if (ctx.settings.YimplicitAsGiven.value) Mod.Given() else 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/tests/pos-macros/i22482/Macro_1.scala b/tests/pos-macros/i22482/Macro_1.scala new file mode 100644 index 000000000000..1d3b119d1758 --- /dev/null +++ b/tests/pos-macros/i22482/Macro_1.scala @@ -0,0 +1,22 @@ +//> using options -Yimplicit-as-given +import scala.quoted._ +object Macro: + inline def test[I] = ${testImpl[I]} + def testImpl[I: Type](using Quotes): Expr[Unit] = + import quotes.reflect._ + + val implicitClause = '{ + def clause(implicit num: Int) = ??? + } + val expectedImplicitClause = + """|{ + | def clause(using num: scala.Int): scala.Nothing = scala.Predef.??? + | () + |}""".stripMargin + + assert(implicitClause.show == expectedImplicitClause) + + // test ImplicitClauseInCaseClass + assert(TypeRepr.of[I].typeSymbol.primaryConstructor.paramSymss(1)(0).flags.is(Flags.Given)) + + '{()} diff --git a/tests/pos-macros/i22482/Test_2.scala b/tests/pos-macros/i22482/Test_2.scala new file mode 100644 index 000000000000..2411872fde55 --- /dev/null +++ b/tests/pos-macros/i22482/Test_2.scala @@ -0,0 +1,3 @@ +//> using options -Yimplicit-as-given +case class ImplicitClauseInCaseClass(dummy: Int)(implicit num: Int) +def Test() = Macro.test[ImplicitClauseInCaseClass] diff --git a/tests/run/i22482.check b/tests/run/i22482.check new file mode 100644 index 000000000000..eff056eb8c01 --- /dev/null +++ b/tests/run/i22482.check @@ -0,0 +1 @@ +found if given \ No newline at end of file diff --git a/tests/run/i22482.scala b/tests/run/i22482.scala new file mode 100644 index 000000000000..ff66db933472 --- /dev/null +++ b/tests/run/i22482.scala @@ -0,0 +1,15 @@ +//> using options -Yimplicit-as-given + +object Lib: + class LibComponent: + def msg = "found if given" + implicit val libComponent: LibComponent = LibComponent() + + class UserComponent extends LibComponent: + override def msg = "found if implicit" + implicit val userComponent: UserComponent = UserComponent() + + def printComponent(implicit c: LibComponent) = println(c.msg) + + +@main def Test = Lib.printComponent From 9a5bd705acd1b41c458774282f8433abbdfbef24 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 6 Mar 2025 14:24:55 +0100 Subject: [PATCH 2/2] Handle apply on ContextualMethodTypes created with -Yimplicit-as-given as if it was implicit --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 ++ compiler/src/dotty/tools/dotc/core/Flags.scala | 3 +++ compiler/src/dotty/tools/dotc/core/NamerOps.scala | 8 ++++---- compiler/src/dotty/tools/dotc/core/Types.scala | 13 ++++++++++--- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 1 + tests/pos/i22482.scala | 8 ++++++++ 7 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 tests/pos/i22482.scala diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 2acfc4cf86e3..160c1e96159a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -210,6 +210,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Given()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Given) + case class GivenFromImplicit()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Given | Flags.FromImplicit) + case class Erased()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Erased) case class Final()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Final) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 0775b3caaf0c..8909d0d5fd77 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -436,6 +436,9 @@ object Flags { /** Symbol is a constructor proxy (either companion, or apply method) */ val (ConstructorProxy @ _, _, _) = newFlags(62, "") // (could be merged with Lifted) + /** Changed from implicit with -Yimplicits-as-given flags */ + val (FromImplicit @ _, _, _) = newFlags(63, "") + // --------- Combined Flag Sets and Conjunctions ---------------------- /** All possible flags */ diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index 363a01665564..8e948f278fcd 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -78,10 +78,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, fromImplicit) = + if params.isEmpty then (false, false, false) + else (params.head.is(Given), params.head.is(Implicit), params.head.is(FromImplicit)) + val make = MethodType.companion(isContextual = isContextual, isImplicit = isImplicit, fromImplicit = fromImplicit) 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/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2fcf628dbc01..e6c2f98e211e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -479,6 +479,8 @@ object Types extends TypeUtils { /** Is this a Method or PolyType which has implicit or contextual parameters? */ def isImplicitMethod: Boolean = false + def wasImplicitMethod: Boolean = false + /** Is this a Method or PolyType which has contextual parameters as first value parameter list? */ def isContextualMethod: Boolean = false @@ -4104,7 +4106,10 @@ object Types extends TypeUtils { paramInfos.exists(p => p.hasAnnotation(defn.ErasedParamAnnot)) final override def isContextualMethod: Boolean = - companion.eq(ContextualMethodType) + companion.eq(ContextualMethodType) || companion.eq(ContextualFromImplicitMethodType) + + final override def wasImplicitMethod: Boolean = + companion.eq(ContextualFromImplicitMethodType) def erasedParams(using Context): List[Boolean] = paramInfos.map(p => p.hasAnnotation(defn.ErasedParamAnnot)) @@ -4209,14 +4214,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, fromImplicit: Boolean = false): MethodTypeCompanion = + if (fromImplicit) ContextualFromImplicitMethodType + else if (isContextual) ContextualMethodType else if (isImplicit) ImplicitMethodType else MethodType } object ContextualMethodType extends MethodTypeCompanion("ContextualMethodType") object ImplicitMethodType extends MethodTypeCompanion("ImplicitMethodType") + object ContextualFromImplicitMethodType extends MethodTypeCompanion("ContextualFromImplicitMethodType") /** 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 1e419d88be7b..f14c8b6a16c2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3283,7 +3283,7 @@ object Parsers { private def modOfToken(tok: Int, name: Name): Mod = tok match { case ABSTRACT => Mod.Abstract() case FINAL => Mod.Final() - case IMPLICIT if ctx.settings.YimplicitAsGiven.value => Mod.Given() + case IMPLICIT if ctx.settings.YimplicitAsGiven.value => Mod.GivenFromImplicit() case IMPLICIT => Mod.Implicit() case GIVEN => Mod.Given() case LAZY => Mod.Lazy() @@ -3553,7 +3553,7 @@ object Parsers { def paramMods() = if in.token == IMPLICIT then - addParamMod(() => if (ctx.settings.YimplicitAsGiven.value) Mod.Given() else Mod.Implicit()) + addParamMod(() => if (ctx.settings.YimplicitAsGiven.value) Mod.GivenFromImplicit() else 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/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6e0651128e8e..5077b9fa54e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4855,6 +4855,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val isUsingApply = pt.applyKind == ApplyKind.Using methType.isContextualMethod == isUsingApply || methType.isImplicitMethod && isUsingApply // for a transition allow `using` arguments for regular implicit parameters + || methType.wasImplicitMethod && !isUsingApply /** Check that `tree == x: pt` is typeable. Used when checking a pattern * against a selector of type `pt`. This implementation accounts for diff --git a/tests/pos/i22482.scala b/tests/pos/i22482.scala new file mode 100644 index 000000000000..d45070f2b88c --- /dev/null +++ b/tests/pos/i22482.scala @@ -0,0 +1,8 @@ +//> using options -Yimplicit-as-given + +def foo(implicit a: Int): Unit = ??? +def bar()(implicit a: Int) : Unit = ??? + +def main() = + foo(0) + bar()(0)