2
votes

I develop a scala macro annotation that enriches objects with various definitions (cf. play form macro). Amongst other things I want that the object contains the type alias

type WFS = FS[_, _, _, _]

for a varying number of wildcard arguments.

I already tried to extract the value of a single wildcard type by

q"type WFS = FS[_]" match { q"type WFS = FS[$t]" => t }

and hoped to use the extracted value in a list of type parameters (e.g. q"type WFS = FS[..$tplist]"). Yet the above statement yields an error:

scala> q"type WFS = FS[_]" match { case q"type WFS = FS[$t]" => t }
scala.MatchError: type WFS = FS[_$1] forSome { 
  <synthetic> type _$1 >: _root_.scala.Nothing <: _root_.scala.Any
} (of class scala.reflect.internal.Trees$TypeDef)
    at .<init>(<console>:15)
    at .<clinit>(<console>)
    at .<init>(<console>:7)
    at .<clinit>(<console>)
    at $print(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43

Is there another - maybe simpler - way to construct the necessary tree?

1

1 Answers

0
votes

The error is printing the actual shape of the value you're matching on, and apparently you need to use the same shape in the matching quasiquote:

scala> q"type WFS = FS[_]" match { case q"type WFS = FS[$a] forSome { $b }" => println(s"$a --- $b") }
_$1 --- <synthetic> type _$1 >: _root_.scala.Nothing <: _root_.scala.Any

If you want to allow for more parameters, and not just one, you better use "..$x" to allow for that:

scala> q"type WFS = FS[_]" match { case q"type WFS = FS[..$a] forSome { ..$b }" => println(s"$a --- $b") }
List(_$1) --- List(<synthetic> type _$1 >: _root_.scala.Nothing <: _root_.scala.Any)

Wildcards are transformed into existentials — FS[_] means FS[T] forSome { type T } (for details on existentials, there's the Scala Language Specification, Sec. 3.2.10, under "Placeholder Syntax for Existential Types"). When you write FS[$a] in your matching quasiquote, that means "I expect here an application of a type constructor to one type argument". But FS[T] forSome { type T } is an existential type which contains an application of a type constructor to an argument, so the pattern will not match.

To understand that better, and to know how to debug similar problems, it's insightful to look with showRaw at the trees generated by the quasiquotes — since these are types, we need type quasiquotes, that is tq"...":

scala> showRaw(tq"FS[_]")
res15: String = ExistentialTypeTree(AppliedTypeTree(Ident(newTypeName("FS")), List(Ident(newTypeName("_$1")))), List(TypeDef(Modifiers(DEFERRED | SYNTHETIC), newTypeName("_$1"), List(), TypeBoundsTree(Select(Select(Ident(nme.ROOTPKG), newTermName("scala")), newTypeName("Nothing")), Select(Select(Ident(nme.ROOTPKG), newTermName("scala")), newTypeName("Any"))))))

scala> showRaw(tq"FS[T]")
res16: String = AppliedTypeTree(Ident(newTypeName("FS")), List(Ident(newTypeName("T"))))

As far as I understand, matching with a quasiquotes means matching with the corresponding tree. So the above demonstrates the problem I described.

It's unfortunate that quasiquotes don't hide such details of the Scala type system. That might or might not be a bug of quasiquotes, but I can't comment on that — I do guess it would be cool if one would not need deal with