Skip to content

Commit

Permalink
Introduce MethodTypeKind to quotes reflection API
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. The MethodType unapply is kept as it was for source
compatibility, instead users are encouraged to use isImplicit and
isContextual methods.
  • Loading branch information
jchyb committed Apr 24, 2024
1 parent 3cad257 commit 4eff748
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 2 deletions.
11 changes: 11 additions & 0 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand All @@ -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
Expand All @@ -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
Expand Down
25 changes: 24 additions & 1 deletion library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* +- MatchCase
* +- TypeBounds
* +- NoPrefix
*
* +- MethodTypeKind
*
* +- Selector -+- SimpleSelector
* +- RenameSelector
Expand Down Expand Up @@ -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

Expand All @@ -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)
}

Expand All @@ -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
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 4eff748

Please sign in to comment.