diff --git a/core/src/main/scala/shapeless/annotation.scala b/core/src/main/scala/shapeless/annotation.scala index fcbc19fbb..46e7fcc51 100644 --- a/core/src/main/scala/shapeless/annotation.scala +++ b/core/src/main/scala/shapeless/annotation.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-6 Alexandre Archambault + * Copyright (c) 2015-9 Alexandre Archambault * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -114,9 +114,66 @@ object Annotations { def apply() = annotations } - implicit def materialize[A, T, Out <: HList]: Aux[A, T, Out] = macro AnnotationMacros.materializeAnnotations[A, T, Out] + implicit def materialize[A, T, Out <: HList]: Aux[A, T, Out] = macro AnnotationMacros.materializeVariableAnnotations[A, T, Out] } +/** + * 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] :: HNil + * // ccFirsts.apply() is + * // None :: Some(First("a")) :: HNil + * + * }}} + * + * 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 DepFn0 with Serializable { + type Out <: HList +} + +object TypeAnnotations { + def apply[A,T](implicit annotations: TypeAnnotations[A,T]): Aux[A, T, annotations.Out] = annotations + + type Aux[A, T, Out0 <: HList] = TypeAnnotations[A, T] { type Out = Out0 } + + def mkAnnotations[A, T, Out0 <: HList](annotations: => Out0): Aux[A, T, Out0] = + new TypeAnnotations[A, T] { + type Out = Out0 + def apply() = annotations + } + + implicit def materialize[A, T, Out <: HList]: Aux[A, T, Out] = macro AnnotationMacros.materializeTypeAnnotations[A, T, Out] +} + +/* + * AnnotationsHelper is defined in scala_2.10 or scala_2.11+ folders. + */ @macrocompat.bundle class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { import c.universe._ @@ -162,7 +219,11 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { } } - def materializeAnnotations[A: WeakTypeTag, T: WeakTypeTag, Out: WeakTypeTag]: Tree = { + def materializeVariableAnnotations[A: WeakTypeTag, T: WeakTypeTag, Out: WeakTypeTag]: Tree = materializeAnnotations[A, T, Out](false) + + def materializeTypeAnnotations[A: WeakTypeTag, T: WeakTypeTag, Out: WeakTypeTag]: Tree = materializeAnnotations[A, T, Out](true) + + def materializeAnnotations[A: WeakTypeTag, T: WeakTypeTag, Out: WeakTypeTag](typeAnnotation: Boolean): Tree = { val annTpe = weakTypeOf[A] if (!isProduct(annTpe)) @@ -185,16 +246,19 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { fieldsOf(tpe).map { case (name, _) => val paramConstrSym = constructorSyms(name.decodedName.toString) - paramConstrSym.annotations.collectFirst { + val annots = extract(typeAnnotation, paramConstrSym) + annots.collectFirst { case ann if ann.tree.tpe =:= annTpe => construct0(ann.tree.children.tail) } } } else if (isCoproduct(tpe)) ctorsOf(tpe).map { cTpe => - cTpe.typeSymbol.annotations.collectFirst { - case ann if ann.tree.tpe =:= annTpe => construct0(ann.tree.children.tail) - } + + val annots = extract(typeAnnotation, cTpe.typeSymbol) + annots.collectFirst { + case ann if ann.tree.tpe =:= annTpe => construct0(ann.tree.children.tail) } + } else abort(s"$tpe is not case class like or the root of a sealed family of types") @@ -208,6 +272,18 @@ class AnnotationMacros(val c: whitebox.Context) extends CaseClassMacros { case ((_, bound), acc) => pq"_root_.shapeless.::($bound, $acc)" } - q"_root_.shapeless.Annotations.mkAnnotations[$annTpe, $tpe, $outTpe]($outTree)" + if (typeAnnotation) q"_root_.shapeless.TypeAnnotations.mkAnnotations[$annTpe, $tpe, $outTpe]($outTree)" + else q"_root_.shapeless.Annotations.mkAnnotations[$annTpe, $tpe, $outTpe]($outTree)" + } + + def extract(tpe: Boolean, s: Symbol): List[c.universe.Annotation] = { + if (tpe) { + s.typeSignature match { + case a: AnnotatedType => a.annotations + case _ => Nil + } + } else { + s.annotations + } } } diff --git a/core/src/test/scala/shapeless/annotation.scala b/core/src/test/scala/shapeless/annotation.scala index 96886362f..567f22205 100644 --- a/core/src/test/scala/shapeless/annotation.scala +++ b/core/src/test/scala/shapeless/annotation.scala @@ -44,6 +44,11 @@ object AnnotationTestsDefinitions { trait Dummy + case class CC2( + i: Int @First, + s: String, + ob: Option[Boolean] @Second(2, "b") + ) } class AnnotationTests { @@ -118,4 +123,36 @@ class AnnotationTests { illTyped(" Annotations[Second, Dummy] ", "could not find implicit value for parameter annotations: .*") } + @Test + def typeAnnotations: Unit = { + { + val first: Some[First] :: None.type :: None.type :: HNil = TypeAnnotations[First, CC2].apply() + assert(first == Some(First()) :: None :: None :: HNil) + + val second: None.type :: None.type :: Some[Second] :: HNil = TypeAnnotations[Second, CC2].apply() + assert(second == None :: None :: Some(Second(2, "b")) :: HNil) + + val unused: None.type :: None.type :: None.type :: HNil = TypeAnnotations[Unused, CC2].apply() + assert(unused == None :: None :: None :: HNil) + } + + { + val first = TypeAnnotations[First, CC2].apply() + assert(first == Some(First()) :: None :: None :: HNil) + + val second = TypeAnnotations[Second, CC2].apply() + assert(second == None :: None :: Some(Second(2, "b")) :: HNil) + + val unused = TypeAnnotations[Unused, CC2].apply() + assert(unused == None :: None :: None :: HNil) + } + } + + @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: .*") + } + }