From a2ab93036640a390841d496f0b839762165d1efb Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 24 May 2023 10:15:57 +0200 Subject: [PATCH 1/2] Add reflect `TypeLambda.paramVariances` Fixes #16734 --- .../quoted/runtime/impl/QuotesImpl.scala | 2 + library/src/scala/quoted/Quotes.scala | 8 +++ tests/run-macros/i16734a.check | 67 +++++++++++++++++++ tests/run-macros/i16734a/Macro_1.scala | 21 ++++++ tests/run-macros/i16734a/Test_2.scala | 34 ++++++++++ tests/run-macros/i16734b.check | 36 ++++++++++ tests/run-macros/i16734b/Macro_1.scala | 14 ++++ tests/run-macros/i16734b/Test_2.scala | 27 ++++++++ .../stdlibExperimentalDefinitions.scala | 1 + 9 files changed, 210 insertions(+) create mode 100644 tests/run-macros/i16734a.check create mode 100644 tests/run-macros/i16734a/Macro_1.scala create mode 100644 tests/run-macros/i16734a/Test_2.scala create mode 100644 tests/run-macros/i16734b.check create mode 100644 tests/run-macros/i16734b/Macro_1.scala create mode 100644 tests/run-macros/i16734b/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index bc4f84c147c8..14b4f0d8c12f 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -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] = + self.typeParams.map(_.paramVariance) end extension end TypeLambdaMethods diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index a998b2765571..3cac4e508e93 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -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] end extension end TypeLambdaMethods diff --git a/tests/run-macros/i16734a.check b/tests/run-macros/i16734a.check new file mode 100644 index 000000000000..730fe709b146 --- /dev/null +++ b/tests/run-macros/i16734a.check @@ -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 + diff --git a/tests/run-macros/i16734a/Macro_1.scala b/tests/run-macros/i16734a/Macro_1.scala new file mode 100644 index 000000000000..eedbe2d0e6a1 --- /dev/null +++ b/tests/run-macros/i16734a/Macro_1.scala @@ -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) diff --git a/tests/run-macros/i16734a/Test_2.scala b/tests/run-macros/i16734a/Test_2.scala new file mode 100644 index 000000000000..c6b02c145841 --- /dev/null +++ b/tests/run-macros/i16734a/Test_2.scala @@ -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]]) diff --git a/tests/run-macros/i16734b.check b/tests/run-macros/i16734b.check new file mode 100644 index 000000000000..b894ffba4fc4 --- /dev/null +++ b/tests/run-macros/i16734b.check @@ -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 + diff --git a/tests/run-macros/i16734b/Macro_1.scala b/tests/run-macros/i16734b/Macro_1.scala new file mode 100644 index 000000000000..f1e8e12d308d --- /dev/null +++ b/tests/run-macros/i16734b/Macro_1.scala @@ -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") diff --git a/tests/run-macros/i16734b/Test_2.scala b/tests/run-macros/i16734b/Test_2.scala new file mode 100644 index 000000000000..f5481aaf96cf --- /dev/null +++ b/tests/run-macros/i16734b/Test_2.scala @@ -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]) diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index e68559dbfc66..f7fff9f1594b 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -68,6 +68,7 @@ val experimentalDefinitionInLibrary = Set( "scala.annotation.init$.region", //// New APIs: Quotes + "scala.quoted.Quotes.reflectModule.TypeLambdaMethods.paramVariances", // Can be stabilized in 3.4.0 (unsure) or later "scala.quoted.Quotes.reflectModule.CompilationInfoModule.XmacroSettings", "scala.quoted.Quotes.reflectModule.FlagsModule.JavaAnnotation", From f86a682157807475de182c56b31cdb26ac33d51f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 25 May 2023 10:06:09 +0200 Subject: [PATCH 2/2] Add reflect `Symbol.paramVariance` Related to #16734 --- .../quoted/runtime/impl/QuotesImpl.scala | 1 + library/src/scala/quoted/Quotes.scala | 9 +++++ tests/run-macros/i16734c.check | 36 +++++++++++++++++++ tests/run-macros/i16734c/Macro_1.scala | 14 ++++++++ tests/run-macros/i16734c/Test_2.scala | 27 ++++++++++++++ .../stdlibExperimentalDefinitions.scala | 1 + 6 files changed, 88 insertions(+) create mode 100644 tests/run-macros/i16734c.check create mode 100644 tests/run-macros/i16734c/Macro_1.scala create mode 100644 tests/run-macros/i16734c/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 14b4f0d8c12f..e27140459a49 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2756,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 diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 3cac4e508e93..3d5896e74f66 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -4086,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`. + */ + @experimental + def paramVariance: Flags + /** Signature of this definition */ def signature: Signature diff --git a/tests/run-macros/i16734c.check b/tests/run-macros/i16734c.check new file mode 100644 index 000000000000..2f33f1c83dd6 --- /dev/null +++ b/tests/run-macros/i16734c.check @@ -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 + diff --git a/tests/run-macros/i16734c/Macro_1.scala b/tests/run-macros/i16734c/Macro_1.scala new file mode 100644 index 000000000000..755aeb617d8c --- /dev/null +++ b/tests/run-macros/i16734c/Macro_1.scala @@ -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) diff --git a/tests/run-macros/i16734c/Test_2.scala b/tests/run-macros/i16734c/Test_2.scala new file mode 100644 index 000000000000..a49e43cba00c --- /dev/null +++ b/tests/run-macros/i16734c/Test_2.scala @@ -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]) diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index f7fff9f1594b..a01c71724b0e 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -69,6 +69,7 @@ val experimentalDefinitionInLibrary = Set( //// 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",