Frage

I have next class structure:

trait SomeType

trait Root {
  val allMySomeTypes: Seq[SomeType]
}

class Child extends Root {
  object MyType1 extends SomeType {...}
  object MyType2 extends SomeType {...}
}

And I want to initialize val allMySomeTypes as Seq of all objects extending SomeType defined in concrete class. So for Child instances it would be val allMySomeTypes: Seq[SomeType] = Seq(MyType1, MyType2).

I wrote a macro for looking for objects with some base type:

def getMembersOfCurrentByType_impl[S](c: Context)(implicit ev: c.WeakTypeTag[S]) = {
  import c.universe._
  val seqApply = Select(reify(Seq).tree, newTermName("apply"))
  val objs = c.enclosingClass.symbol.typeSignature.declarations.collect {
    case o: ModuleSymbol if o.typeSignature <:< ev.tpe => Ident(o) //Select(c.prefix.tree, o)
  }.toList
  c.Expr[Seq[S]] {Apply(seqApply, objs)}
}

and binded it to

trait Root {
  val allMySomeTypes: Seq[SomeType] = macro getMembersOfCurrentByType_impl[SomeType]
}

But, obviously, I have Empty sequence in Child class because of macro expansion for base trait. Can I build Seq of actual members without extra typing in Child class and without using of runtime reflection?

War es hilfreich?

Lösung

General case of subclass detection

Interestingly enough, we had a similar discussion at scala-user just a few days ago, where we conversed about the possibility of listing all subclasses of a given class in general case. Here's the answer that I posted back then:

It is currently not possible to enumerate all subclasses of an arbitrary class, but we do have an API that's called ClassSymbol.knownDirectSubclasses, which enumerates sealed descendants.

But unfortunately, even that is not easy. The knownDirectSubclasses API is more or less fine in the sense that people are somehow using it, but it also has a serious flaw that we currently don't know how to fix: https://groups.google.com/forum/#!topic/scala-internals/LRfKffsPxVA.

Particular case of subclass detection

However this StackOverflow question asks about something more specific. How about we restrict ourselves to just the members of some class, would it then be possible to pick those that are subclasses of the class we're interested in?

Well, it turns out that despite that there's currently an API to deal with that (c.enclosingClass of the family of c.enclosingTree methods), even this particular case still can't be dealt with robustly yet.

The thing is that Scala macros are expanded during typechecking, which means that at the time when a given macro is being expanded, its enclosing trees are being typechecked. The "being typechecked" status means that some enclosing trees might temporarily be in inconsistent state that's going to break down if one tries to inspect them. There's a more elaborate discussion at Scala Macro Annotations: c.TypeCheck of annotated type causes StackOverflowError, but here I'll give just a quick example.

For instance, if your macro is expanded inside a method with an unspecified return type (e.g. def foo = yourMacro(1, 2, 3)), then calling c.enclosingDef.symbol.typeSignature is going to fail macro expansion, because, well, to know the signature of the method, you need to infer its return type, but to infer the return type you need to expand the macro, for which you need to know the signature of the method, etc. By the way, the compiler is smart enough in order not to loop infinitely here - it will break the loop early and show a cyclic reference error.

This means that to handle non-local macro expansions well, we need some sort of a declarative abstraction over type signatures and their dependencies rather than just an imperative typeSignature method. I've been thinking about this on and off for the last couple of months, but so far haven't come up with anything satisfactory, which was one of the reasons why in Scala 2.11 we're going to deprecate non-local c.enclosingTree methods: https://github.com/scala/scala/pull/3354.

With the advent of macro annotations, this theme is going to become much more important, so we're not admitting defeat just yet. I think, it is reasonable to expect advances in this area, but there are no concrete dates that we can provide for this one.

A possible workaround

As I mentioned above, the "only" problem that we have with the task of detecting subclasses is that it's an operation that is non-local with respect to the macro expansion that performs it. So how about we turn that into a local operation? It turns out that it's possible.

By putting a macro annotation on the Child class, in your macro you'll be able to see all the members of the class before they are typechecked, which means that inspecting these members won't be causing potentially spurious cyclic errors.

However, if these members aren't typechecked, then what good are they to us? We need types to perform the subclass check, right? Well, yes and no. We indeed need types, but we don't have to get those types from the members themselves. We can take the annotated class, duplicate it, typecheck it and then inspect the results of the typecheck without the fear of disrupting the annottee. Here's an example that will provide inspiration for implementing this strategy: Can't access Parent's Members while dealing with Macro Annotations.

tl;dr

  1. At the moment, non-local operations during macro expansion can be hard or impossible to perform robustly, because of both theoretical and implementational reasons. SI-7046 is one example, this question provides another one.
  2. Sometimes it is possible to reformulate a non-local macro expansion problem as a local one by enclosing a bigger scope in a macro. Macro annotations provided by the macro paradise plugin might be helpful for this purpose.
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top