Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for type annotations (issue #922) #925

Merged
merged 1 commit into from
Sep 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 84 additions & 8 deletions core/src/main/scala/shapeless/annotation.scala
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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._
Expand Down Expand Up @@ -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))
Expand All @@ -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")

Expand All @@ -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
}
}
}
37 changes: 37 additions & 0 deletions core/src/test/scala/shapeless/annotation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ object AnnotationTestsDefinitions {

trait Dummy

case class CC2(
i: Int @First,
s: String,
ob: Option[Boolean] @Second(2, "b")
)
}

class AnnotationTests {
Expand Down Expand Up @@ -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: .*")
}

}