Skip to content

Commit

Permalink
Introduce MethodTypeKind to quotes reflection API (#20249)
Browse files Browse the repository at this point in the history
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 method. The
MethodType unapply is kept as it was for source compatibility, instead
users are encouraged to use `isImplicit` and `isContextual` methods.

Based on #18499 
Fixes #18477
  • Loading branch information
nicolasstucki committed Apr 25, 2024
2 parents a0513b0 + 4d7e6ad commit 00802ab
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 2 deletions.
12 changes: 12 additions & 0 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2215,6 +2215,12 @@ 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 =
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
Expand All @@ -2223,6 +2229,12 @@ 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 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
Expand Down
20 changes: 19 additions & 1 deletion library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* +- MatchCase
* +- TypeBounds
* +- NoPrefix
*
* +- MethodTypeKind -+- Contextual
* +- Implicit
* +- Plain
*
* +- Selector -+- SimpleSelector
* +- RenameSelector
Expand Down Expand Up @@ -3234,6 +3238,15 @@ 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`. */
enum MethodTypeKind:
/** Represents a parameter list without any implicitness of parameters, like (x1: X1, x2: X2, ...) */
case Plain
/** Represents a parameter list with implicit parameters, like `(implicit X1, ..., Xn)`, `(using X1, ..., Xn)`, `(using x1: X1, ..., xn: Xn)` */
case Implicit
/** Represents a parameter list of a contextual method, like `(using X1, ..., Xn)` or `(using x1: X1, ..., xn: Xn)` */
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

Expand All @@ -3246,6 +3259,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)
}

Expand All @@ -3255,8 +3269,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
Expand Down
7 changes: 6 additions & 1 deletion project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
64 changes: 64 additions & 0 deletions tests/run-macros/reflect-method-type-kind/macro_1.scala
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions tests/run-macros/reflect-method-type-kind/test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Test:
def main(args: Array[String]): Unit =
Macro.macroCall()

0 comments on commit 00802ab

Please sign in to comment.