From 25fc9c302f07da8b1225afb99e949b58503889a5 Mon Sep 17 00:00:00 2001 From: Patrick Grandjean Date: Tue, 14 Dec 2021 19:07:26 -0800 Subject: [PATCH 1/9] Implementation of issue #55: Implement type annotations --- .gitignore | 3 + .../shapeless3/deriving/annotation.scala | 304 ++++++++++++++++-- .../shapeless3/deriving/annotation.scala | 106 ++++++ 3 files changed, 382 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 0863164..1a58805 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ local.sbt *.iml *.iws +# Ignore project files for VSCode +.vscode + # Ignore OS X metadata .DS_Store diff --git a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala index 593b679..77ee1f7 100644 --- a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala @@ -18,9 +18,10 @@ package shapeless3.deriving import scala.deriving.* import scala.quoted.* - import shapeless3.deriving.internals.* +import scala.annotation.tailrec + /** * Evidence that type `T` has annotation `A`, and provides an instance of the annotation. * @@ -121,9 +122,178 @@ object Annotations { } transparent inline implicit def mkAnnotations[A, T]: Annotations[A, T] = - ${ AnnotationMacros.mkAnnotations[A, T] } + ${ AnnotationMacros.mkVariableAnnotations[A, T] } +} + +/** + * Provides the type annotations of type `A` of the fields or constructors of case class-like or sum type `T`. + * + * If type `T` is case class-like, this type class inspects its fields and provides their type annotations of type `A`. If + * type `T` is a sum type, its constructor types are looked for type annotations. + * + * Type `Out` is an HList having the same number of elements as `T` (number of fields of `T` if `T` is case class-like, + * or number of constructors of `T` if it is a sum type). It is made of `None.type` (no annotation on corresponding + * field or constructor) and `Some[A]` (corresponding field or constructor is annotated). + * + * Method `apply` provides an HList of type `Out` made of `None` (corresponding field or constructor not annotated) + * or `Some(annotation)` (corresponding field or constructor has annotation `annotation`). + * + * Note that type annotations must be case class-like for this type class to take them into account. + * + * Example: + * {{{ + * case class First(s: String) + * + * case class CC(i: Int, s: String @First("a")) + * + * val ccFirsts = TypeAnnotations[First, CC] + * + * // ccFirsts.Out is (None.type, Some[First]) + * // ccFirsts.apply() is (None, Some(First("a"))) + * + * }}} + * + * This implementation is based on [[shapeless.Annotations]] by Alexandre Archambault. + * + * @tparam A: type annotation type + * @tparam T: case class-like or sum type, whose fields or constructors are annotated + * + * @author Patrick Grandjean + */ +trait TypeAnnotations[A, T] extends Serializable { + type Out <: Tuple + + def apply(): Out +} + +object TypeAnnotations { + def apply[A, T](implicit annotations: TypeAnnotations[A, T]): Aux[A, T, annotations.Out] = annotations + + type Aux[A, T, Out0 <: Tuple] = TypeAnnotations[A, T] { type Out = Out0 } + + def mkAnnotations[A, T, Out0 <: Tuple](annotations: Out0): Aux[A, T, Out0] = + new TypeAnnotations[A, T] { + type Out = Out0 + def apply(): Out = annotations + } + + transparent inline implicit def mkAnnotations[A, T]: TypeAnnotations[A, T] = + ${ AnnotationMacros.mkTypeAnnotations[A, T] } +} +/** + * Provides all variable annotations for the fields or constructors of case class-like or sum type `T`. + * + * If type `T` is case class-like, this type class inspects its fields and provides their variable annotations. If + * type `T` is a sum type, its constructor types are looked for variable annotations as well. + * + * Type `Out` is an HList having the same number of elements as `T` (number of fields of `T` if `T` is case + * class-like, or number of constructors of `T` if it is a sum type). It is made of `HNil` (no annotations for corresponding + * field or constructor) or `HLists` (list of annotations for corresponding field or constructor). + * + * Method `apply` provides an HList of type `Out` made of `HNil` (corresponding field or constructor not annotated) + * or `HList` (corresponding field or constructor has annotations). + * + * Note that variable annotations must be case class-like for this type class to take them into account. + * + * Example: + * {{{ + * case class First(s: String) + * case class Second(i: Int) + * + * case class CC(i: Int, @First("a") @Second(0) s: String) + * + * val ccFirsts = AllAnnotations[CC] + * + * // ccFirsts.Out is ((), (First, Second)) + * // ccFirsts.apply() is + * // ((), (First("a"), Second(0))) + * + * }}} + * + * This implementation is based on [[shapeless.Annotations]] by Alexandre Archambault. + * + * @tparam T: case class-like or sum type, whose fields or constructors are annotated + * + * @author Patrick Grandjean + */ +trait AllAnnotations[T] extends Serializable { + type Out <: Tuple + + def apply(): Out } +object AllAnnotations { + def apply[T](implicit annotations: AllAnnotations[T]): Aux[T, annotations.Out] = annotations + + type Aux[T, Out0 <: Tuple] = AllAnnotations[T] { type Out = Out0 } + + def mkAnnotations[T, Out0 <: Tuple](annotations: Out0): Aux[T, Out0] = + new AllAnnotations[T] { + type Out = Out0 + def apply(): Out = annotations + } + + transparent inline implicit def mkAnnotations[T]: AllAnnotations[T] = + ${ AnnotationMacros.mkAllVariableAnnotations[T] } +} + +/** + * Provides all type annotations for the fields or constructors of case class-like or sum type `T`. + * + * If type `T` is case class-like, this type class inspects its fields and provides their type annotations. If + * type `T` is a sum type, its constructor types are looked for type annotations as well. + * + * Type `Out` is an HList having the same number of elements as `T` (number of fields of `T` if `T` is case + * class-like, or number of constructors of `T` if it is a sum type). It is made of `HNil` (no annotations for corresponding + * field or constructor) or `HLists` (list of annotations for corresponding field or constructor). + * + * Method `apply` provides an HList of type `Out` made of `HNil` (corresponding field or constructor not annotated) + * or `HList` (corresponding field or constructor has annotations). + * + * Note that type annotations must be case class-like for this type class to take them into account. + * + * Example: + * {{{ + * case class First(s: String) + * case class Second(i: Int) + * + * case class CC(i: Int, s: String @First("a") @Second(0)) + * + * val ccFirsts = AllTypeAnnotations[CC] + * + * // ccFirsts.Out is HNil :: (First :: Second :: HNil) :: HNil + * // ccFirsts.apply() is + * // HNil :: (First("a") :: Second(0) :: HNil) :: HNil + * + * }}} + * + * This implementation is based on [[shapeless.Annotations]] by Alexandre Archambault. + * + * @tparam T: case class-like or sum type, whose fields or constructors are annotated + * + * @author Patrick Grandjean + */ + trait AllTypeAnnotations[T] extends Serializable { + type Out <: Tuple + + def apply(): Out + } + + object AllTypeAnnotations { + def apply[T](implicit annotations: AllTypeAnnotations[T]): Aux[T, annotations.Out] = annotations + + type Aux[T, Out0 <: Tuple] = AllTypeAnnotations[T] { type Out = Out0 } + + def mkAnnotations[T, Out0 <: Tuple](annotations: Out0): Aux[T, Out0] = + new AllTypeAnnotations[T] { + type Out = Out0 + def apply(): Out = annotations + } + + transparent inline implicit def mkAnnotations[T]: AllTypeAnnotations[T] = + ${ AnnotationMacros.mkAllTypeAnnotations[T] } + } + object AnnotationMacros { def mkAnnotation[A: Type, T: Type](using Quotes): Expr[Annotation[A, T]] = { import quotes.reflect._ @@ -145,45 +315,117 @@ object AnnotationMacros { } } - def mkAnnotations[A: Type, T: Type](using q: Quotes): Expr[Annotations[A, T]] = { - import quotes.reflect._ + def mkVariableAnnotations[A: Type, T: Type](using Quotes) = mkAnnotations[A, T, Annotations](ofExprVariableAnnotations[A, T](_)) + + def mkTypeAnnotations[A: Type, T: Type](using Quotes) = mkAnnotations[A, T, TypeAnnotations](ofExprTypeAnnotations[A, T](_)) + + def mkAnnotations[A: Type, T: Type, AS[A, T]: Type](mk: Seq[Expr[Any]] => Expr[AS[A, T]])(using q: Quotes): Expr[AS[A, T]] = + import q.reflect._ + + val tpe = TypeRepr.of[AS[A, T]] <:< TypeRepr.of[TypeAnnotations[A, T]] + // println(s"tpe = ${tpe}") val annotTpe = TypeRepr.of[A] val annotFlags = annotTpe.typeSymbol.flags if (annotFlags.is(Flags.Abstract) || annotFlags.is(Flags.Trait)) { report.throwError(s"Bad annotation type ${annotTpe.show} is abstract") } else { - val r = new ReflectionUtils(q) - import r._ - - def mkAnnotations(annotTrees: Seq[Expr[Any]]): Expr[Annotations[A, T]] = - Expr.ofTupleFromSeq(annotTrees) match { - case '{ $t: tup } => '{ Annotations.mkAnnotations[A, T, tup & Tuple]($t) } - } - - def findAnnotation[A: Type](annoteeSym: Symbol): Expr[Option[A]] = - // TODO try to use `getAnnotation` for performance - annoteeSym.annotations.find(_.tpe <:< TypeRepr.of[A]) match { + val annotations = extractAnnotations[T](tpe) + // println(s"extractAnnotations = \n\t${annotations.mkString("\n\t")}") + val exprs = annotations.map { child => + child.find(_.tpe <:< TypeRepr.of[A]) match { case Some(tree) => '{ Some(${tree.asExprOf[A]}) } case None => '{ None } } + } - val annoteeTpe = TypeRepr.of[T] - annoteeTpe.classSymbol match { - case Some(annoteeCls) if annoteeCls.flags.is(Flags.Case) => - val valueParams = annoteeCls.primaryConstructor.paramSymss - .find(_.headOption.fold(false)( _.isTerm)).getOrElse(Nil) - mkAnnotations(valueParams.map { vparam => findAnnotation[A](vparam) }) - case Some(annoteeCls) => - Mirror(annoteeTpe) match { - case Some(rm) => - mkAnnotations(rm.MirroredElemTypes.map { child => findAnnotation[A](child.typeSymbol) }) - case None => - report.throwError(s"No Annotations for sum type ${annoteeTpe.show} with no Mirror") - } - case None => - report.throwError(s"No Annotations for non-class ${annoteeTpe.show}") + mk(exprs) + } + + def mkAllVariableAnnotations[T: Type](using Quotes) = mkAllAnnotations[T, AllAnnotations](ofExprAllVariableAnnotations) + + def mkAllTypeAnnotations[T: Type](using Quotes) = mkAllAnnotations[T, AllTypeAnnotations](ofExprAllTypeAnnotations) + + def mkAllAnnotations[T: Type, AS[T]: Type](mk: Seq[Expr[Any]] => Expr[AS[T]])(using q: Quotes): Expr[AS[T]] = + import q.reflect._ + + val tpe = TypeRepr.of[AS[T]] <:< TypeRepr.of[AllTypeAnnotations[T]] + // println(s"tpe = ${tpe}") + + val annotations = extractAnnotations[T](tpe) + // println(s"annotations = \n\t${annotations.mkString("\n\t")}") + val exprs = annotations.map { anns => + Expr.ofTupleFromSeq(anns.map(_.asExpr)) + } + + mk(exprs) + + def extractAnnotations[T: Type](tpe: Boolean)(using q: Quotes): Seq[List[q.reflect.Term]] = + import q.reflect._ + + val r = new ReflectionUtils(q) + import r._ + + def getAnnotations(tree: Tree, acc: List[Term] = Nil, depth: Int = 0): List[Term] = + // println(s"${depth}: ${tree.show(using Printer.TreeStructure)}") + if (tpe) { + tree match { + case classDef: ClassDef => classDef.parents.flatMap(getAnnotations(_, acc, depth + 1)) + case valDef: ValDef => getAnnotations(valDef.tpt, acc, depth + 1) + case typeId: TypeIdent => getAnnotationsFromType(typeId.tpe, acc, depth) + case inferred: Inferred => getAnnotationsFromType(inferred.tpe, acc, depth) + case annotated: Annotated => getAnnotations(annotated.arg, annotated.annotation :: acc, depth + 1) + case _ => acc + } + } else { + tree.symbol.annotations.reverse + } + + @tailrec + def getAnnotationsFromType(typeRepr: TypeRepr, acc: List[Term] = Nil, depth: Int = 0): List[Term] = + // println(s"${depth}: typeRepr = ${typeRepr}") + typeRepr match { + case annotatedType: AnnotatedType => getAnnotationsFromType(annotatedType.underlying, annotatedType.annotation :: acc, depth + 1) + case typeRef: TypeRef if typeRef.typeSymbol.isAliasType => getAnnotationsFromType(typeRef.translucentSuperType, acc, depth + 1) + case _ => acc } + + val annoteeTpe = TypeRepr.of[T] + annoteeTpe.classSymbol match { + case Some(annoteeCls) if annoteeCls.flags.is(Flags.Case) => + val valueParams = annoteeCls.primaryConstructor + .paramSymss + .find(_.headOption.fold(false)( _.isTerm)) + .getOrElse(Nil) + valueParams.map { vparam => getAnnotations(vparam.tree) } + case Some(annoteeCls) => + Mirror(annoteeTpe) match { + case Some(rm) => + rm.MirroredElemTypes.map { child => getAnnotations(child.typeSymbol.tree) } + case None => + report.throwError(s"No Annotations for sum type ${annoteeTpe.show} with no Mirror") + } + case None => + report.throwError(s"No Annotations for non-class ${annoteeTpe.show}") + } + + def ofExprVariableAnnotations[A: Type, T: Type](annotTrees: Seq[Expr[Any]])(using q: Quotes): Expr[Annotations[A, T]] = + Expr.ofTupleFromSeq(annotTrees) match { + case '{ $t: tup } => '{ Annotations.mkAnnotations[A, T, tup & Tuple]($t) } + } + + def ofExprTypeAnnotations[A: Type, T: Type](annotTrees: Seq[Expr[Any]])(using q: Quotes): Expr[TypeAnnotations[A, T]] = + Expr.ofTupleFromSeq(annotTrees) match { + case '{ $t: tup } => '{ TypeAnnotations.mkAnnotations[A, T, tup & Tuple]($t) } + } + + def ofExprAllVariableAnnotations[T: Type](annotTrees: Seq[Expr[Any]])(using q: Quotes): Expr[AllAnnotations[T]] = + Expr.ofTupleFromSeq(annotTrees) match { + case '{ $t: tup } => '{ AllAnnotations.mkAnnotations[T, tup & Tuple]($t) } + } + + def ofExprAllTypeAnnotations[T: Type](annotTrees: Seq[Expr[Any]])(using q: Quotes): Expr[AllTypeAnnotations[T]] = + Expr.ofTupleFromSeq(annotTrees) match { + case '{ $t: tup } => '{ AllTypeAnnotations.mkAnnotations[T, tup & Tuple]($t) } } - } } diff --git a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala index d1e06de..fc6e7d0 100644 --- a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala @@ -24,6 +24,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 Other() extends saAnnotation case class Last(b: Boolean) extends saAnnotation @@ -44,6 +45,34 @@ object AnnotationTestsDefinitions { trait Abstract1 abstract class Abstract2 + + sealed trait Base2 + case class BaseI2(i: Int) extends Base2 @First + case class BaseS2(s: String) extends Base2 @Second(3, "e") @Third('c') + + trait Dummy + + case class CC2( + i: Int @First, + s: String, + ob: Option[Boolean] @Second(2, "b") + ) + + case class CC3( + @First i: Int, + s: String, + @Second(2, "b") @Third('c') ob: Option[Boolean] + ) + + case class CC4( + i: Int @First, + s: String, + ob: Option[Boolean] @Second(2, "b") @Third('c') + ) + + type PosInt = Int @First + type Email = String @Third('c') + case class User(age: PosInt, email: Email) } class AnnotationTests { @@ -120,4 +149,81 @@ class AnnotationTests { illTyped(" Annotations[Abstract2, Base] ", ".*no implicit argument.*") illTyped(" Annotations[Second, Abstract1] ", ".*no implicit argument.*") } + + @Test + def typeAnnotations: Unit = { + { + val first: (Some[First], None.type, None.type) = TypeAnnotations[First, CC4].apply() + assert(first == (Some(First()), None, None)) + + val second: (None.type, None.type, Some[Second]) = TypeAnnotations[Second, CC2].apply() + assert(second == (None, None, Some(Second(2, "b")))) + + val unused: (None.type, None.type, None.type) = TypeAnnotations[Unused, CC2].apply() + assert(unused == (None, None, None)) + + val firstSum: (Some[First], None.type) = TypeAnnotations[First, Base2].apply() + assert(firstSum == (Some(First()), None)) + + val secondSum: (None.type, Some[Second]) = TypeAnnotations[Second, Base2].apply() + assert(secondSum == (None, Some(Second(3, "e")))) + } + + { + val first = TypeAnnotations[First, CC2].apply() + assert(first == (Some(First()), None, None)) + + val second = TypeAnnotations[Second, CC2].apply() + assert(second == (None, None, Some(Second(2, "b")))) + + val unused = TypeAnnotations[Unused, CC2].apply() + assert(unused == (None, None, None)) + + val firstSum = TypeAnnotations[First, Base2].apply() + assert(firstSum == (Some(First()), None)) + + val secondSum = TypeAnnotations[Second, Base2].apply() + assert(secondSum == (None, Some(Second(3, "e")))) + } + } + + @Test + 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: .*") + } + + @Test + def allAnnotations: Unit = { + type T1First = Tuple1[First] + val first: T1First = Tuple1(First()) + + val cc: (T1First, EmptyTuple.type, (Second, Third)) = AllAnnotations[CC3].apply() + assert(cc == (first, EmptyTuple, (Second(2, "b"), Third('c')))) + + type T1Second = Tuple1[Second] + val second: T1Second = Tuple1(Second(3, "e")) + + val st: (T1First, T1Second) = AllAnnotations[Base].apply() + assert(st == (first, second)) + } + + @Test + def allTypeAnnotations: Unit = { + type T1First = Tuple1[First] + val first: T1First = Tuple1(First()) + + val st: (T1First, (Second, Third)) = AllTypeAnnotations[Base2].apply() // sealed trait + assert(cc == (first, (Second(3, "e"), Third('c')))) + + val cc: (T1First, EmptyTuple.type, (Second, Third)) = AllTypeAnnotations[CC4].apply() // case class + assert(cc == (first, EmptyTuple, (Second(2, "b"), Third('c')))) + + type T1Third = Tuple1[Third] + val third: T1Third = Tuple1(Third('c')) + + val user: (T1First, T1Third) = AllTypeAnnotations[User].apply() // type refs + assert(user == (first, third)) + } } From eb54253940bea9417ef5dbb320db3f6bd0d979f5 Mon Sep 17 00:00:00 2001 From: Patrick Grandjean Date: Thu, 16 Dec 2021 09:09:36 -0800 Subject: [PATCH 2/9] fixed binary compatibility check --- .../scala/shapeless3/deriving/annotation.scala | 16 ++++++++-------- .../scala/shapeless3/deriving/annotation.scala | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala index 77ee1f7..5723e3e 100644 --- a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala @@ -122,7 +122,7 @@ object Annotations { } transparent inline implicit def mkAnnotations[A, T]: Annotations[A, T] = - ${ AnnotationMacros.mkVariableAnnotations[A, T] } + ${ AnnotationMacros.mkAnnotations[A, T] } } /** @@ -234,7 +234,7 @@ object AllAnnotations { } transparent inline implicit def mkAnnotations[T]: AllAnnotations[T] = - ${ AnnotationMacros.mkAllVariableAnnotations[T] } + ${ AnnotationMacros.mkAllAnnotations[T] } } /** @@ -315,11 +315,11 @@ object AnnotationMacros { } } - def mkVariableAnnotations[A: Type, T: Type](using Quotes) = mkAnnotations[A, T, Annotations](ofExprVariableAnnotations[A, T](_)) + def mkAnnotations[A: Type, T: Type](using Quotes): Expr[Annotations[A, T]] = mkAnnotationsImpl[A, T, Annotations](ofExprVariableAnnotations[A, T](_)) - def mkTypeAnnotations[A: Type, T: Type](using Quotes) = mkAnnotations[A, T, TypeAnnotations](ofExprTypeAnnotations[A, T](_)) + def mkTypeAnnotations[A: Type, T: Type](using Quotes): Expr[TypeAnnotations[A, T]] = mkAnnotationsImpl[A, T, TypeAnnotations](ofExprTypeAnnotations[A, T](_)) - def mkAnnotations[A: Type, T: Type, AS[A, T]: Type](mk: Seq[Expr[Any]] => Expr[AS[A, T]])(using q: Quotes): Expr[AS[A, T]] = + private def mkAnnotationsImpl[A: Type, T: Type, AS[A, T]: Type](mk: Seq[Expr[Any]] => Expr[AS[A, T]])(using q: Quotes): Expr[AS[A, T]] = import q.reflect._ val tpe = TypeRepr.of[AS[A, T]] <:< TypeRepr.of[TypeAnnotations[A, T]] @@ -342,11 +342,11 @@ object AnnotationMacros { mk(exprs) } - def mkAllVariableAnnotations[T: Type](using Quotes) = mkAllAnnotations[T, AllAnnotations](ofExprAllVariableAnnotations) + def mkAllAnnotations[T: Type](using Quotes): Expr[AllAnnotations[T]] = mkAllAnnotationsImpl[T, AllAnnotations](ofExprAllVariableAnnotations) - def mkAllTypeAnnotations[T: Type](using Quotes) = mkAllAnnotations[T, AllTypeAnnotations](ofExprAllTypeAnnotations) + def mkAllTypeAnnotations[T: Type](using Quotes): Expr[AllTypeAnnotations[T]] = mkAllAnnotationsImpl[T, AllTypeAnnotations](ofExprAllTypeAnnotations) - def mkAllAnnotations[T: Type, AS[T]: Type](mk: Seq[Expr[Any]] => Expr[AS[T]])(using q: Quotes): Expr[AS[T]] = + private def mkAllAnnotationsImpl[T: Type, AS[T]: Type](mk: Seq[Expr[Any]] => Expr[AS[T]])(using q: Quotes): Expr[AS[T]] = import q.reflect._ val tpe = TypeRepr.of[AS[T]] <:< TypeRepr.of[AllTypeAnnotations[T]] diff --git a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala index fc6e7d0..3b5d6fb 100644 --- a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala @@ -215,7 +215,7 @@ class AnnotationTests { val first: T1First = Tuple1(First()) val st: (T1First, (Second, Third)) = AllTypeAnnotations[Base2].apply() // sealed trait - assert(cc == (first, (Second(3, "e"), Third('c')))) + assert(st == (first, (Second(3, "e"), Third('c')))) val cc: (T1First, EmptyTuple.type, (Second, Third)) = AllTypeAnnotations[CC4].apply() // case class assert(cc == (first, EmptyTuple, (Second(2, "b"), Third('c')))) From aba6dfa90135efa4f8b917d7464fc54f37905ef9 Mon Sep 17 00:00:00 2001 From: Patrick Grandjean Date: Wed, 19 Jan 2022 07:57:21 -0800 Subject: [PATCH 3/9] Using Tuple1 syntactic sugar in tests --- .../scala/shapeless3/deriving/annotation.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala index 3b5d6fb..b00b041 100644 --- a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala @@ -196,14 +196,14 @@ class AnnotationTests { @Test def allAnnotations: Unit = { - type T1First = Tuple1[First] - val first: T1First = Tuple1(First()) + type T1First = First *: EmptyTuple.type + val first: T1First = First() *: EmptyTuple val cc: (T1First, EmptyTuple.type, (Second, Third)) = AllAnnotations[CC3].apply() assert(cc == (first, EmptyTuple, (Second(2, "b"), Third('c')))) - type T1Second = Tuple1[Second] - val second: T1Second = Tuple1(Second(3, "e")) + type T1Second = Second *: EmptyTuple.type + val second: T1Second = Second(3, "e") *: EmptyTuple val st: (T1First, T1Second) = AllAnnotations[Base].apply() assert(st == (first, second)) @@ -211,8 +211,8 @@ class AnnotationTests { @Test def allTypeAnnotations: Unit = { - type T1First = Tuple1[First] - val first: T1First = Tuple1(First()) + type T1First = First *: EmptyTuple.type + val first: T1First = First() *: EmptyTuple val st: (T1First, (Second, Third)) = AllTypeAnnotations[Base2].apply() // sealed trait assert(st == (first, (Second(3, "e"), Third('c')))) @@ -220,8 +220,8 @@ class AnnotationTests { val cc: (T1First, EmptyTuple.type, (Second, Third)) = AllTypeAnnotations[CC4].apply() // case class assert(cc == (first, EmptyTuple, (Second(2, "b"), Third('c')))) - type T1Third = Tuple1[Third] - val third: T1Third = Tuple1(Third('c')) + type T1Third = Third *: EmptyTuple.type + val third: T1Third = Third('c') *: EmptyTuple val user: (T1First, T1Third) = AllTypeAnnotations[User].apply() // type refs assert(user == (first, third)) From 1132d8d90005b43329aa78a33a54e058e5b764a2 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 15 Apr 2022 11:30:29 +0200 Subject: [PATCH 4/9] Apply suggestions from code review --- .../shapeless3/deriving/annotation.scala | 64 +++++++++---------- .../shapeless3/deriving/annotation.scala | 18 +++--- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala index 5723e3e..fcdca05 100644 --- a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala @@ -126,19 +126,19 @@ object Annotations { } /** - * Provides the type annotations of type `A` of the fields or constructors of case class-like or sum type `T`. + * Provides the type annotations of type `A` of the fields of a product type or constructors of a sum type `T`. * - * If type `T` is case class-like, this type class inspects its fields and provides their type annotations of type `A`. If + * If type `T` is a product type, this type class inspects its fields and provides their type annotations of type `A`. If * type `T` is a sum type, its constructor types are looked for type annotations. * - * Type `Out` is an HList having the same number of elements as `T` (number of fields of `T` if `T` is case class-like, + * Type `Out` is a tuple having the same number of elements as `T` (number of fields of `T` if `T` is a product type, * or number of constructors of `T` if it is a sum type). It is made of `None.type` (no annotation on corresponding * field or constructor) and `Some[A]` (corresponding field or constructor is annotated). * - * Method `apply` provides an HList of type `Out` made of `None` (corresponding field or constructor not annotated) + * Method `apply` provides a tuple of type `Out` made of `None` (corresponding field or constructor not annotated) * or `Some(annotation)` (corresponding field or constructor has annotation `annotation`). * - * Note that type annotations must be case class-like for this type class to take them into account. + * Note that type annotations must not be abstract for this type class to take them into account. * * Example: * {{{ @@ -156,7 +156,7 @@ object Annotations { * This implementation is based on [[shapeless.Annotations]] by Alexandre Archambault. * * @tparam A: type annotation type - * @tparam T: case class-like or sum type, whose fields or constructors are annotated + * @tparam T: product or sum type, whose fields or constructors are annotated * * @author Patrick Grandjean */ @@ -177,23 +177,23 @@ object TypeAnnotations { def apply(): Out = annotations } - transparent inline implicit def mkAnnotations[A, T]: TypeAnnotations[A, T] = + transparent inline given mkAnnotations[A, T]: TypeAnnotations[A, T] = ${ AnnotationMacros.mkTypeAnnotations[A, T] } } /** - * Provides all variable annotations for the fields or constructors of case class-like or sum type `T`. + * Provides all variable annotations for the fields of a product type or constructors of a sum type `T`. * - * If type `T` is case class-like, this type class inspects its fields and provides their variable annotations. If + * If type `T` is a product type, this type class inspects its fields and provides their variable annotations. If * type `T` is a sum type, its constructor types are looked for variable annotations as well. * - * Type `Out` is an HList having the same number of elements as `T` (number of fields of `T` if `T` is case - * class-like, or number of constructors of `T` if it is a sum type). It is made of `HNil` (no annotations for corresponding - * field or constructor) or `HLists` (list of annotations for corresponding field or constructor). + * Type `Out` is a tuple having the same number of elements as `T` (number of fields of `T` if `T` is a product type, + * or number of constructors of `T` if it is a sum type). It is made of tuples + * containing all annotations for the corresponding field or constructor (`EmptyTuple` if none). * - * Method `apply` provides an HList of type `Out` made of `HNil` (corresponding field or constructor not annotated) - * or `HList` (corresponding field or constructor has annotations). + * Method `apply` provides a tuple of type `Out` made of tuples + * containing all annotations of the corresponding field or constructor (`EmptyTuple` if none). * - * Note that variable annotations must be case class-like for this type class to take them into account. + * Note that variable annotations must not be abstract for this type class to take them into account. * * Example: * {{{ @@ -204,15 +204,15 @@ object TypeAnnotations { * * val ccFirsts = AllAnnotations[CC] * - * // ccFirsts.Out is ((), (First, Second)) + * // ccFirsts.Out is (EmptyTuple, (First, Second)) * // ccFirsts.apply() is - * // ((), (First("a"), Second(0))) + * // (EmptyTuple, (First("a"), Second(0))) * * }}} * * This implementation is based on [[shapeless.Annotations]] by Alexandre Archambault. * - * @tparam T: case class-like or sum type, whose fields or constructors are annotated + * @tparam T: product or sum type, whose fields or constructors are annotated * * @author Patrick Grandjean */ @@ -233,24 +233,24 @@ object AllAnnotations { def apply(): Out = annotations } - transparent inline implicit def mkAnnotations[T]: AllAnnotations[T] = + transparent inline given mkAnnotations[T]: AllAnnotations[T] = ${ AnnotationMacros.mkAllAnnotations[T] } } /** - * Provides all type annotations for the fields or constructors of case class-like or sum type `T`. + * Provides all type annotations for the fields of product type or constructors of sum type `T`. * - * If type `T` is case class-like, this type class inspects its fields and provides their type annotations. If + * If type `T` is a product type, this type class inspects its fields and provides their type annotations. If * type `T` is a sum type, its constructor types are looked for type annotations as well. * - * Type `Out` is an HList having the same number of elements as `T` (number of fields of `T` if `T` is case - * class-like, or number of constructors of `T` if it is a sum type). It is made of `HNil` (no annotations for corresponding - * field or constructor) or `HLists` (list of annotations for corresponding field or constructor). + * Type `Out` is a tuple having the same number of elements as `T` (number of fields of `T` if `T` is a product type, + * or number of constructors of `T` if it is a sum type). It is made of tuples + * containing all annotations for the corresponding field or constructor (`EmptyTuple` if none). * - * Method `apply` provides an HList of type `Out` made of `HNil` (corresponding field or constructor not annotated) - * or `HList` (corresponding field or constructor has annotations). + * Method `apply` provides a tuple of type `Out` made of tuples + * containing all annotations for the corresponding field or constructor (`EmptyTuple` if none). * - * Note that type annotations must be case class-like for this type class to take them into account. + * Note that type annotations must not be abstract for this type class to take them into account. * * Example: * {{{ @@ -261,9 +261,9 @@ object AllAnnotations { * * val ccFirsts = AllTypeAnnotations[CC] * - * // ccFirsts.Out is HNil :: (First :: Second :: HNil) :: HNil + * // ccFirsts.Out is (EmptyTuple, (First, Second) * // ccFirsts.apply() is - * // HNil :: (First("a") :: Second(0) :: HNil) :: HNil + * // (EmptyTuple, (First("a"), Second(0)) * * }}} * @@ -290,7 +290,7 @@ object AllAnnotations { def apply(): Out = annotations } - transparent inline implicit def mkAnnotations[T]: AllTypeAnnotations[T] = + transparent inline given mkAnnotations[T]: AllTypeAnnotations[T] = ${ AnnotationMacros.mkAllTypeAnnotations[T] } } @@ -315,9 +315,9 @@ object AnnotationMacros { } } - def mkAnnotations[A: Type, T: Type](using Quotes): Expr[Annotations[A, T]] = mkAnnotationsImpl[A, T, Annotations](ofExprVariableAnnotations[A, T](_)) + def mkAnnotations[A: Type, T: Type](using Quotes): Expr[Annotations[A, T]] = mkAnnotationsImpl[A, T, Annotations](ofExprVariableAnnotations) - def mkTypeAnnotations[A: Type, T: Type](using Quotes): Expr[TypeAnnotations[A, T]] = mkAnnotationsImpl[A, T, TypeAnnotations](ofExprTypeAnnotations[A, T](_)) + def mkTypeAnnotations[A: Type, T: Type](using Quotes): Expr[TypeAnnotations[A, T]] = mkAnnotationsImpl[A, T, TypeAnnotations](ofExprTypeAnnotations) private def mkAnnotationsImpl[A: Type, T: Type, AS[A, T]: Type](mk: Seq[Expr[Any]] => Expr[AS[A, T]])(using q: Quotes): Expr[AS[A, T]] = import q.reflect._ diff --git a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala index b00b041..c3cbedd 100644 --- a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala @@ -189,20 +189,20 @@ class AnnotationTests { @Test 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[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: .*") } @Test def allAnnotations: Unit = { - type T1First = First *: EmptyTuple.type + type T1First = First *: EmptyTuple val first: T1First = First() *: EmptyTuple - val cc: (T1First, EmptyTuple.type, (Second, Third)) = AllAnnotations[CC3].apply() + val cc: (T1First, EmptyTuple, (Second, Third)) = AllAnnotations[CC3].apply() assert(cc == (first, EmptyTuple, (Second(2, "b"), Third('c')))) - type T1Second = Second *: EmptyTuple.type + type T1Second = Second *: EmptyTuple val second: T1Second = Second(3, "e") *: EmptyTuple val st: (T1First, T1Second) = AllAnnotations[Base].apply() @@ -211,16 +211,16 @@ class AnnotationTests { @Test def allTypeAnnotations: Unit = { - type T1First = First *: EmptyTuple.type + type T1First = First *: EmptyTuple val first: T1First = First() *: EmptyTuple val st: (T1First, (Second, Third)) = AllTypeAnnotations[Base2].apply() // sealed trait assert(st == (first, (Second(3, "e"), Third('c')))) - val cc: (T1First, EmptyTuple.type, (Second, Third)) = AllTypeAnnotations[CC4].apply() // case class + val cc: (T1First, EmptyTuple, (Second, Third)) = AllTypeAnnotations[CC4].apply() // case class assert(cc == (first, EmptyTuple, (Second(2, "b"), Third('c')))) - type T1Third = Third *: EmptyTuple.type + type T1Third = Third *: EmptyTuple val third: T1Third = Third('c') *: EmptyTuple val user: (T1First, T1Third) = AllTypeAnnotations[User].apply() // type refs From fd72adb4c9be3ad03d2aae21d7c03573fd086480 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 15 Apr 2022 11:41:09 +0200 Subject: [PATCH 5/9] Remove stray printlns --- .../src/main/scala/shapeless3/deriving/annotation.scala | 8 -------- 1 file changed, 8 deletions(-) diff --git a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala index fcdca05..5f343d5 100644 --- a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala @@ -323,15 +323,12 @@ object AnnotationMacros { import q.reflect._ val tpe = TypeRepr.of[AS[A, T]] <:< TypeRepr.of[TypeAnnotations[A, T]] - // println(s"tpe = ${tpe}") - val annotTpe = TypeRepr.of[A] val annotFlags = annotTpe.typeSymbol.flags if (annotFlags.is(Flags.Abstract) || annotFlags.is(Flags.Trait)) { report.throwError(s"Bad annotation type ${annotTpe.show} is abstract") } else { val annotations = extractAnnotations[T](tpe) - // println(s"extractAnnotations = \n\t${annotations.mkString("\n\t")}") val exprs = annotations.map { child => child.find(_.tpe <:< TypeRepr.of[A]) match { case Some(tree) => '{ Some(${tree.asExprOf[A]}) } @@ -350,10 +347,7 @@ object AnnotationMacros { import q.reflect._ val tpe = TypeRepr.of[AS[T]] <:< TypeRepr.of[AllTypeAnnotations[T]] - // println(s"tpe = ${tpe}") - val annotations = extractAnnotations[T](tpe) - // println(s"annotations = \n\t${annotations.mkString("\n\t")}") val exprs = annotations.map { anns => Expr.ofTupleFromSeq(anns.map(_.asExpr)) } @@ -367,7 +361,6 @@ object AnnotationMacros { import r._ def getAnnotations(tree: Tree, acc: List[Term] = Nil, depth: Int = 0): List[Term] = - // println(s"${depth}: ${tree.show(using Printer.TreeStructure)}") if (tpe) { tree match { case classDef: ClassDef => classDef.parents.flatMap(getAnnotations(_, acc, depth + 1)) @@ -383,7 +376,6 @@ object AnnotationMacros { @tailrec def getAnnotationsFromType(typeRepr: TypeRepr, acc: List[Term] = Nil, depth: Int = 0): List[Term] = - // println(s"${depth}: typeRepr = ${typeRepr}") typeRepr match { case annotatedType: AnnotatedType => getAnnotationsFromType(annotatedType.underlying, annotatedType.annotation :: acc, depth + 1) case typeRef: TypeRef if typeRef.typeSymbol.isAliasType => getAnnotationsFromType(typeRef.translucentSuperType, acc, depth + 1) From c951a91794275c031f7300d0935ddd1dce932294 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 15 Apr 2022 11:44:07 +0200 Subject: [PATCH 6/9] Use Quotes as intended --- .../scala/shapeless3/deriving/annotation.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala index 5f343d5..b174c3d 100644 --- a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala @@ -319,8 +319,8 @@ object AnnotationMacros { def mkTypeAnnotations[A: Type, T: Type](using Quotes): Expr[TypeAnnotations[A, T]] = mkAnnotationsImpl[A, T, TypeAnnotations](ofExprTypeAnnotations) - private def mkAnnotationsImpl[A: Type, T: Type, AS[A, T]: Type](mk: Seq[Expr[Any]] => Expr[AS[A, T]])(using q: Quotes): Expr[AS[A, T]] = - import q.reflect._ + private def mkAnnotationsImpl[A: Type, T: Type, AS[A, T]: Type](mk: Seq[Expr[Any]] => Expr[AS[A, T]])(using Quotes): Expr[AS[A, T]] = + import quotes.reflect._ val tpe = TypeRepr.of[AS[A, T]] <:< TypeRepr.of[TypeAnnotations[A, T]] val annotTpe = TypeRepr.of[A] @@ -343,8 +343,8 @@ object AnnotationMacros { def mkAllTypeAnnotations[T: Type](using Quotes): Expr[AllTypeAnnotations[T]] = mkAllAnnotationsImpl[T, AllTypeAnnotations](ofExprAllTypeAnnotations) - private def mkAllAnnotationsImpl[T: Type, AS[T]: Type](mk: Seq[Expr[Any]] => Expr[AS[T]])(using q: Quotes): Expr[AS[T]] = - import q.reflect._ + private def mkAllAnnotationsImpl[T: Type, AS[T]: Type](mk: Seq[Expr[Any]] => Expr[AS[T]])(using Quotes): Expr[AS[T]] = + import quotes.reflect._ val tpe = TypeRepr.of[AS[T]] <:< TypeRepr.of[AllTypeAnnotations[T]] val annotations = extractAnnotations[T](tpe) @@ -355,7 +355,7 @@ object AnnotationMacros { mk(exprs) def extractAnnotations[T: Type](tpe: Boolean)(using q: Quotes): Seq[List[q.reflect.Term]] = - import q.reflect._ + import quotes.reflect._ val r = new ReflectionUtils(q) import r._ @@ -401,22 +401,22 @@ object AnnotationMacros { report.throwError(s"No Annotations for non-class ${annoteeTpe.show}") } - def ofExprVariableAnnotations[A: Type, T: Type](annotTrees: Seq[Expr[Any]])(using q: Quotes): Expr[Annotations[A, T]] = + def ofExprVariableAnnotations[A: Type, T: Type](annotTrees: Seq[Expr[Any]])(using Quotes): Expr[Annotations[A, T]] = Expr.ofTupleFromSeq(annotTrees) match { case '{ $t: tup } => '{ Annotations.mkAnnotations[A, T, tup & Tuple]($t) } } - def ofExprTypeAnnotations[A: Type, T: Type](annotTrees: Seq[Expr[Any]])(using q: Quotes): Expr[TypeAnnotations[A, T]] = + def ofExprTypeAnnotations[A: Type, T: Type](annotTrees: Seq[Expr[Any]])(using Quotes): Expr[TypeAnnotations[A, T]] = Expr.ofTupleFromSeq(annotTrees) match { case '{ $t: tup } => '{ TypeAnnotations.mkAnnotations[A, T, tup & Tuple]($t) } } - def ofExprAllVariableAnnotations[T: Type](annotTrees: Seq[Expr[Any]])(using q: Quotes): Expr[AllAnnotations[T]] = + def ofExprAllVariableAnnotations[T: Type](annotTrees: Seq[Expr[Any]])(using Quotes): Expr[AllAnnotations[T]] = Expr.ofTupleFromSeq(annotTrees) match { case '{ $t: tup } => '{ AllAnnotations.mkAnnotations[T, tup & Tuple]($t) } } - def ofExprAllTypeAnnotations[T: Type](annotTrees: Seq[Expr[Any]])(using q: Quotes): Expr[AllTypeAnnotations[T]] = + def ofExprAllTypeAnnotations[T: Type](annotTrees: Seq[Expr[Any]])(using Quotes): Expr[AllTypeAnnotations[T]] = Expr.ofTupleFromSeq(annotTrees) match { case '{ $t: tup } => '{ AllTypeAnnotations.mkAnnotations[T, tup & Tuple]($t) } } From 75804ab6cde4ae0125b589bf9241480e9758f266 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 15 Apr 2022 11:48:08 +0200 Subject: [PATCH 7/9] Use report.errorAndAbort --- .../scala/shapeless3/deriving/annotation.scala | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala index b174c3d..a1467fd 100644 --- a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala @@ -301,16 +301,13 @@ object AnnotationMacros { val annotTpe = TypeRepr.of[A] val annotFlags = annotTpe.typeSymbol.flags if (annotFlags.is(Flags.Abstract) || annotFlags.is(Flags.Trait)) { - report.error(s"Bad annotation type ${annotTpe.show} is abstract") - '{???} + report.errorAndAbort(s"Bad annotation type ${annotTpe.show} is abstract") } else { val annoteeTpe = TypeRepr.of[T] // TODO try to use `getAnnotation` for performance annoteeTpe.typeSymbol.annotations.find(_.tpe <:< annotTpe) match { case Some(tree) => '{ Annotation.mkAnnotation[A, T](${tree.asExprOf[A]}) } - case None => - report.error(s"No Annotation of type ${annotTpe.show} for type ${annoteeTpe.show}") - '{???} + case None => report.errorAndAbort(s"No Annotation of type ${annotTpe.show} for type ${annoteeTpe.show}") } } } @@ -326,7 +323,7 @@ object AnnotationMacros { val annotTpe = TypeRepr.of[A] val annotFlags = annotTpe.typeSymbol.flags if (annotFlags.is(Flags.Abstract) || annotFlags.is(Flags.Trait)) { - report.throwError(s"Bad annotation type ${annotTpe.show} is abstract") + report.errorAndAbort(s"Bad annotation type ${annotTpe.show} is abstract") } else { val annotations = extractAnnotations[T](tpe) val exprs = annotations.map { child => @@ -387,7 +384,7 @@ object AnnotationMacros { case Some(annoteeCls) if annoteeCls.flags.is(Flags.Case) => val valueParams = annoteeCls.primaryConstructor .paramSymss - .find(_.headOption.fold(false)( _.isTerm)) + .find(_.exists(_.isTerm)) .getOrElse(Nil) valueParams.map { vparam => getAnnotations(vparam.tree) } case Some(annoteeCls) => @@ -395,10 +392,10 @@ object AnnotationMacros { case Some(rm) => rm.MirroredElemTypes.map { child => getAnnotations(child.typeSymbol.tree) } case None => - report.throwError(s"No Annotations for sum type ${annoteeTpe.show} with no Mirror") + report.errorAndAbort(s"No Annotations for sum type ${annoteeTpe.show} with no Mirror") } case None => - report.throwError(s"No Annotations for non-class ${annoteeTpe.show}") + report.errorAndAbort(s"No Annotations for non-class ${annoteeTpe.show}") } def ofExprVariableAnnotations[A: Type, T: Type](annotTrees: Seq[Expr[Any]])(using Quotes): Expr[Annotations[A, T]] = From 8d78a2cf17408bbec7b82aed2701a8e0cf05663a Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 15 Apr 2022 11:51:45 +0200 Subject: [PATCH 8/9] Fix TODO - use getAnnotation --- .../src/main/scala/shapeless3/deriving/annotation.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala index a1467fd..286b279 100644 --- a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala @@ -304,10 +304,9 @@ object AnnotationMacros { report.errorAndAbort(s"Bad annotation type ${annotTpe.show} is abstract") } else { val annoteeTpe = TypeRepr.of[T] - // TODO try to use `getAnnotation` for performance - annoteeTpe.typeSymbol.annotations.find(_.tpe <:< annotTpe) match { - case Some(tree) => '{ Annotation.mkAnnotation[A, T](${tree.asExprOf[A]}) } - case None => report.errorAndAbort(s"No Annotation of type ${annotTpe.show} for type ${annoteeTpe.show}") + annoteeTpe.typeSymbol.getAnnotation(annotTpe.typeSymbol) match { + case Some(tree) if tree.tpe <:< annotTpe => '{ Annotation.mkAnnotation[A, T](${tree.asExprOf[A]}) } + case _ => report.errorAndAbort(s"No Annotation of type ${annotTpe.show} for type ${annoteeTpe.show}") } } } From 1cb623282ff22d5cca0f616f44221498e5fca2c9 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 15 Apr 2022 23:28:40 +0200 Subject: [PATCH 9/9] Some enum support for annotations --- .../shapeless3/deriving/annotation.scala | 21 ++++++----- .../shapeless3/deriving/annotation.scala | 35 ++++++++++++++----- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala index 286b279..52c7a2d 100644 --- a/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala @@ -62,12 +62,12 @@ object Annotation { } /** - * Provides the annotations of type `A` of the fields or constructors of case class-like or sum type `T`. + * Provides the annotations of type `A` of the fields of product type or constructors of sum type `T`. * - * If type `T` is case class-like, this type class inspects the parameters in the first parameter list of the primary constructor - * and provides their annotations of type `A`. If type `T` is a sum type, its constructor types are inspected for annotations. + * If type `T` is a product type, this type class inspects its fields and provides their annotations of type `A`. + * If type `T` is a sum type, its constructor types are inspected for annotations. * - * Type `Out` is a tuple having the same number of elements as `T` (number of parameters of `T` if `T` is case class-like, + * Type `Out` is a tuple having the same number of elements as `T` (number of parameters of `T` if `T` is a product type, * or number of constructors of `T` if it is a sum type). It is made of `None.type` (no annotation on corresponding * field or constructor) and `Some[A]` (corresponding field or constructor is annotated). * @@ -100,7 +100,7 @@ object Annotation { * }}} * * @tparam A: annotation type - * @tparam T: case class-like or sum type, whose constructor parameters or constructors are annotated + * @tparam T: product or sum type, whose constructor parameters or constructors are annotated * * @author Alexandre Archambault */ @@ -269,7 +269,7 @@ object AllAnnotations { * * This implementation is based on [[shapeless.Annotations]] by Alexandre Archambault. * - * @tparam T: case class-like or sum type, whose fields or constructors are annotated + * @tparam T: product or sum type, whose fields or constructors are annotated * * @author Patrick Grandjean */ @@ -304,7 +304,8 @@ object AnnotationMacros { report.errorAndAbort(s"Bad annotation type ${annotTpe.show} is abstract") } else { val annoteeTpe = TypeRepr.of[T] - annoteeTpe.typeSymbol.getAnnotation(annotTpe.typeSymbol) match { + val symbol = if (annoteeTpe.isSingleton) annoteeTpe.termSymbol else annoteeTpe.typeSymbol + symbol.getAnnotation(annotTpe.typeSymbol) match { case Some(tree) if tree.tpe <:< annotTpe => '{ Annotation.mkAnnotation[A, T](${tree.asExprOf[A]}) } case _ => report.errorAndAbort(s"No Annotation of type ${annotTpe.show} for type ${annoteeTpe.show}") } @@ -388,10 +389,8 @@ object AnnotationMacros { valueParams.map { vparam => getAnnotations(vparam.tree) } case Some(annoteeCls) => Mirror(annoteeTpe) match { - case Some(rm) => - rm.MirroredElemTypes.map { child => getAnnotations(child.typeSymbol.tree) } - case None => - report.errorAndAbort(s"No Annotations for sum type ${annoteeTpe.show} with no Mirror") + case Some(rm) => rm.MirroredElemTypes.map(child => getAnnotations(child.typeSymbol.tree)) + case None => report.errorAndAbort(s"No Annotations for type ${annoteeTpe.show} without no Mirror") } case None => report.errorAndAbort(s"No Annotations for non-class ${annoteeTpe.show}") diff --git a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala index c3cbedd..732202f 100644 --- a/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala +++ b/modules/deriving/src/test/scala/shapeless3/deriving/annotation.scala @@ -73,6 +73,10 @@ object AnnotationTestsDefinitions { type PosInt = Int @First type Email = String @Third('c') case class User(age: PosInt, email: Email) + + @Other @Last(false) enum Control: + @First @Third('!') case Automatic + @Second(100, "@") case Manual(age: PosInt, email: Email) } class AnnotationTests { @@ -95,13 +99,28 @@ class AnnotationTests { val last: Last = Annotation[Last, Something].apply() assert(last == Last(true)) } + + { + val other = Annotation[Other, Control].apply() + assert(other == Other()) + + val first = Annotation[First, Control.Automatic.type].apply() + assert(first == First()) + + val second = Annotation[Second, Control.Manual].apply() + assert(second == Second(100, "@")) + + val third = Annotation[Third, Control.Automatic.type].apply() + assert(third == Third('!')) + } } @Test def invalidAnnotation: Unit = { - illTyped(" Annotation[Other, Abstract1] ", ".*no implicit argument.*") - illTyped(" Annotation[Abstract1, CC] ", ".*no implicit argument.*") - illTyped(" Annotation[Abstract2, CC] ", ".*no implicit argument.*") + illTyped("Annotation[Other, Abstract1]", ".*no implicit argument.*") + illTyped("Annotation[Abstract1, CC]", ".*no implicit argument.*") + illTyped("Annotation[Abstract2, CC]", ".*no implicit argument.*") + illTyped("Annotation[Unused, Control]", ".*no implicit argument.*") } @Test @@ -143,11 +162,11 @@ class AnnotationTests { @Test def invalidAnnotations: Unit = { - illTyped(" Annotations[Abstract1, CC] ", ".*no implicit argument.*") - illTyped(" Annotations[Abstract1, Base] ", ".*no implicit argument.*") - illTyped(" Annotations[Abstract2, CC] ", ".*no implicit argument.*") - illTyped(" Annotations[Abstract2, Base] ", ".*no implicit argument.*") - illTyped(" Annotations[Second, Abstract1] ", ".*no implicit argument.*") + illTyped("Annotations[Abstract1, CC]", ".*no implicit argument.*") + illTyped("Annotations[Abstract1, Base]", ".*no implicit argument.*") + illTyped("Annotations[Abstract2, CC]", ".*no implicit argument.*") + illTyped("Annotations[Abstract2, Base]", ".*no implicit argument.*") + illTyped("Annotations[Second, Abstract1]", ".*no implicit argument.*") } @Test