Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce MethodTypeKind to quotes reflection API #20249

Merged
merged 2 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
nicolasstucki marked this conversation as resolved.
Show resolved Hide resolved
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()