From 4eff7488a7e017190af42be1c21ea17681ec238c Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 23 Apr 2024 12:06:20 +0200 Subject: [PATCH 1/2] Introduce MethodTypeKind to quotes reflection API It allows to create Contextual and Implicit MethodTypes. MethodTypeKind abstracts away the MethodTypeCompanion implementation into a simple enum style choice for a newly added MethodType apply. The MethodType unapply is kept as it was for source compatibility, instead users are encouraged to use isImplicit and isContextual methods. --- .../quoted/runtime/impl/QuotesImpl.scala | 11 ++++ library/src/scala/quoted/Quotes.scala | 25 +++++++- project/MiMaFilters.scala | 7 +- .../reflect-method-type-kind/macro_1.scala | 64 +++++++++++++++++++ .../reflect-method-type-kind/test_2.scala | 3 + 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 tests/run-macros/reflect-method-type-kind/macro_1.scala create mode 100644 tests/run-macros/reflect-method-type-kind/test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 81fadb6baa89..cf26d6f16ef3 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2204,6 +2204,13 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler case _ => None end MethodOrPolyTypeTest + type MethodTypeKind = dotc.core.Types.MethodTypeCompanion + + object MethodTypeKind extends MethodTypeKindModule: + val Plain: MethodTypeKind = Types.MethodType + val Contextual: MethodTypeKind = Types.ContextualMethodType + val Implicit: MethodTypeKind = Types.ImplicitMethodType + type MethodType = dotc.core.Types.MethodType object MethodTypeTypeTest extends TypeTest[TypeRepr, MethodType]: @@ -2215,6 +2222,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object MethodType extends MethodTypeModule: def apply(paramNames: List[String])(paramInfosExp: MethodType => List[TypeRepr], resultTypeExp: MethodType => TypeRepr): MethodType = Types.MethodType(paramNames.map(_.toTermName))(paramInfosExp, resultTypeExp) + def apply(kind: MethodTypeKind)(paramNames: List[String])(paramInfosExp: MethodType => List[TypeRepr], resultTypeExp: MethodType => TypeRepr): MethodType = + kind.apply(paramNames.map(_.toTermName))(paramInfosExp, resultTypeExp) def unapply(x: MethodType): (List[String], List[TypeRepr], TypeRepr) = (x.paramNames.map(_.toString), x.paramTypes, x.resType) end MethodType @@ -2223,6 +2232,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler extension (self: MethodType) def isErased: Boolean = false def isImplicit: Boolean = self.isImplicitMethod + def isContextual: Boolean = self.isContextualMethod + def methodTypeKind: MethodTypeKind = self.companion def param(idx: Int): TypeRepr = self.newParamRef(idx) def erasedParams: List[Boolean] = self.erasedParams diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index e5cdc3bf4fb7..e76c924ece20 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -211,6 +211,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * +- MatchCase * +- TypeBounds * +- NoPrefix + * + * +- MethodTypeKind * * +- Selector -+- SimpleSelector * +- RenameSelector @@ -3234,6 +3236,22 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** `TypeTest` that allows testing at runtime in a pattern match if a `TypeRepr` is a `MethodOrPoly` */ given MethodOrPolyTypeTest: TypeTest[TypeRepr, MethodOrPoly] + /** Type which decides on the kind of parameter list represented by `MethodType`. */ + type MethodTypeKind + + /** Module object of `type MethodKind` */ + val MethodTypeKind: MethodTypeKindModule + + /** Methods of the module object `val MethodKind` */ + trait MethodTypeKindModule { this: MethodTypeKind.type => + /** Represents a parameter list without any implicitness of parameters, like (x1: X1, x2: X2, ...) */ + val Plain: MethodTypeKind + /** Represents a parameter list with implicit parameters, like `(implicit X1, ..., Xn)`, `(using X1, ..., Xn)`, `(using x1: X1, ..., xn: Xn)` */ + val Implicit: MethodTypeKind + /** Represents a parameter list of a contextual method, like `(using X1, ..., Xn)` or `(using x1: X1, ..., xn: Xn)` */ + val Contextual: MethodTypeKind + } + /** Type of the definition of a method taking a single list of parameters. It's return type may be a MethodType. */ type MethodType <: MethodOrPoly @@ -3246,6 +3264,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val MethodType` */ trait MethodTypeModule { this: MethodType.type => def apply(paramNames: List[String])(paramInfosExp: MethodType => List[TypeRepr], resultTypeExp: MethodType => TypeRepr): MethodType + def apply(kind: MethodTypeKind)(paramNames: List[String])(paramInfosExp: MethodType => List[TypeRepr], resultTypeExp: MethodType => TypeRepr): MethodType def unapply(x: MethodType): (List[String], List[TypeRepr], TypeRepr) } @@ -3255,8 +3274,12 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Extension methods of `MethodType` */ trait MethodTypeMethods: extension (self: MethodType) - /** Is this the type of using parameter clause `(implicit X1, ..., Xn)`, `(using X1, ..., Xn)` or `(using x1: X1, ..., xn: Xn)` */ + /** Is this the type of parameter clause like `(implicit X1, ..., Xn)`, `(using X1, ..., Xn)` or `(using x1: X1, ..., xn: Xn)` */ def isImplicit: Boolean + /** Is this the type of parameter clause like `(using X1, ..., Xn)` or `(using x1: X1, x2: X2, ... )` */ + def isContextual: Boolean + /** Returns a MethodTypeKind object representing the implicitness of the MethodType parameter clause. */ + def methodTypeKind: MethodTypeKind /** Is this the type of erased parameter clause `(erased x1: X1, ..., xn: Xn)` */ @deprecated("Use `hasErasedParams` and `erasedParams`", "3.4") def isErased: Boolean diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index b22b2e3446e5..904367d15ecd 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -50,7 +50,12 @@ object MiMaFilters { // Breaking changes since last reference version Build.mimaPreviousDottyVersion -> // Seq.empty, // We should never break backwards compatibility Seq( - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.isSuperAccessor"), // This change is acceptable. See comment in `Breaking changes since last LTS`. + // `ReversedMissingMethodProblem`s are acceptable. See comment in `Breaking changes since last LTS`. + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.isSuperAccessor"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.MethodTypeKind"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeModule.apply"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.methodTypeKind"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.isContextual"), ), // Breaking changes since last LTS diff --git a/tests/run-macros/reflect-method-type-kind/macro_1.scala b/tests/run-macros/reflect-method-type-kind/macro_1.scala new file mode 100644 index 000000000000..6e996a85603d --- /dev/null +++ b/tests/run-macros/reflect-method-type-kind/macro_1.scala @@ -0,0 +1,64 @@ +trait Foo +trait Bar + +object Methods: + def implicitMethod(implicit foo: Foo, int: Int): Bar = ??? + def contextualMethod(using foo: Foo, int: Int): Bar = ??? + def plainMethod(foo: Foo, int: Int): Bar = ??? + +object Macro: + import scala.quoted._ + inline def macroCall(): Unit = ${ macroCallImpl } + def macroCallImpl(using Quotes): Expr[Unit] = + testReadingMethodTypeKind + testCreatingMethodTypeKind + '{()} + + def testReadingMethodTypeKind(using Quotes) = + import quotes.reflect._ + def getFromMethods(name: String): TypeRepr = + val typeRepr = TypeRepr.of[Methods.type] + val symbol = + typeRepr.typeSymbol.methodMember(name).headOption.getOrElse( + typeRepr.typeSymbol.fieldMember(name) + ) + typeRepr.memberType(symbol) + + assert(getFromMethods("implicitMethod").asInstanceOf[MethodType].isImplicit) + assert(!getFromMethods("implicitMethod").asInstanceOf[MethodType].isContextual) + assert(getFromMethods("implicitMethod").asInstanceOf[MethodType].methodTypeKind == MethodTypeKind.Implicit) + + assert(getFromMethods("contextualMethod").asInstanceOf[MethodType].isImplicit) + assert(getFromMethods("contextualMethod").asInstanceOf[MethodType].isContextual) + assert(getFromMethods("contextualMethod").asInstanceOf[MethodType].methodTypeKind == MethodTypeKind.Contextual) + + assert(!getFromMethods("plainMethod").asInstanceOf[MethodType].isImplicit) + assert(!getFromMethods("plainMethod").asInstanceOf[MethodType].isContextual) + assert(getFromMethods("plainMethod").asInstanceOf[MethodType].methodTypeKind == MethodTypeKind.Plain) + + + def testCreatingMethodTypeKind(using Quotes) = + import quotes.reflect._ + val paramTypes = List(TypeRepr.of[Foo], TypeRepr.of[Int]) + val resType = TypeRepr.of[Bar] + val implicitMethodType = MethodType.apply(MethodTypeKind.Implicit)(List("foo", "int"))(mt => paramTypes, mt => resType) + assert(implicitMethodType.isImplicit) + assert(!implicitMethodType.isContextual) + assert(implicitMethodType.methodTypeKind == MethodTypeKind.Implicit) + assert(implicitMethodType.methodTypeKind != MethodTypeKind.Contextual) + assert(implicitMethodType.methodTypeKind != MethodTypeKind.Plain) + + + val contextualMethodType = MethodType.apply(MethodTypeKind.Contextual)(List("foo", "int"))(mt => paramTypes, mt => resType) + assert(contextualMethodType.isImplicit) + assert(contextualMethodType.isContextual) + assert(contextualMethodType.methodTypeKind != MethodTypeKind.Implicit) + assert(contextualMethodType.methodTypeKind == MethodTypeKind.Contextual) + assert(contextualMethodType.methodTypeKind != MethodTypeKind.Plain) + + val plainMethodType = MethodType.apply(MethodTypeKind.Plain)(List("foo", "int"))(mt => paramTypes, mt => resType) + assert(!plainMethodType.isContextual) + assert(!plainMethodType.isImplicit) + assert(plainMethodType.methodTypeKind != MethodTypeKind.Implicit) + assert(plainMethodType.methodTypeKind != MethodTypeKind.Contextual) + assert(plainMethodType.methodTypeKind == MethodTypeKind.Plain) diff --git a/tests/run-macros/reflect-method-type-kind/test_2.scala b/tests/run-macros/reflect-method-type-kind/test_2.scala new file mode 100644 index 000000000000..a020dc4e2d93 --- /dev/null +++ b/tests/run-macros/reflect-method-type-kind/test_2.scala @@ -0,0 +1,3 @@ +object Test: + def main(args: Array[String]): Unit = + Macro.macroCall() From 4d7e6add055277ca56458b492b24a2b6bc3f07c4 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 25 Apr 2024 11:34:36 +0200 Subject: [PATCH 2/2] Make MethodTypeKind into an enum --- .../quoted/runtime/impl/QuotesImpl.scala | 19 ++++++++++--------- library/src/scala/quoted/Quotes.scala | 19 +++++++------------ 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index cf26d6f16ef3..492d214a2c8d 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2204,13 +2204,6 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler case _ => None end MethodOrPolyTypeTest - type MethodTypeKind = dotc.core.Types.MethodTypeCompanion - - object MethodTypeKind extends MethodTypeKindModule: - val Plain: MethodTypeKind = Types.MethodType - val Contextual: MethodTypeKind = Types.ContextualMethodType - val Implicit: MethodTypeKind = Types.ImplicitMethodType - type MethodType = dotc.core.Types.MethodType object MethodTypeTypeTest extends TypeTest[TypeRepr, MethodType]: @@ -2223,7 +2216,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def apply(paramNames: List[String])(paramInfosExp: MethodType => List[TypeRepr], resultTypeExp: MethodType => TypeRepr): MethodType = Types.MethodType(paramNames.map(_.toTermName))(paramInfosExp, resultTypeExp) def apply(kind: MethodTypeKind)(paramNames: List[String])(paramInfosExp: MethodType => List[TypeRepr], resultTypeExp: MethodType => TypeRepr): MethodType = - kind.apply(paramNames.map(_.toTermName))(paramInfosExp, resultTypeExp) + val companion = kind match + case MethodTypeKind.Contextual => Types.ContextualMethodType + case MethodTypeKind.Implicit => Types.ImplicitMethodType + case MethodTypeKind.Plain => Types.MethodType + companion.apply(paramNames.map(_.toTermName))(paramInfosExp, resultTypeExp) def unapply(x: MethodType): (List[String], List[TypeRepr], TypeRepr) = (x.paramNames.map(_.toString), x.paramTypes, x.resType) end MethodType @@ -2233,7 +2230,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def isErased: Boolean = false def isImplicit: Boolean = self.isImplicitMethod def isContextual: Boolean = self.isContextualMethod - def methodTypeKind: MethodTypeKind = self.companion + def methodTypeKind: MethodTypeKind = + self.companion match + case Types.ContextualMethodType => MethodTypeKind.Contextual + case Types.ImplicitMethodType => MethodTypeKind.Implicit + case _ => MethodTypeKind.Plain def param(idx: Int): TypeRepr = self.newParamRef(idx) def erasedParams: List[Boolean] = self.erasedParams diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index e76c924ece20..e4fcc0ce29d0 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -212,7 +212,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * +- TypeBounds * +- NoPrefix * - * +- MethodTypeKind + * +- MethodTypeKind -+- Contextual + * +- Implicit + * +- Plain * * +- Selector -+- SimpleSelector * +- RenameSelector @@ -3237,20 +3239,13 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => given MethodOrPolyTypeTest: TypeTest[TypeRepr, MethodOrPoly] /** Type which decides on the kind of parameter list represented by `MethodType`. */ - type MethodTypeKind - - /** Module object of `type MethodKind` */ - val MethodTypeKind: MethodTypeKindModule - - /** Methods of the module object `val MethodKind` */ - trait MethodTypeKindModule { this: MethodTypeKind.type => + enum MethodTypeKind: /** Represents a parameter list without any implicitness of parameters, like (x1: X1, x2: X2, ...) */ - val Plain: MethodTypeKind + case Plain /** Represents a parameter list with implicit parameters, like `(implicit X1, ..., Xn)`, `(using X1, ..., Xn)`, `(using x1: X1, ..., xn: Xn)` */ - val Implicit: MethodTypeKind + case Implicit /** Represents a parameter list of a contextual method, like `(using X1, ..., Xn)` or `(using x1: X1, ..., xn: Xn)` */ - val Contextual: MethodTypeKind - } + case Contextual /** Type of the definition of a method taking a single list of parameters. It's return type may be a MethodType. */ type MethodType <: MethodOrPoly