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

Add reflect TypeLambda.paramVariances #17568

Merged
merged 2 commits into from
Oct 13, 2023
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
3 changes: 3 additions & 0 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2246,6 +2246,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
extension (self: TypeLambda)
def param(idx: Int): TypeRepr = self.newParamRef(idx)
def paramBounds: List[TypeBounds] = self.paramInfos
def paramVariances: List[Flags] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having this just on TypeLambda is a bit limiting since classes also have parameter with variance annotations, it would be nice if the same API worked for TypeLambda, TypeRef of classes and TypeRef of parameterized type aliases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added Symbol.paramVariance and added tests for all these cases. I did not add it on TypeRef because it is more consistent if we go to the definition of the class and ask for the variances on it.

self.typeParams.map(_.paramVariance)
end extension
end TypeLambdaMethods

Expand Down Expand Up @@ -2754,6 +2756,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
}

def isTypeParam: Boolean = self.isTypeParam
def paramVariance: Flags = self.paramVariance
def signature: Signature = self.signature
def moduleClass: Symbol = self.denot.moduleClass
def companionClass: Symbol = self.denot.companionClass
Expand Down
17 changes: 17 additions & 0 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3258,8 +3258,16 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
/** Extension methods of `TypeLambda` */
trait TypeLambdaMethods:
extension (self: TypeLambda)
/** Reference to the i-th parameter */
def param(idx: Int) : TypeRepr
/** Type bounds of the i-th parameter */
def paramBounds: List[TypeBounds]
/** Variance flags for the i-th parameter
*
* Variance flags can be one of `Flags.{Covariant, Contravariant, EmptyFlags}`.
*/
@experimental
def paramVariances: List[Flags]
smarter marked this conversation as resolved.
Show resolved Hide resolved
end extension
end TypeLambdaMethods

Expand Down Expand Up @@ -4078,8 +4086,17 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
/** Fields of a case class type -- only the ones declared in primary constructor */
def caseFields: List[Symbol]

/** Is this the symbol of a type parameter */
def isTypeParam: Boolean

/** Variance flags for of this type parameter.
*
* Variance flags can be one of `Flags.{Covariant, Contravariant, EmptyFlags}`.
* If this is not the symbol of a type parameter the result is `Flags.EmptyFlags`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we simply assert that this is a type parameter to prevent misinterpreting EmptyFlags as Invariant on things which aren't type parameters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not show the distinction between type and term flags in the reflection API.

*/
@experimental
def paramVariance: Flags

/** Signature of this definition */
def signature: Signature

Expand Down
67 changes: 67 additions & 0 deletions tests/run-macros/i16734a.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K1Inv[F]
F
A

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K1Cov[F]
F
+A

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K1Con[F]
F
-A

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2InvInv[F]
F
A, B

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2InvCov[F]
F
A, +B

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2InvCon[F]
F
A, -B

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2CovInv[F]
F
+A, B

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2CovCov[F]
F
+A, +B

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2CovCon[F]
F
+A, -B

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2ConInv[F]
F
-A, B

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2ConCov[F]
F
-A, +B

[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2ConCon[F]
F
-A, -B

[G >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any, C >: scala.Nothing <: scala.Any, D >: scala.Nothing <: [X1 >: scala.Nothing <: scala.Any, Y1 >: scala.Nothing <: scala.Any, Z1 >: scala.Nothing <: scala.Any] =>> scala.Any, E >: scala.Nothing <: [X2 >: scala.Nothing <: scala.Any, Y2 >: scala.Nothing <: scala.Any, Z2 >: scala.Nothing <: scala.Any] =>> scala.Any, F >: scala.Nothing <: [X3 >: scala.Nothing <: scala.Any, Y3 >: scala.Nothing <: scala.Any, Z3 >: scala.Nothing <: scala.Any] =>> scala.Any] =>> scala.Any] =>> KFunky[G]
G
A, +B, -C, D, +E, -F
X1, +Y1, -Z1
X2, +Y2, -Z2
X3, +Y3, -Z3

[A >: scala.Nothing <: scala.Any, F >: scala.Nothing <: [B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> F[A]
A, +F
B

[A >: scala.Nothing <: scala.Any, F >: scala.Nothing <: [B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> F[A]
+A, +F
+B

[A >: scala.Nothing <: scala.Any, F >: scala.Nothing <: [B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> F[A]
-A, +F
-B

21 changes: 21 additions & 0 deletions tests/run-macros/i16734a/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import scala.quoted.*

inline def variances[A <: AnyKind]: String =
${variancesImpl[A]}

def variancesImpl[A <: AnyKind: Type](using Quotes): Expr[String] =
import quotes.reflect.*
def loop(tpe: TypeRepr): List[String] =
tpe match
case tpe: TypeLambda =>
tpe.paramNames.zip(tpe.paramVariances).map { (name, variance) =>
if variance == Flags.Covariant then "+" + name
else if variance == Flags.Contravariant then "-" + name
else name
}.mkString(", ") :: tpe.paramTypes.flatMap(loop)
case tpe: TypeBounds =>
loop(tpe.low) ++ loop(tpe.hi)
case _ =>
Nil
val res = (Type.show[A] :: loop(TypeRepr.of[A])).mkString("", "\n", "\n")
Expr(res)
34 changes: 34 additions & 0 deletions tests/run-macros/i16734a/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
trait K1Inv[F[A]]
trait K1Cov[F[+A]]
trait K1Con[F[-A]]

trait K2InvInv[F[A, B]]
trait K2InvCov[F[A, +B]]
trait K2InvCon[F[A, -B]]
trait K2CovInv[F[+A, B]]
trait K2CovCov[F[+A, +B]]
trait K2CovCon[F[+A, -B]]
trait K2ConInv[F[-A, B]]
trait K2ConCov[F[-A, +B]]
trait K2ConCon[F[-A, -B]]


trait KFunky[G[A, +B, -C, D[X1, +Y1, -Z1], +E[X2, +Y2, -Z2], -F[X3, +Y3, -Z3]]]

@main def Test =
println(variances[K1Inv])
println(variances[K1Cov])
println(variances[K1Con])
println(variances[K2InvInv])
println(variances[K2InvCov])
println(variances[K2InvCon])
println(variances[K2CovInv])
println(variances[K2CovCov])
println(variances[K2CovCon])
println(variances[K2ConInv])
println(variances[K2ConCov])
println(variances[K2ConCon])
println(variances[KFunky])
println(variances[[A, F[B]] =>> F[A]])
println(variances[[A, F[+B]] =>> F[A]])
println(variances[[A, F[-B]] =>> F[A]])
36 changes: 36 additions & 0 deletions tests/run-macros/i16734b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
type F1Inv
A

type F1Cov
+A

type F1Con
-A

type F2InvInv
A, B

type F2InvCov
A, +B

type F2InvCon
A, -B

type F2CovInv
+A, B

type F2CovCov
+A, +B

type F2CovCon
+A, -B

type F2ConInv
-A, B

type F2ConCov
-A, +B

type F2ConCon
-A, -B

14 changes: 14 additions & 0 deletions tests/run-macros/i16734b/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import scala.quoted.*

inline def typeVariances[A <: AnyKind]: String =
${variancesImpl[A]}

def variancesImpl[A <: AnyKind: Type](using Quotes): Expr[String] =
import quotes.reflect.*
val TypeBounds(_, tl: TypeLambda) = TypeRepr.of[A].typeSymbol.info: @unchecked
val variances = tl.paramNames.zip(tl.paramVariances).map { (name, variance) =>
if variance == Flags.Covariant then "+" + name
else if variance == Flags.Contravariant then "-" + name
else name
}.mkString(", ")
Expr(TypeRepr.of[A].typeSymbol.toString() + "\n" + variances + "\n")
27 changes: 27 additions & 0 deletions tests/run-macros/i16734b/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
type F1Inv[A]
type F1Cov[+A]
type F1Con[-A]

type F2InvInv[A, B]
type F2InvCov[A, +B]
type F2InvCon[A, -B]
type F2CovInv[+A, B]
type F2CovCov[+A, +B]
type F2CovCon[+A, -B]
type F2ConInv[-A, B]
type F2ConCov[-A, +B]
type F2ConCon[-A, -B]

@main def Test =
println(typeVariances[F1Inv])
println(typeVariances[F1Cov])
println(typeVariances[F1Con])
println(typeVariances[F2InvInv])
println(typeVariances[F2InvCov])
println(typeVariances[F2InvCon])
println(typeVariances[F2CovInv])
println(typeVariances[F2CovCov])
println(typeVariances[F2CovCon])
println(typeVariances[F2ConInv])
println(typeVariances[F2ConCov])
println(typeVariances[F2ConCon])
36 changes: 36 additions & 0 deletions tests/run-macros/i16734c.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class C1Inv
A

class C1Cov
+A

class C1Con
-A

class C2InvInv
A, B

class C2InvCov
A, +B

class C2InvCon
A, -B

class C2CovInv
+A, B

class C2CovCov
+A, +B

class C2CovCon
+A, -B

class C2ConInv
-A, B

class C2ConCov
-A, +B

class C2ConCon
-A, -B

14 changes: 14 additions & 0 deletions tests/run-macros/i16734c/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import scala.quoted.*

inline def classVariances[A <: AnyKind]: String =
${variancesImpl[A]}

def variancesImpl[A <: AnyKind: Type](using Quotes): Expr[String] =
import quotes.reflect.*
val variances = TypeRepr.of[A].typeSymbol.typeMembers.filter(_.isTypeParam).map { sym =>
if sym.paramVariance == Flags.Covariant then "+" + sym.name
else if sym.paramVariance == Flags.Contravariant then "-" + sym.name
else sym.name
}
val res = variances.mkString(TypeRepr.of[A].typeSymbol.toString + "\n", ", ", "\n")
Expr(res)
27 changes: 27 additions & 0 deletions tests/run-macros/i16734c/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class C1Inv[A] { type T }
class C1Cov[+A] { type T }
class C1Con[-A] { type T }

class C2InvInv[A, B] { type T }
class C2InvCov[A, +B] { type T }
class C2InvCon[A, -B] { type T }
class C2CovInv[+A, B] { type T }
class C2CovCov[+A, +B] { type T }
class C2CovCon[+A, -B] { type T }
class C2ConInv[-A, B] { type T }
class C2ConCov[-A, +B] { type T }
class C2ConCon[-A, -B] { type T }

@main def Test =
println(classVariances[C1Inv])
println(classVariances[C1Cov])
println(classVariances[C1Con])
println(classVariances[C2InvInv])
println(classVariances[C2InvCov])
println(classVariances[C2InvCon])
println(classVariances[C2CovInv])
println(classVariances[C2CovCov])
println(classVariances[C2CovCon])
println(classVariances[C2ConInv])
println(classVariances[C2ConCov])
println(classVariances[C2ConCon])
2 changes: 2 additions & 0 deletions tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ val experimentalDefinitionInLibrary = Set(
"scala.annotation.init$.region",

//// New APIs: Quotes
"scala.quoted.Quotes.reflectModule.TypeLambdaMethods.paramVariances",
"scala.quoted.Quotes.reflectModule.SymbolMethods.paramVariance",
// Can be stabilized in 3.4.0 (unsure) or later
"scala.quoted.Quotes.reflectModule.CompilationInfoModule.XmacroSettings",
"scala.quoted.Quotes.reflectModule.FlagsModule.JavaAnnotation",
Expand Down