Skip to content

Commit 40cee39

Browse files
Add reflect TypeLambda.paramVariances (#17568)
Fixes #16734
2 parents 242ba21 + f86a682 commit 40cee39

File tree

12 files changed

+298
-0
lines changed

12 files changed

+298
-0
lines changed

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

+3
Original file line numberDiff line numberDiff line change
@@ -2246,6 +2246,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
22462246
extension (self: TypeLambda)
22472247
def param(idx: Int): TypeRepr = self.newParamRef(idx)
22482248
def paramBounds: List[TypeBounds] = self.paramInfos
2249+
def paramVariances: List[Flags] =
2250+
self.typeParams.map(_.paramVariance)
22492251
end extension
22502252
end TypeLambdaMethods
22512253

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

27562758
def isTypeParam: Boolean = self.isTypeParam
2759+
def paramVariance: Flags = self.paramVariance
27572760
def signature: Signature = self.signature
27582761
def moduleClass: Symbol = self.denot.moduleClass
27592762
def companionClass: Symbol = self.denot.companionClass

library/src/scala/quoted/Quotes.scala

+17
Original file line numberDiff line numberDiff line change
@@ -3258,8 +3258,16 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
32583258
/** Extension methods of `TypeLambda` */
32593259
trait TypeLambdaMethods:
32603260
extension (self: TypeLambda)
3261+
/** Reference to the i-th parameter */
32613262
def param(idx: Int) : TypeRepr
3263+
/** Type bounds of the i-th parameter */
32623264
def paramBounds: List[TypeBounds]
3265+
/** Variance flags for the i-th parameter
3266+
*
3267+
* Variance flags can be one of `Flags.{Covariant, Contravariant, EmptyFlags}`.
3268+
*/
3269+
@experimental
3270+
def paramVariances: List[Flags]
32633271
end extension
32643272
end TypeLambdaMethods
32653273

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

4089+
/** Is this the symbol of a type parameter */
40814090
def isTypeParam: Boolean
40824091

4092+
/** Variance flags for of this type parameter.
4093+
*
4094+
* Variance flags can be one of `Flags.{Covariant, Contravariant, EmptyFlags}`.
4095+
* If this is not the symbol of a type parameter the result is `Flags.EmptyFlags`.
4096+
*/
4097+
@experimental
4098+
def paramVariance: Flags
4099+
40834100
/** Signature of this definition */
40844101
def signature: Signature
40854102

tests/run-macros/i16734a.check

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K1Inv[F]
2+
F
3+
A
4+
5+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K1Cov[F]
6+
F
7+
+A
8+
9+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K1Con[F]
10+
F
11+
-A
12+
13+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2InvInv[F]
14+
F
15+
A, B
16+
17+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2InvCov[F]
18+
F
19+
A, +B
20+
21+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2InvCon[F]
22+
F
23+
A, -B
24+
25+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2CovInv[F]
26+
F
27+
+A, B
28+
29+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2CovCov[F]
30+
F
31+
+A, +B
32+
33+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2CovCon[F]
34+
F
35+
+A, -B
36+
37+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2ConInv[F]
38+
F
39+
-A, B
40+
41+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2ConCov[F]
42+
F
43+
-A, +B
44+
45+
[F >: scala.Nothing <: [A >: scala.Nothing <: scala.Any, B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> K2ConCon[F]
46+
F
47+
-A, -B
48+
49+
[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]
50+
G
51+
A, +B, -C, D, +E, -F
52+
X1, +Y1, -Z1
53+
X2, +Y2, -Z2
54+
X3, +Y3, -Z3
55+
56+
[A >: scala.Nothing <: scala.Any, F >: scala.Nothing <: [B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> F[A]
57+
A, +F
58+
B
59+
60+
[A >: scala.Nothing <: scala.Any, F >: scala.Nothing <: [B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> F[A]
61+
+A, +F
62+
+B
63+
64+
[A >: scala.Nothing <: scala.Any, F >: scala.Nothing <: [B >: scala.Nothing <: scala.Any] =>> scala.Any] =>> F[A]
65+
-A, +F
66+
-B
67+
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.quoted.*
2+
3+
inline def variances[A <: AnyKind]: String =
4+
${variancesImpl[A]}
5+
6+
def variancesImpl[A <: AnyKind: Type](using Quotes): Expr[String] =
7+
import quotes.reflect.*
8+
def loop(tpe: TypeRepr): List[String] =
9+
tpe match
10+
case tpe: TypeLambda =>
11+
tpe.paramNames.zip(tpe.paramVariances).map { (name, variance) =>
12+
if variance == Flags.Covariant then "+" + name
13+
else if variance == Flags.Contravariant then "-" + name
14+
else name
15+
}.mkString(", ") :: tpe.paramTypes.flatMap(loop)
16+
case tpe: TypeBounds =>
17+
loop(tpe.low) ++ loop(tpe.hi)
18+
case _ =>
19+
Nil
20+
val res = (Type.show[A] :: loop(TypeRepr.of[A])).mkString("", "\n", "\n")
21+
Expr(res)

tests/run-macros/i16734a/Test_2.scala

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
trait K1Inv[F[A]]
2+
trait K1Cov[F[+A]]
3+
trait K1Con[F[-A]]
4+
5+
trait K2InvInv[F[A, B]]
6+
trait K2InvCov[F[A, +B]]
7+
trait K2InvCon[F[A, -B]]
8+
trait K2CovInv[F[+A, B]]
9+
trait K2CovCov[F[+A, +B]]
10+
trait K2CovCon[F[+A, -B]]
11+
trait K2ConInv[F[-A, B]]
12+
trait K2ConCov[F[-A, +B]]
13+
trait K2ConCon[F[-A, -B]]
14+
15+
16+
trait KFunky[G[A, +B, -C, D[X1, +Y1, -Z1], +E[X2, +Y2, -Z2], -F[X3, +Y3, -Z3]]]
17+
18+
@main def Test =
19+
println(variances[K1Inv])
20+
println(variances[K1Cov])
21+
println(variances[K1Con])
22+
println(variances[K2InvInv])
23+
println(variances[K2InvCov])
24+
println(variances[K2InvCon])
25+
println(variances[K2CovInv])
26+
println(variances[K2CovCov])
27+
println(variances[K2CovCon])
28+
println(variances[K2ConInv])
29+
println(variances[K2ConCov])
30+
println(variances[K2ConCon])
31+
println(variances[KFunky])
32+
println(variances[[A, F[B]] =>> F[A]])
33+
println(variances[[A, F[+B]] =>> F[A]])
34+
println(variances[[A, F[-B]] =>> F[A]])

tests/run-macros/i16734b.check

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
type F1Inv
2+
A
3+
4+
type F1Cov
5+
+A
6+
7+
type F1Con
8+
-A
9+
10+
type F2InvInv
11+
A, B
12+
13+
type F2InvCov
14+
A, +B
15+
16+
type F2InvCon
17+
A, -B
18+
19+
type F2CovInv
20+
+A, B
21+
22+
type F2CovCov
23+
+A, +B
24+
25+
type F2CovCon
26+
+A, -B
27+
28+
type F2ConInv
29+
-A, B
30+
31+
type F2ConCov
32+
-A, +B
33+
34+
type F2ConCon
35+
-A, -B
36+
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.quoted.*
2+
3+
inline def typeVariances[A <: AnyKind]: String =
4+
${variancesImpl[A]}
5+
6+
def variancesImpl[A <: AnyKind: Type](using Quotes): Expr[String] =
7+
import quotes.reflect.*
8+
val TypeBounds(_, tl: TypeLambda) = TypeRepr.of[A].typeSymbol.info: @unchecked
9+
val variances = tl.paramNames.zip(tl.paramVariances).map { (name, variance) =>
10+
if variance == Flags.Covariant then "+" + name
11+
else if variance == Flags.Contravariant then "-" + name
12+
else name
13+
}.mkString(", ")
14+
Expr(TypeRepr.of[A].typeSymbol.toString() + "\n" + variances + "\n")

tests/run-macros/i16734b/Test_2.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
type F1Inv[A]
2+
type F1Cov[+A]
3+
type F1Con[-A]
4+
5+
type F2InvInv[A, B]
6+
type F2InvCov[A, +B]
7+
type F2InvCon[A, -B]
8+
type F2CovInv[+A, B]
9+
type F2CovCov[+A, +B]
10+
type F2CovCon[+A, -B]
11+
type F2ConInv[-A, B]
12+
type F2ConCov[-A, +B]
13+
type F2ConCon[-A, -B]
14+
15+
@main def Test =
16+
println(typeVariances[F1Inv])
17+
println(typeVariances[F1Cov])
18+
println(typeVariances[F1Con])
19+
println(typeVariances[F2InvInv])
20+
println(typeVariances[F2InvCov])
21+
println(typeVariances[F2InvCon])
22+
println(typeVariances[F2CovInv])
23+
println(typeVariances[F2CovCov])
24+
println(typeVariances[F2CovCon])
25+
println(typeVariances[F2ConInv])
26+
println(typeVariances[F2ConCov])
27+
println(typeVariances[F2ConCon])

tests/run-macros/i16734c.check

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class C1Inv
2+
A
3+
4+
class C1Cov
5+
+A
6+
7+
class C1Con
8+
-A
9+
10+
class C2InvInv
11+
A, B
12+
13+
class C2InvCov
14+
A, +B
15+
16+
class C2InvCon
17+
A, -B
18+
19+
class C2CovInv
20+
+A, B
21+
22+
class C2CovCov
23+
+A, +B
24+
25+
class C2CovCon
26+
+A, -B
27+
28+
class C2ConInv
29+
-A, B
30+
31+
class C2ConCov
32+
-A, +B
33+
34+
class C2ConCon
35+
-A, -B
36+
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.quoted.*
2+
3+
inline def classVariances[A <: AnyKind]: String =
4+
${variancesImpl[A]}
5+
6+
def variancesImpl[A <: AnyKind: Type](using Quotes): Expr[String] =
7+
import quotes.reflect.*
8+
val variances = TypeRepr.of[A].typeSymbol.typeMembers.filter(_.isTypeParam).map { sym =>
9+
if sym.paramVariance == Flags.Covariant then "+" + sym.name
10+
else if sym.paramVariance == Flags.Contravariant then "-" + sym.name
11+
else sym.name
12+
}
13+
val res = variances.mkString(TypeRepr.of[A].typeSymbol.toString + "\n", ", ", "\n")
14+
Expr(res)

tests/run-macros/i16734c/Test_2.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class C1Inv[A] { type T }
2+
class C1Cov[+A] { type T }
3+
class C1Con[-A] { type T }
4+
5+
class C2InvInv[A, B] { type T }
6+
class C2InvCov[A, +B] { type T }
7+
class C2InvCon[A, -B] { type T }
8+
class C2CovInv[+A, B] { type T }
9+
class C2CovCov[+A, +B] { type T }
10+
class C2CovCon[+A, -B] { type T }
11+
class C2ConInv[-A, B] { type T }
12+
class C2ConCov[-A, +B] { type T }
13+
class C2ConCon[-A, -B] { type T }
14+
15+
@main def Test =
16+
println(classVariances[C1Inv])
17+
println(classVariances[C1Cov])
18+
println(classVariances[C1Con])
19+
println(classVariances[C2InvInv])
20+
println(classVariances[C2InvCov])
21+
println(classVariances[C2InvCon])
22+
println(classVariances[C2CovInv])
23+
println(classVariances[C2CovCov])
24+
println(classVariances[C2CovCon])
25+
println(classVariances[C2ConInv])
26+
println(classVariances[C2ConCov])
27+
println(classVariances[C2ConCon])

tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ val experimentalDefinitionInLibrary = Set(
6868
"scala.annotation.init$.region",
6969

7070
//// New APIs: Quotes
71+
"scala.quoted.Quotes.reflectModule.TypeLambdaMethods.paramVariances",
72+
"scala.quoted.Quotes.reflectModule.SymbolMethods.paramVariance",
7173
// Can be stabilized in 3.4.0 (unsure) or later
7274
"scala.quoted.Quotes.reflectModule.CompilationInfoModule.XmacroSettings",
7375
"scala.quoted.Quotes.reflectModule.FlagsModule.JavaAnnotation",

0 commit comments

Comments
 (0)