-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
||
|
@@ -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`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
||
|
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 | ||
|
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) |
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]]) |
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 | ||
|
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") |
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]) |
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 | ||
|
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) |
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]) |
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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 onTypeRef
because it is more consistent if we go to the definition of the class and ask for the variances on it.