1
+ package org .cvogt .scala
2
+ import scala .reflect .macros .blackbox .Context
3
+ import scala .language .experimental .macros
4
+ import macrocompat .bundle
5
+
6
+ object EnumerateSingletons {
7
+ /** singleton objects transitively extending the given class or trait */
8
+ def apply [A ]: Set [A ] = macro EnumerateSingletonsMacros .enumerateSingletonsMacros[A ]
9
+ }
10
+
11
+ @ bundle
12
+ class EnumerateSingletonsMacros ( val c : Context ) {
13
+ import c .universe ._
14
+ def enumerateSingletonsMacros [T : c.WeakTypeTag ]: Tree = {
15
+ val T = weakTypeOf[T ].typeSymbol.asClass
16
+ val ( subs, verifiers ) = knownTransitiveSubclassesAndVerifiers( T )
17
+ val (singletons, classes) = subs.partition( _.isModuleClass )
18
+ val nonClosed = classes.filterNot(_.isSealed).filterNot(_.isFinal)
19
+ if (nonClosed.nonEmpty){
20
+ c.error(
21
+ c.enclosingPosition,
22
+ " EnumerateSingleton requires all transitive subclasses to be sealed or final. These are not: " ++ nonClosed.mkString(" , " )
23
+ )
24
+ }
25
+ val trees = singletons.map( _.module ).map( m => q " $m" )
26
+ val tree = q """ {
27
+ .. $verifiers
28
+ _root_.scala.collection.immutable.Set[ $T](.. $trees)
29
+ } """
30
+ tree
31
+ }
32
+
33
+ /** Generates a list of all singleton objects extending the given class directly or transitively. */
34
+ private def knownTransitiveSubclassesAndVerifiers ( sym : ClassSymbol ): ( Set [ClassSymbol ], List [Tree ] ) = {
35
+ val direct = knownDirectSubclassesAndVerifier( sym )
36
+ direct._1.map( knownTransitiveSubclassesAndVerifiers ).fold(
37
+ direct
38
+ )(
39
+ ( l, r ) => ( l._1 ++ r._1, l._2 ++ r._2 )
40
+ )
41
+ }
42
+
43
+ private def knownDirectSubclassesAndVerifier ( T : ClassSymbol ): ( Set [ClassSymbol ], List [Tree ] ) = {
44
+ val subs = T .knownDirectSubclasses
45
+
46
+ // hack to detect breakage of knownDirectSubclasses as suggested in
47
+ // https://gitter.im/scala/scala/archives/2015/05/05 and
48
+ // https://gist.github.com/retronym/639080041e3fecf58ba9
49
+ val global = c.universe.asInstanceOf [scala.tools.nsc.Global ]
50
+ def checkSubsPostTyper = if ( subs != T .knownDirectSubclasses )
51
+ c.error(
52
+ c.macroApplication.pos,
53
+ s """ No child classes found for $T. If there clearly are child classes,
54
+ Try moving the call lower in the file, into a separate file, a sibbling package, a separate sbt sub project or else.
55
+ This is caused by https://issues.scala-lang.org/browse/SI-7046 and can only be avoided by manually moving the call.
56
+ It is triggered when a macro call happend in a place, where typechecking of $T hasn't been completed yet.
57
+ Completion is required in order to find subclasses.
58
+ """
59
+ )
60
+
61
+ val checkSubsPostTyperTypTree =
62
+ new global.TypeTreeWithDeferredRefCheck ()( () => { checkSubsPostTyper; global.TypeTree ( global.NoType ) } ).asInstanceOf [TypTree ]
63
+
64
+ val name = TypeName ( c.freshName( " VerifyKnownDirectSubclassesPostTyper" ) )
65
+
66
+ (
67
+ subs.map( _.asClass ).toSet,
68
+ List ( q " type ${name} = $checkSubsPostTyperTypTree" )
69
+ )
70
+ }
71
+ }
0 commit comments