From 5fe41faf50c2e33d44bd078e4036e0a1ea388942 Mon Sep 17 00:00:00 2001 From: Patrick Grandjean Date: Sat, 30 Jul 2022 22:53:24 -0700 Subject: [PATCH 1/2] [fix] extract annotations with type parameter --- .../src/main/scala/shapeless/annotation.scala | 2 +- .../src/test/scala/shapeless/annotation.scala | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/shapeless/annotation.scala b/core/src/main/scala/shapeless/annotation.scala index 3e481203c..3c6d3103b 100644 --- a/core/src/main/scala/shapeless/annotation.scala +++ b/core/src/main/scala/shapeless/annotation.scala @@ -374,7 +374,7 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { val tpe = weakTypeOf[T] val annTreeOpts = getAnnotationTreeOptions(tpe, typeAnnotation).map { list => - list.find(_._1 =:= annTpe).map(_._2) + list.find(_._1.erasure =:= annTpe.erasure).map(_._2) } val wrapTpeTrees = annTreeOpts.map { diff --git a/core/src/test/scala/shapeless/annotation.scala b/core/src/test/scala/shapeless/annotation.scala index 24e761876..d3e43770c 100644 --- a/core/src/test/scala/shapeless/annotation.scala +++ b/core/src/test/scala/shapeless/annotation.scala @@ -25,6 +25,7 @@ object AnnotationTestsDefinitions { case class First() extends saAnnotation case class Second(i: Int, s: String) extends saAnnotation case class Third(c: Char) extends saAnnotation + case class Fourth[T](t: T) extends saAnnotation case class Other() extends saAnnotation case class Last(b: Boolean) extends saAnnotation @@ -58,15 +59,17 @@ object AnnotationTestsDefinitions { case class CC3( @First i: Int, s: String, - @Second(2, "b") @Third('c') ob: Option[Boolean] + @Second(2, "b") @Third('c') ob: Option[Boolean], + @Fourth(4) c: Char ) case class CC4( i: Int @First, s: String, - ob: Option[Boolean] @Second(2, "b") @Third('c') + ob: Option[Boolean] @Second(2, "b") @Third('c'), + c: Char @Fourth(4) ) - + type PosInt = Int @First type Email = String @Third('c') case class User(age: PosInt, email: Email) @@ -192,14 +195,14 @@ class AnnotationTests { def invalidTypeAnnotations: Unit = { illTyped(" TypeAnnotations[Dummy, CC2] ", "could not find implicit value for parameter annotations: .*") illTyped(" TypeAnnotations[Dummy, Base] ", "could not find implicit value for parameter annotations: .*") - illTyped(" TypeAnnotations[Second, Dummy] ", "could not find implicit value for parameter annotations: .*") + illTyped(" TypeAnnotations[Second, Dummy] ", "could not find implicit value for parameter annotations: .*") } @Test def allAnnotations: Unit = { val cc = AllAnnotations[CC3].apply() - typed[(First :: HNil) :: HNil :: (Second :: Third :: HNil) :: HNil](cc) - assert(cc == (First() :: HNil) :: HNil :: (Second(2, "b") :: Third('c') :: HNil) :: HNil) + typed[(First :: HNil) :: HNil :: (Second :: Third :: HNil) :: (Fourth[_] :: HNil) :: HNil](cc) + assert(cc == (First() :: HNil) :: HNil :: (Second(2, "b") :: Third('c') :: HNil) :: (Fourth(4) :: HNil) :: HNil) val st = AllAnnotations[Base].apply() typed[(First :: HNil) :: (Second :: Third :: HNil) :: HNil](st) @@ -211,9 +214,9 @@ class AnnotationTests { typed[(First :: HNil) :: (Second :: Third :: HNil) :: HNil](st) val cc = AllTypeAnnotations[CC4].apply() // case class - typed[(First :: HNil) :: HNil :: (Second :: Third :: HNil) :: HNil](cc) - assert(cc == (First() :: HNil) :: HNil :: (Second(2, "b") :: Third('c') :: HNil) :: HNil) - + typed[(First :: HNil) :: HNil :: (Second :: Third :: HNil) :: (Fourth[_] :: HNil) :: HNil](cc) + assert(cc == (First() :: HNil) :: HNil :: (Second(2, "b") :: Third('c') :: HNil) :: (Fourth(4) :: HNil) :: HNil) + val user = AllTypeAnnotations[User].apply() // type refs typed[(First :: HNil) :: (Third :: HNil) :: HNil](user) assert(user == (First() :: HNil) :: (Third('c') :: HNil) :: HNil) From 614bc4e3c2cfb3a90509d57eea6d44e74be83bbc Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Wed, 7 Sep 2022 08:27:15 +0200 Subject: [PATCH 2/2] Extract all subtype annotations --- .../src/main/scala/shapeless/annotation.scala | 109 ++++++------------ .../src/test/scala/shapeless/annotation.scala | 25 ++-- 2 files changed, 55 insertions(+), 79 deletions(-) diff --git a/core/src/main/scala/shapeless/annotation.scala b/core/src/main/scala/shapeless/annotation.scala index 3c6d3103b..34a542b93 100644 --- a/core/src/main/scala/shapeless/annotation.scala +++ b/core/src/main/scala/shapeless/annotation.scala @@ -118,7 +118,8 @@ object Annotations { def apply() = annotations } - implicit def materialize[A, T, Out <: HList]: Aux[A, T, Out] = macro AnnotationMacros.materializeVariableAnnotations[A, T, Out] + implicit def materialize[A, T, Out <: HList]: Aux[A, T, Out] = + macro AnnotationMacros.materializeVariableAnnotations[A, T, Out] } /** @@ -172,7 +173,8 @@ object TypeAnnotations { def apply() = annotations } - implicit def materialize[A, T, Out <: HList]: Aux[A, T, Out] = macro AnnotationMacros.materializeTypeAnnotations[A, T, Out] + implicit def materialize[A, T, Out <: HList]: Aux[A, T, Out] = + macro AnnotationMacros.materializeTypeAnnotations[A, T, Out] } /** @@ -226,7 +228,8 @@ object AllAnnotations { def apply(): Out = annotations } - implicit def materialize[T, Out <: HList]: Aux[T, Out] = macro AnnotationMacros.materializeAllVariableAnnotations[T, Out] + implicit def materialize[T, Out <: HList]: Aux[T, Out] = + macro AnnotationMacros.materializeAllVariableAnnotations[T, Out] } /** @@ -280,7 +283,8 @@ object AllTypeAnnotations { def apply(): Out = annotations } - implicit def materialize[T, Out <: HList]: Aux[T, Out] = macro AnnotationMacros.materializeAllTypeAnnotations[T, Out] + implicit def materialize[T, Out <: HList]: Aux[T, Out] = + macro AnnotationMacros.materializeAllTypeAnnotations[T, Out] } class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { @@ -290,9 +294,9 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { def someTpe: Type = typeOf[Some[_]].typeConstructor def noneTpe: Type = typeOf[None.type] - private def annotation = objectRef[shapeless.Annotation.type] - private def some = objectRef[Some.type] - private def none = objectRef[None.type] + private def someVal = objectRef[Some.type] + private def noneVal = objectRef[None.type] + private def annotationVal = objectRef[shapeless.Annotation.type] /** * FIXME Most of the content of this method is cut-n-pasted from generic.scala @@ -300,41 +304,31 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { * @return The AST of the `tpe` constructor. */ def construct(tpe: Type): List[Tree] => Tree = { - // FIXME Cut-n-pasted from generic.scala val sym = tpe.typeSymbol - val isCaseClass = sym.asClass.isCaseClass - def hasNonGenericCompanionMember(name: String): Boolean = { - val mSym = sym.companion.typeSignature.member(TermName(name)) - mSym != NoSymbol && !isNonGeneric(mSym) + def hasCompanionMember(name: String) = { + val member = sym.companion.typeSignature.member(TermName(name)) + member != NoSymbol && !isNonGeneric(member) } - if(isCaseClass || hasNonGenericCompanionMember("apply")) - args => q"${companionRef(tpe)}(..$args)" - else - args => q"new $tpe(..$args)" + val useCompanion = sym.asClass.isCaseClass || hasCompanionMember("apply") + if (useCompanion) args => q"${companionRef(tpe)}(..$args)" else args => q"new $tpe(..$args)" } private def getAnnotation[A: WeakTypeTag, T: WeakTypeTag]: Option[Tree] = { val annTpe = weakTypeOf[A] - - if (!isProduct(annTpe)) - abort(s"$annTpe is not a case class-like type") - - val construct0 = construct(annTpe) - val tpe = weakTypeOf[T] - + if (!isProduct(annTpe)) abort(s"$annTpe is not a case class-like type") + val constructor = construct(annTpe) tpe.typeSymbol.annotations.collectFirst { - case ann if ann.tree.tpe =:= annTpe => construct0(ann.tree.children.tail) + case ann if ann.tree.tpe =:= annTpe => constructor(ann.tree.children.tail) } } def materializeAnnotation[A: WeakTypeTag, T: WeakTypeTag]: Tree = { val annTpe = weakTypeOf[A] val tpe = weakTypeOf[T] - getAnnotation[A, T] match { - case Some(annTree) => q"$annotation.mkAnnotation[$annTpe, $tpe]($annTree)" + case Some(annTree) => q"$annotationVal.mkAnnotation[$annTpe, $tpe]($annTree)" case None => abort(s"No $annTpe annotation found on $tpe") } } @@ -342,10 +336,9 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { def materializeAnnotationOptional[A: WeakTypeTag, T: WeakTypeTag]: Tree = { val optAnnTpe = appliedType(optionTpe, weakTypeOf[A]) val tpe = weakTypeOf[T] - getAnnotation[A, T] match { - case Some(annTree) => q"$annotation.mkAnnotation[$optAnnTpe, $tpe]($some($annTree))" - case None => q"$annotation.mkAnnotation[$optAnnTpe, $tpe]($none)" + case Some(annTree) => q"$annotationVal.mkAnnotation[$optAnnTpe, $tpe]($someVal($annTree))" + case None => q"$annotationVal.mkAnnotation[$optAnnTpe, $tpe]($noneVal)" } } @@ -354,7 +347,7 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { def materializeAllVariableAnnotations[T: WeakTypeTag, Out: WeakTypeTag]: Tree = materializeAllAnnotations[T, Out](typeAnnotation = false) - + def materializeTypeAnnotations[A: WeakTypeTag, T: WeakTypeTag, Out: WeakTypeTag]: Tree = materializeAnnotations[A, T, Out](typeAnnotation = true) @@ -364,54 +357,27 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { @deprecated("Use materializeVariableAnnotations instead", "2.3.6") def materializeAnnotations[A: WeakTypeTag, T: WeakTypeTag, Out: WeakTypeTag]: Tree = materializeVariableAnnotations[A, T, Out] - + def materializeAnnotations[A: WeakTypeTag, T: WeakTypeTag, Out: WeakTypeTag](typeAnnotation: Boolean): Tree = { val annTpe = weakTypeOf[A] - - if (!isProduct(annTpe)) - abort(s"$annTpe is not a case class-like type") - val tpe = weakTypeOf[T] - - val annTreeOpts = getAnnotationTreeOptions(tpe, typeAnnotation).map { list => - list.find(_._1.erasure =:= annTpe.erasure).map(_._2) - } - - val wrapTpeTrees = annTreeOpts.map { - case Some(annTree) => appliedType(someTpe, annTpe) -> q"_root_.scala.Some($annTree)" - case None => noneTpe -> q"_root_.scala.None" - } - - val outTpe = mkHListTpe(wrapTpeTrees.map { case (aTpe, _) => aTpe }) - val outTree = wrapTpeTrees.foldRight(q"_root_.shapeless.HNil": Tree) { - case ((_, bound), acc) => pq"_root_.shapeless.::($bound, $acc)" - } - - if (typeAnnotation) q"_root_.shapeless.TypeAnnotations.mkAnnotations[$annTpe, $tpe, $outTpe]($outTree)" - else q"_root_.shapeless.Annotations.mkAnnotations[$annTpe, $tpe, $outTpe]($outTree)" + if (!isProduct(annTpe)) abort(s"$annTpe is not a case class-like type") + val (annTypes, annTrees) = getAnnotationTreeOptions(tpe, typeAnnotation).map(_.find(_._1 <:< annTpe) match { + case Some((annTpe, annTree)) => appliedType(someTpe, annTpe) -> q"$someVal($annTree)" + case None => noneTpe -> noneVal + }).unzip + val tc = if (typeAnnotation) objectRef[TypeAnnotations.type] else objectRef[Annotations.type] + q"$tc.mkAnnotations[$annTpe, $tpe, ${mkHListTpe(annTypes)}](${mkHListValue(annTrees)})" } def materializeAllAnnotations[T: WeakTypeTag, Out: WeakTypeTag](typeAnnotation: Boolean): Tree = { val tpe = weakTypeOf[T] - val annTreeOpts = getAnnotationTreeOptions(tpe, typeAnnotation) - - val wrapTpeTrees = annTreeOpts.map { - case Nil => - mkHListTpe(Nil) -> q"(_root_.shapeless.HNil)" - case list => - mkHListTpe(list.map(_._1)) -> list.foldRight(q"_root_.shapeless.HNil": Tree) { - case ((_, bound), acc) => pq"_root_.shapeless.::($bound, $acc)" - } - } - - val outTpe = mkHListTpe(wrapTpeTrees.map { case (aTpe, _) => aTpe }) - val outTree = wrapTpeTrees.foldRight(q"_root_.shapeless.HNil": Tree) { - case ((_, bound), acc) => - pq"_root_.shapeless.::($bound, $acc)" - } - - if (typeAnnotation) q"_root_.shapeless.AllTypeAnnotations.mkAnnotations[$tpe, $outTpe]($outTree)" - else q"_root_.shapeless.AllAnnotations.mkAnnotations[$tpe, $outTpe]($outTree)" + val (annTypes, annTrees) = getAnnotationTreeOptions(tpe, typeAnnotation).map { annotations => + val (types, trees) = annotations.unzip + mkHListTpe(types) -> mkHListValue(trees) + }.unzip + val tc = if (typeAnnotation) objectRef[AllTypeAnnotations.type] else objectRef[AllAnnotations.type] + q"$tc.mkAnnotations[$tpe, ${mkHListTpe(annTypes)}](${mkHListValue(annTrees)})" } private def getAnnotationTreeOptions(tpe: Type, typeAnnotation: Boolean): List[List[(Type, Tree)]] = { @@ -456,5 +422,4 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { if (tpe) fromType(s.typeSignature) else s.annotations } - } diff --git a/core/src/test/scala/shapeless/annotation.scala b/core/src/test/scala/shapeless/annotation.scala index d3e43770c..2b315e823 100644 --- a/core/src/test/scala/shapeless/annotation.scala +++ b/core/src/test/scala/shapeless/annotation.scala @@ -100,18 +100,18 @@ class AnnotationTests { def optionalAnnotation: Unit = { { val other = Annotation[Option[Other], CC].apply() - assert(other == Some(Other())) + assert(other.contains(Other())) val last = Annotation[Option[Last], Something].apply() - assert(last == Some(Last(true))) + assert(last.contains(Last(true))) } { val other: Option[Other] = Annotation[Option[Other], Something].apply() - assert(other == None) + assert(other.isEmpty) val last: Option[Last] = Annotation[Option[Last], CC].apply() - assert(last == None) + assert(last.isEmpty) } } @@ -131,6 +131,9 @@ class AnnotationTests { val second: None.type :: None.type :: Some[Second] :: HNil = Annotations[Second, CC].apply() assert(second == None :: None :: Some(Second(2, "b")) :: HNil) + val fourth: None.type :: None.type :: None.type :: Some[Fourth[Int]] :: HNil = Annotations[Fourth[_], CC3].apply() + assert(fourth == None :: None :: None :: Some(Fourth(4)) :: HNil) + val unused: None.type :: None.type :: None.type :: HNil = Annotations[Unused, CC].apply() assert(unused == None :: None :: None :: HNil) @@ -148,6 +151,9 @@ class AnnotationTests { val second = Annotations[Second, CC].apply() assert(second == None :: None :: Some(Second(2, "b")) :: HNil) + val fourth = Annotations[Fourth[_], CC3].apply() + assert(fourth == None :: None :: None :: Some(Fourth(4)) :: HNil) + val unused = Annotations[Unused, CC].apply() assert(unused == None :: None :: None :: HNil) @@ -175,6 +181,9 @@ class AnnotationTests { val second: None.type :: None.type :: Some[Second] :: HNil = TypeAnnotations[Second, CC2].apply() assert(second == None :: None :: Some(Second(2, "b")) :: HNil) + val fourth: None.type :: None.type :: None.type :: Some[Fourth[Int]] :: HNil = TypeAnnotations[Fourth[_], CC4].apply() + assert(fourth == None :: None :: None :: Some(Fourth(4)) :: HNil) + val unused: None.type :: None.type :: None.type :: HNil = TypeAnnotations[Unused, CC2].apply() assert(unused == None :: None :: None :: HNil) } @@ -186,6 +195,9 @@ class AnnotationTests { val second = TypeAnnotations[Second, CC2].apply() assert(second == None :: None :: Some(Second(2, "b")) :: HNil) + val fourth = TypeAnnotations[Fourth[_], CC4].apply() + assert(fourth == None :: None :: None :: Some(Fourth(4)) :: HNil) + val unused = TypeAnnotations[Unused, CC2].apply() assert(unused == None :: None :: None :: HNil) } @@ -201,7 +213,7 @@ class AnnotationTests { @Test def allAnnotations: Unit = { val cc = AllAnnotations[CC3].apply() - typed[(First :: HNil) :: HNil :: (Second :: Third :: HNil) :: (Fourth[_] :: HNil) :: HNil](cc) + typed[(First :: HNil) :: HNil :: (Second :: Third :: HNil) :: (Fourth[Int] :: HNil) :: HNil](cc) assert(cc == (First() :: HNil) :: HNil :: (Second(2, "b") :: Third('c') :: HNil) :: (Fourth(4) :: HNil) :: HNil) val st = AllAnnotations[Base].apply() @@ -214,12 +226,11 @@ class AnnotationTests { typed[(First :: HNil) :: (Second :: Third :: HNil) :: HNil](st) val cc = AllTypeAnnotations[CC4].apply() // case class - typed[(First :: HNil) :: HNil :: (Second :: Third :: HNil) :: (Fourth[_] :: HNil) :: HNil](cc) + typed[(First :: HNil) :: HNil :: (Second :: Third :: HNil) :: (Fourth[Int] :: HNil) :: HNil](cc) assert(cc == (First() :: HNil) :: HNil :: (Second(2, "b") :: Third('c') :: HNil) :: (Fourth(4) :: HNil) :: HNil) val user = AllTypeAnnotations[User].apply() // type refs typed[(First :: HNil) :: (Third :: HNil) :: HNil](user) assert(user == (First() :: HNil) :: (Third('c') :: HNil) :: HNil) } - }