Skip to content

Commit

Permalink
refactor reflections in shared
Browse files Browse the repository at this point in the history
  • Loading branch information
nevillelyh committed Feb 25, 2021
1 parent 6c00ecb commit ca3927a
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 22 deletions.
51 changes: 51 additions & 0 deletions shared/src/main/scala/magnolify/shared/AnnotationType.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2021 Spotify AB.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package magnolify.shared

import scala.language.experimental.macros
import scala.reflect.macros._

sealed case class AnnotationType[T](annotations: List[Any])

object AnnotationType {
implicit def apply[T]: AnnotationType[T] = macro applyImpl[T]

def applyImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val wtt = weakTypeTag[T]
val pre = wtt.tpe.asInstanceOf[TypeRef].pre

// Scala 2.12 & 2.13 macros seem to handle annotations differently
// Scala annotation works in both but Java annotations only works in 2.13
val saType = typeOf[scala.annotation.StaticAnnotation]
val jaType = typeOf[java.lang.annotation.Annotation]
// Annotation for Scala enumerations are on the outer object
val annotated = if (pre <:< typeOf[scala.Enumeration]) pre else wtt.tpe
val trees = annotated.typeSymbol.annotations.collect {
case t if t.tree.tpe <:< saType && !(t.tree.tpe <:< jaType) =>
// FIXME `t.tree` should work but somehow crashes the compiler
val q"new $n(..$args)" = t.tree
q"new $n(..$args)"
}

// Get Java annotations via reflection
val j = q"classOf[${annotated.typeSymbol.asClass}].getAnnotations.toList"
val annotations = q"_root_.scala.List(..$trees) ++ $j"

q"_root_.magnolify.shared.AnnotationType[$wtt]($annotations)"
}
}
28 changes: 8 additions & 20 deletions shared/src/main/scala/magnolify/shared/EnumType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ object EnumType {
// Java `enum`
implicit def javaEnumType[T <: Enum[T]](implicit ct: ClassTag[T]): EnumType[T] = {
val cls: Class[_] = ct.runtimeClass
val n = cls.getSimpleName
val ns = cls.getCanonicalName.replaceFirst(s".$n$$", "")
val n = ReflectionUtils.name[T]
val ns = ReflectionUtils.namespace[T]
val map: Map[String, T] = cls
.getMethod("values")
.invoke(null)
Expand All @@ -77,34 +77,22 @@ object EnumType {
//////////////////////////////////////////////////

// Scala `Enumeration`
implicit def scalaEnumType[T <: Enumeration#Value]: EnumType[T] = macro scalaEnumTypeImpl[T]
implicit def scalaEnumType[T <: Enumeration#Value: AnnotationType]: EnumType[T] =
macro scalaEnumTypeImpl[T]

def scalaEnumTypeImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
def scalaEnumTypeImpl[T: c.WeakTypeTag](
c: whitebox.Context
)(annotations: c.Expr[AnnotationType[T]]): c.Tree = {
import c.universe._
val wtt = weakTypeTag[T]
val ref = wtt.tpe.asInstanceOf[TypeRef]
val n = ref.sym.name.toString // `type $Name = Value`
val ns = ref.pre.typeSymbol.asClass.fullName // `object Namespace extends Enumeration`
val map = q"${ref.pre.termSymbol}.values.map(x => x.toString -> x).toMap"

// Scala 2.12 & 2.13 macros seem to handle annotations differently
// Scala annotation works in both but Java annotations only works in 2.13
val saType = typeOf[scala.annotation.StaticAnnotation]
val jaType = typeOf[java.lang.annotation.Annotation]
val trees = ref.pre.typeSymbol.annotations.collect {
case t if t.tree.tpe <:< saType && !(t.tree.tpe <:< jaType) =>
// FIXME `t.tree` should work but somehow crashes the compiler
val q"new $n(..$args)" = t.tree
q"new $n(..$args)"
}

// Get Java annotations via reflection
val j = q"classOf[${ref.pre.typeSymbol.asClass}].getAnnotations.toList"
val annotations = q"_root_.scala.List(..$trees) ++ $j"

q"""
_root_.magnolify.shared.EnumType.create[$wtt](
$n, $ns, $map.keys.toList, $annotations, $map.apply(_), _.toString)
$n, $ns, $map.keys.toList, $annotations.annotations, $map.apply(_), _.toString)
"""
}

Expand Down
26 changes: 26 additions & 0 deletions shared/src/main/scala/magnolify/shared/ReflectionUtils.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2021 Spotify AB.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package magnolify.shared

import scala.reflect.ClassTag

object ReflectionUtils {
def name[T](implicit ct: ClassTag[T]): String = ct.runtimeClass.getSimpleName

def namespace[T](implicit ct: ClassTag[T]): String =
ct.runtimeClass.getCanonicalName.replaceFirst(s".${name[T]}$$", "")
}
45 changes: 45 additions & 0 deletions shared/src/test/scala/magnolify/shared/AnnotationTypeSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2021 Spotify AB.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package magnolify.shared

import magnolify.test._

import scala.annotation.StaticAnnotation

class AnnotationTypeSuite extends MagnolifySuite {
import AnnotationTypeSuite._
test("Enumeration") {
assertEquals(AnnotationType[AccountType.Type].annotations, List(Version("1.0")))
}

test("Class") {
assertEquals(AnnotationType[Account].annotations, List(Version("2.0")))
}
}

object AnnotationTypeSuite {
case class Version(version: String) extends StaticAnnotation

@Version("1.0")
object AccountType extends Enumeration {
type Type = Value
val Checking, Saving = Value
}

@Version("2.0")
case class Account(user: String, accountType: AccountType.Type)
}
3 changes: 1 addition & 2 deletions shared/src/test/scala/magnolify/shims/ShimsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ class ShimsSuite extends MagnolifySuite {
ti: C[Int] => Iterable[Int],
fc: FactoryCompat[Int, C[Int]]
): Unit = {
val name = ct.runtimeClass.getSimpleName
property(name) {
property(className[C[Int]]) {
Prop.forAll { xs: List[Int] => fc.build(xs).toList == xs }
}
}
Expand Down

0 comments on commit ca3927a

Please sign in to comment.