-
Notifications
You must be signed in to change notification settings - Fork 146
/
EnumMacros.scala
184 lines (172 loc) · 5.95 KB
/
EnumMacros.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package enumeratum
import ContextUtils.Context
import scala.collection.immutable._
import scala.util.control.NonFatal
object EnumMacros {
/**
* Finds any [A] in the current scope and returns an expression for a list of them
*/
def findValuesImpl[A: c.WeakTypeTag](c: Context): c.Expr[IndexedSeq[A]] = {
import c.universe._
val typeSymbol = weakTypeOf[A].typeSymbol
validateType(c)(typeSymbol)
val subclassSymbols = enclosedSubClasses(c)(typeSymbol)
buildSeqExpr[A](c)(subclassSymbols)
}
/**
* Given an A, provides its companion
*/
def materializeEnumImpl[A: c.WeakTypeTag](c: Context) = {
import c.universe._
val symbol = weakTypeOf[A].typeSymbol
val companionSymbol = ContextUtils.companion(c)(symbol)
if (companionSymbol == NoSymbol) {
c.abort(c.enclosingPosition,
s"""
|
| Could not find the companion object for type $symbol.
|
| If you're sure the companion object exists, you might be able to fix this error by annotating the
| value you're trying to find the companion object for with a parent type (e.g. Light.Red: Light).
|
| This error usually happens when trying to find the companion object of a hard-coded enum member, and
| is caused by Scala inferring the type to be the member's singleton type (e.g. Light.Red.type instead of
| Light).
|
| To illustrate, given an enum:
|
| sealed abstract class Light extends EnumEntry
| case object Light extends Enum[Light] {
| val values = findValues
| case object Red extends Light
| case object Blue extends Light
| case object Green extends Light
| }
|
| and a method:
|
| def indexOf[A <: EnumEntry: Enum](entry: A): Int = implicitly[Enum[A]].indexOf(entry)
|
| Instead of calling like so: indexOf(Light.Red)
| Call like so: indexOf(Light.Red: Light)
""".stripMargin)
} else {
c.Expr[A](Ident(companionSymbol))
}
}
/**
* Makes sure that we can work with the given type as an enum:
*
* Aborts if the type is not sealed
*/
private[enumeratum] def validateType(c: Context)(typeSymbol: c.universe.Symbol): Unit = {
if (!typeSymbol.asClass.isSealed)
c.abort(
c.enclosingPosition,
"You can only use findValues on sealed traits or classes"
)
}
/**
* Finds the actual trees in the current scope that implement objects of the given type
*
* aborts compilation if:
*
* - the implementations are not all objects
* - the current scope is not an object
*/
private[enumeratum] def enclosedSubClassTrees(c: Context)(
typeSymbol: c.universe.Symbol
): Seq[c.universe.ModuleDef] = {
import c.universe._
val enclosingBodySubClassTrees: List[Tree] = try {
val enclosingModule = c.enclosingClass match {
case md @ ModuleDef(_, _, _) => md
case _ =>
c.abort(
c.enclosingPosition,
"The enum (i.e. the class containing the case objects and the call to `findValues`) must be an object"
)
}
enclosingModule.impl.body.filter { x =>
try {
x.symbol.isModule &&
x.symbol.asModule.moduleClass.asClass.baseClasses.contains(typeSymbol)
} catch {
case NonFatal(e) =>
c.warning(
c.enclosingPosition,
s"Got an exception, indicating a possible bug in Enumeratum. Message: ${e.getMessage}"
)
false
}
}
} catch {
case NonFatal(e) =>
c.abort(c.enclosingPosition, s"Unexpected error: ${e.getMessage}")
}
if (isDocCompiler(c))
enclosingBodySubClassTrees.flatMap {
/*
DocDef isn't available without pulling in scala-compiler as a dependency.
That said, DocDef *should* be the only thing that passes the prior filter
*/
case docDef if isDocDef(c)(docDef) => {
docDef.children.collect {
case m: ModuleDef => m
}
}
case moduleDef: ModuleDef => List(moduleDef)
} else
enclosingBodySubClassTrees.collect {
case m: ModuleDef => m
}
}
/**
* Returns a sequence of symbols for objects that implement the given type
*/
private[enumeratum] def enclosedSubClasses(c: Context)(
typeSymbol: c.universe.Symbol
): Seq[c.universe.Symbol] = {
enclosedSubClassTrees(c)(typeSymbol).map(_.symbol)
}
/**
* Builds and returns an expression for an IndexedSeq containing the given symbols
*/
@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
private[enumeratum] def buildSeqExpr[A: c.WeakTypeTag](c: Context)(
subclassSymbols: Seq[c.universe.Symbol]
) = {
import c.universe._
val resultType = weakTypeOf[A]
if (subclassSymbols.isEmpty) {
c.Expr[IndexedSeq[A]](reify(IndexedSeq.empty[A]).tree)
} else {
c.Expr[IndexedSeq[A]](
Apply(
TypeApply(
Select(reify(IndexedSeq).tree, ContextUtils.termName(c)("apply")),
List(TypeTree(resultType))
),
subclassSymbols.map(Ident(_)).toList
)
)
}
}
/**
* Returns whether or not we are in doc mode.
*
* It's a bit of a hack, but I don't think it's much worse than pulling in scala-compiler
* for the sake of getting access to this class and doing an `isInstanceOf`
*/
private[this] def isDocCompiler(c: Context): Boolean = {
c.universe.getClass.toString.contains("doc.DocFactory")
}
/**
* Returns whether or not a given tree is a DocDef
*
* DocDefs are not part of the public API, so we try to hack around it here.
*/
private[this] def isDocDef(c: Context)(t: c.universe.Tree): Boolean = {
t.getClass.toString.contains("DocDef")
}
}