This doc page is specific to features shipped in Scala 2, which have either been removed in Scala 3 or replaced by an alternative. Unless otherwise stated, all the code examples in this page assume you are using Scala 2.
Denys Shabalin EXPERIMENTAL
Modifiers
Every definition except packages and package objects have associated a modifiers object that contains the following data:
FlagSet
, a set of bits that characterizes the given definition.- Private within name (e.g.
foo
inprivate[foo] def f
). - A list of annotations.
Quasiquotes let you work easily with those fields through native support for Modifiers
, FlagSet
and annotation unquoting:
scala> val f1 = q"${Modifiers(PRIVATE | IMPLICIT)} def f"
f1: universe.DefDef = implicit private def f: scala.Unit
scala> val f2 = q"$PRIVATE $IMPLICIT def f"
f2: universe.DefDef = implicit private def f: scala.Unit
scala> val f3 = q"private implicit def f"
f3: universe.DefDef = implicit private def f: scala.Unit
All of those quasiquotes result in equivalent trees. It’s also possible to combine unquoted flags with one provided inline in the source code, but an unquoted one should be used before inline ones:
scala> q"$PRIVATE implicit def foo"
res10: universe.DefDef = implicit private def foo: scala.Unit
scala> q"implicit $PRIVATE def foo"
<console>:32: error: expected start of definition
q"implicit $PRIVATE def foo"
^
To provide a definition annotation one must unquote a new-shaped tree:
scala> val annot = q"new foo(1)"
annot: universe.Tree = new Foo(1)
scala> val f4 = q"@$annot def f"
f4: universe.DefDef = @new foo(1) def f: scala.Unit
scala> val f5 = q"@foo(1) def f"
f5: universe.DefDef = @new foo(1) def f: scala.Unit
In deconstruction one can either extract Modifiers
or annotations, but you can’t extract flags separately:
scala> val q"$mods def f" = q"@foo implicit def f"
mods: universe.Modifiers = Modifiers(<deferred> implicit, , Map())
scala> val q"@..$annots implicit def f" = q"@foo @bar implicit def f"
annots: List[universe.Tree] = List(new foo(), new bar())
Considering that definitions might contain various low-level flags added to trees during typechecking, it's recommended to always extract complete modifiers, or your pattern might not be exhaustive. If you don’t care about them just use a wildcard:
scala> val q"$_ def f" = q"@foo @bar implicit def f"
Templates
Templates are a common abstraction in definition trees that are used in new expressions, classes, traits, objects, and package objects. Although there is no interpolator for it at the moment we can illustrate its structure using the example of a new expression (similar handling will apply to all other template-bearing trees):
q"new { ..$earlydefns } with ..$parents { $self => ..$stats }"
The template consists of:
-
Early definitions. A list of
val
ortype
definitions. Type definitions are still allowed by they are deprecated and will be removed in the future:scala> val withx = q"new { val x = 1 } with RequiresX" withx: universe.Tree = ... scala> val q"new { ..$earlydefns } with RequiresX" = withx earlydefns: List[universe.Tree] = List(val x = 1)
-
List of parents. A list of type identifiers where only the first one in the list may have optional type and value arguments. This is because the first parent must be a class and subsequent parents are just traits that don’t yet accept arguments:
scala> val q"new ..$parents" = q"new Foo(1) with Bar[T]" parents: List[universe.Tree] = List(Foo(1), Bar[T])
The first of the parents has an unusual shape that is a combination of term and type trees:
scala> val q"${tq"$name[..$targs]"}(...$argss)" = parents.head name: universe.Tree = Foo targs: List[universe.Tree] = List() argss: List[List[universe.Tree]] = List(List(1))
The others are just plain type trees:
scala> val tq"$name[..$targs]" = parents.tail.head name: universe.Tree = Bar targs: List[universe.Tree] = List(T)
-
Self type definition. A
val
definition that can be used to define an alias tothis
and provide a self-type viatpt
:scala> val q"new { $self => }" = q"new { self: T => }" self: universe.ValDef = private val self: T = _ scala> val q"$mods val $name: $tpt" = self mods: universe.Modifiers = Modifiers(private, , Map()) name: universe.TermName = self tpt: universe.Tree = T
-
List of body statements.
scala> val q"new { ..$body }" = q"new { val x = 1; def y = 'y }" body: List[universe.Tree] = List(val x = 1, def y = scala.Symbol("y"))
Val and Var Definitions
val
s and var
s allow you to define immutable values and mutable variables respectively. Additionally they can be used to represent function, class and method parameters.
Each val
and var
consistents of four components: modifiers, a name, a type tree and a right hand side:
scala> val valx = q"val x = 2"
valx: universe.ValDef = val x = 2
scala> val q"$mods val $name: $tpt = $rhs" = valx
mods: universe.Modifiers = Modifiers(, , Map())
name: universe.TermName = x
tpt: universe.Tree = <type ?>
rhs: universe.Tree = 2
If the type of the val
isn’t explicitly specified by the user an empty type is used as tpt
.
val
s and var
s are disjoint (they don’t match one another):
scala> val q"$mods val $name: $tpt = $rhs" = q"var x = 2"
scala.MatchError: var x = 2 (of class scala.reflect.internal.Trees$ValDef)
... 32 elided
Vars always have the MUTABLE
flag in their modifiers:
scala> val q"$mods var $name: $tpt = $rhs" = q"var x = 2"
mods: universe.Modifiers = Modifiers(<mutable>, , Map())
name: universe.TermName = x
tpt: universe.Tree = <type ?>
rhs: universe.Tree = 2
Pattern Definitions
Pattern definitions allow you to use Scala pattern matching capabilities to define variables. Unlike val
and var
definitions, pattern definitions are not first-class and they are represented through a combination of regular val
s, var
s and pattern matching:
scala> val patdef = q"val (x, y) = (1, 2)"
patdef: universe.Tree =
{
<synthetic> <artifact> private[this] val x$2 = scala.Tuple2(1, 2): @scala.unchecked match {
case scala.Tuple2((x @ _), (y @ _)) => scala.Tuple2(x, y)
};
val x = x$2._1;
val y = x$2._2;
()
}
This representation has a few side-effects on the usage of such definitions:
-
Due to the fact that a single definition often gets desugared into multiple lower-level ones, you must always use unquote splicing to unquote pattern definitions into other trees:
scala> val tupsum = q"..$patdef; a + b" tupsum: universe.Tree = { <synthetic> <artifact> private[this] val x$3 = scala.Tuple2(1, 2): @scala.unchecked match { case scala.Tuple2((x @ _), (y @ _)) => scala.Tuple2(x, y) }; val x = x$3._1; val y = x$3._2; a.$plus(b) }
Otherwise, if a regular unquoting is used, the definitions will be nested in a block that will make them invisible in the scope where they are meant to be used:
scala> val wrongtupsum = q"$patdef; a + b" wrongtupsum: universe.Tree = { { <synthetic> <artifact> private[this] val x$3 = scala.Tuple2(1, 2): @scala.unchecked match { case scala.Tuple2((x @ _), (y @ _)) => scala.Tuple2(x, y) }; val x = x$3._1; val y = x$3._2; () }; a.$plus(b) }
-
One can only construct pattern definitions, not deconstruct them.
A generic form of pattern definition consists of modifiers, pattern, ascribed type and a right hand side:
q"$mods val $pat: $tpt = $rhs"
Similarly, one can also construct a mutable pattern definition:
q"$mods var $pat: $tpt = $rhs"
Type Definition
Type definitions have two possible shapes: abstract type definitions and alias type definitions.
Abstract type definitions have the following shape:
scala> val q"$mods type $name[..$tparams] >: $low <: $high" =
q"type Foo[T] <: List[T]"
mods: universe.Modifiers = Modifiers(<deferred>, , Map())
name: universe.TypeName = Foo
tparams: List[universe.TypeDef] = List(type T)
low: universe.Tree = <empty>
high: universe.Tree = List[T]
Whenever one of the bounds isn’t available, it gets represented as an empty tree. Here each of the type arguments is a type definition itself.
Another form of type definition is a type alias:
scala> val q"$mods type $name[..$args] = $tpt" =
q"type Foo[T] = List[T]"
mods: universe.Modifiers = Modifiers(, , Map())
name: universe.TypeName = Foo
args: List[universe.TypeDef] = List(type T)
tpt: universe.Tree = List[T]
Due to the low level uniform representation of type aliases and abstract types one matches another:
scala> val q"$mods type $name[..$args] = $tpt" = q"type Foo[T] <: List[T]"
mods: universe.Modifiers = Modifiers(<deferred>, , Map())
name: universe.TypeName = Foo
args: List[universe.TypeDef] = List(type T)
tpt: universe.Tree = <: List[T]
Where tpt
has a TypeBoundsTree(low, high)
shape.
Method Definition
Each method consists of modifiers, a name, type arguments, value arguments, return type and a body:
scala> val q"$mods def $name[..$tparams](...$paramss): $tpt = $body" = q"def f = 1"
mods: universe.Modifiers = Modifiers(, , Map())
name: universe.TermName = f
tparams: List[universe.TypeDef] = List()
paramss: List[List[universe.ValDef]] = List()
tpt: universe.Tree = <type ?>
body: universe.Tree = 1
Type arguments are type definitions and value arguments are val definitions. The inferred return type is represented as an empty type. If the body of the method is an empty expression it means that method is abstract.
Alternatively you can also deconstruct arguments, separating implicit and non-implicit parameters:
scala> val q"def g(...$paramss)(implicit ..$implparams) = $body" =
q"def g(x: Int)(implicit y: Int) = x + y"
paramss: List[List[universe.ValDef]] = List(List(val x: Int = _))
implparams: List[universe.ValDef] = List(implicit val y: Int = _)
body: universe.Tree = x.$plus(y)
This way of handling parameters will still work if the method doesn’t have any implicit parameters and implparams
will get extracted as an empty list:
scala> val q"def g(...$paramss)(implicit ..$implparams) = $rhs" =
q"def g(x: Int)(y: Int) = x + y"
paramss: List[List[universe.ValDef]] = List(List(val x: Int = _), List(val y: Int = _))
implparams: List[universe.ValDef] = List()
body: universe.Tree = x.$plus(y)
Secondary Constructor Definition
Secondary constructors are special kinds of methods that have the following shape:
scala> val q"$mods def this(...$paramss) = this(...$argss)" =
q"def this() = this(0)"
mods: universe.Modifiers = Modifiers(, , Map())
paramss: List[List[universe.ValDef]] = List(List())
argss: List[List[universe.Tree]] = List(List(0))
Due to the low level underlying representation of trees, secondary constructors are represented as a special kind of method with termNames.CONSTRUCTOR
name:
scala> val q"$mods def $name[..$tparams](...$paramss): $tpt = $body"
= q"def this() = this(0)"
mods: universe.Modifiers = Modifiers(, , Map())
name: universe.TermName = <init>
tparams: List[universe.TypeDef] = List()
paramss: List[List[universe.ValDef]] = List(List())
tpt: universe.Tree = <type ?>
body: universe.Tree = <init>(0)
Class Definition
Classes have the following structure:
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
As you can may already see, the right part after the extends
is just a template. Apart from it and the modifiers, classes also have a primary constructor that consists of constructor modifiers, type and value parameters that behave very much like method modifiers and parameters.
Trait Definition
Syntactically, traits are quite similar to classes minus value parameters and constructor modifiers:
q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
An important difference in handling is caused by SI-8399 due to the INTERFACE flag that is set for traits. Wwith only abstract members, trait patterns might not match:
scala> val q"trait $name { ..$stats }" = q"trait X { def x: Int }"
scala.MatchError: ...
A workaround is to always extract modifiers using wildcard pattern:
scala> val q"$_ trait $name { ..$stats }" = q"trait X { def x: Int }"
name: universe.TypeName = X
stats: List[universe.Tree] = List(def x: Int)
Object Definition
Syntactically, objects are quite similar to classes without constructors:
q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
Package Definition
Packages are a fundamental primitive to organize source code. You can express them in quasiquotes as:
scala> val pack = q"package mycorp.myproj { class MyClass }"
pack: universe.PackageDef =
package mycorp.myproj {
class MyClass extends scala.AnyRef {
def <init>() = {
super.<init>();
()
}
}
}
scala> val q"package $ref { ..$body }" = pack
ref: universe.RefTree = mycorp.myproj
body: List[universe.Tree] =
List(class MyClass extends scala.AnyRef {
def <init>() = {
super.<init>();
()
}
})
Quasiquotes don’t support the inline package definition syntax that is usually used in the header of the source file (but it’s equivalent to the supported one in terms of ASTs).
Package Object Definition
Package objects are a cross between packages and object:
q"package object $tname extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
All of the handling properties are equivalent to those of objects apart from the fact that they don’t have modifiers.
Even though package and regular objects seem to be quite similar syntactically, they don’t match one another:
scala> val q"$mods object $name" = q"package object O"
scala.MatchError: ...
scala> val q"package object $name" = q"object O"
scala.MatchError: ...
Internally, they get represented as an object nested in a package with a given name:
scala> val P = q"package object P"
P: universe.PackageDef =
package P {
object `package` extends scala.AnyRef {
def <init>() = {
super.<init>();
()
}
}
}
This also means that you can match a package object as a package.