Skip to content

Commit

Permalink
Implementation of issue typelevel#55: Implement type annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
pgrandjean committed Dec 15, 2021
1 parent 768b825 commit f134cde
Show file tree
Hide file tree
Showing 3 changed files with 382 additions and 31 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ local.sbt
*.iml
*.iws

# Ignore project files for VSCode
.vscode

# Ignore OS X metadata
.DS_Store

Expand Down
304 changes: 273 additions & 31 deletions modules/deriving/src/main/scala/shapeless3/deriving/annotation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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._
Expand All @@ -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) }
}
}
}
Loading

0 comments on commit f134cde

Please sign in to comment.