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.
EXPERIMENTAL
Annotations
In Scala, declarations can be annotated using subtypes of
scala.annotation.Annotation
. Furthermore, since Scala integrates with
Java’s annotation system,
it’s possible to work with annotations produced by a
standard Java compiler.
Annotations can be inspected reflectively if the corresponding annotations
have been persisted, so that they can be read from the classfile containing
the annotated declarations. A custom annotation type can be made persistent by
inheriting from scala.annotation.StaticAnnotation
or
scala.annotation.ClassfileAnnotation
. As a result, instances of the
annotation type are stored as special attributes in the corresponding
classfile. Note that subclassing just
scala.annotation.Annotation
is not enough to have the corresponding metadata
persisted for runtime reflection. Moreover, subclassing
scala.annotation.ClassfileAnnotation
does not make your annotation visible
as a Java annotation at runtime; that requires writing the annotation class
in Java.
The API distinguishes between two kinds of annotations:
- Java annotations: annotations on definitions produced by the Java compiler, i.e., subtypes of
java.lang.annotation.Annotation
attached to program definitions. When read by Scala reflection, thescala.annotation.ClassfileAnnotation
trait is automatically added as a subclass to every Java annotation. - Scala annotations: annotations on definitions or types produced by the Scala compiler.
The distinction between Java and Scala annotations is manifested in the
contract of scala.reflect.api.Annotations#Annotation
, which exposes both
scalaArgs
and javaArgs
. For Scala or Java annotations extending
scala.annotation.ClassfileAnnotation
scalaArgs
is empty and the arguments
(if any) are stored in javaArgs
. For all other Scala annotations, the
arguments are stored in scalaArgs
and javaArgs
is empty.
Arguments in scalaArgs
are represented as typed trees. Note that these trees
are not transformed by any phases following the type-checker. Arguments in
javaArgs
are represented as a map from scala.reflect.api.Names#Name
to
scala.reflect.api.Annotations#JavaArgument
. Instances of JavaArgument
represent different kinds of Java annotation arguments:
- literals (primitive and string constants),
- arrays, and
- nested annotations.
Names
Names are simple wrappers for strings.
Name
has two subtypes TermName
and TypeName
which distinguish names of terms (like
objects or members) and types (like classes, traits, and type members). A term
and a type of the same name can co-exist in the same object. In other words,
types and terms have separate name spaces.
Names are associated with a universe. Example:
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> val mapName = TermName("map")
mapName: scala.reflect.runtime.universe.TermName = map
Above, we’re creating a Name
associated with the runtime reflection universe
(this is also visible in its path-dependent type
reflect.runtime.universe.TermName
).
Names are often used to look up members of types. For example, to search for
the map
method (which is a term) declared in the List
class, one can do:
scala> val listTpe = typeOf[List[Int]]
listTpe: scala.reflect.runtime.universe.Type = scala.List[Int]
scala> listTpe.member(mapName)
res1: scala.reflect.runtime.universe.Symbol = method map
To search for a type member, one can follow the same procedure, using
TypeName
instead.
Standard Names
Certain names, such as “_root_
”, have special meanings in Scala programs. As
such they are essential for reflectively accessing certain Scala constructs.
For example, reflectively invoking a constructor requires using the
standard name universe.termNames.CONSTRUCTOR
, the term name <init>
which represents the
constructor name on the JVM.
There are both
- standard term names, e.g., “
<init>
”, “package
”, and “_root_
”, and - standard type names, e.g., “
<error>
”, “_
”, and “_*
”.
Some names, such as “package”, exist both as a type name and a term name.
Standard names are made available through the termNames
and typeNames
members of
class Universe
. For a complete specification of all standard names, see the
API documentation.
Scopes
A scope object generally maps names to symbols available in a corresponding lexical scope. Scopes can be nested. The base type exposed in the reflection API, however, only exposes a minimal interface, representing a scope as an iterable of Symbols.
Additional functionality is exposed in member scopes that are returned by
members
and decls
defined in
scala.reflect.api.Types#TypeApi.
scala.reflect.api.Scopes#MemberScope
supports the sorted
method, which sorts members in declaration order.
The following example returns a list of the symbols of all final members
of the List
class, in declaration order:
scala> val finals = listTpe.decls.sorted.filter(_.isFinal)
finals: List(method isEmpty, method map, method collect, method flatMap, method takeWhile, method span, method foreach, method reverse, method foldRight, method length, method lengthCompare, method forall, method exists, method contains, method find, method mapConserve, method toList)
Exprs
In addition to type scala.reflect.api.Trees#Tree
, the base type of abstract
syntax trees, typed trees can also be represented as instances of type
scala.reflect.api.Exprs#Expr
.
An Expr
wraps
an abstract syntax tree and an internal type tag to provide access to the type
of the tree. Expr
s are mainly used to simply and conveniently create typed
abstract syntax trees for use in a macro. In most cases, this involves methods
reify
and splice
(see the
macros guide for details).
Flags and flag sets
Flags are used to provide modifiers for abstract syntax trees that represent
definitions via the flags
field of scala.reflect.api.Trees#Modifiers
.
Trees that accept modifiers are:
scala.reflect.api.Trees#ClassDef
. Classes and traits.scala.reflect.api.Trees#ModuleDef
. Objects.scala.reflect.api.Trees#ValDef
. Vals, vars, parameters, and self type annotations.scala.reflect.api.Trees#DefDef
. Methods and constructors.scala.reflect.api.Trees#TypeDef
. Type aliases, abstract type members and type parameters.
For example, to create a class named C
one would write something like:
ClassDef(Modifiers(NoFlags), TypeName("C"), Nil, ...)
Here, the flag set is empty. To make C
private, one would write something
like:
ClassDef(Modifiers(PRIVATE), TypeName("C"), Nil, ...)
Flags can also be combined with the vertical bar operator (|
). For example,
a private final class is written something like:
ClassDef(Modifiers(PRIVATE | FINAL), TypeName("C"), Nil, ...)
The list of all available flags is defined in
scala.reflect.api.FlagSets#FlagValues
, available via
scala.reflect.api.FlagSets#Flag
. (Typically, one uses a wildcard import for
this, e.g., import scala.reflect.runtime.universe.Flag._
.)
Definition trees are compiled down to symbols, so that flags on modifiers of
these trees are transformed into flags on the resulting symbols. Unlike trees,
symbols don’t expose flags, but rather provide test methods following the
isXXX
pattern (e.g., isFinal
can be used to test finality). In some
cases, these test methods require a conversion using asTerm
, asType
, or
asClass
, as some flags only make sense for certain kinds of symbols.
Of note: This part of the reflection API is being considered a candidate for redesign. It is quite possible that in future releases of the reflection API, flag sets could be replaced with something else.
Constants
Certain expressions that the Scala specification calls constant expressions can be evaluated by the Scala compiler at compile time. The following kinds of expressions are compile-time constants (see section 6.24 of the Scala language specification):
-
Literals of primitive value classes (Byte, Short, Int, Long, Float, Double, Char, Boolean and Unit) - represented directly as the corresponding type.
-
String literals - represented as instances of the string.
-
References to classes, typically constructed with scala.Predef#classOf - represented as types.
-
References to Java enumeration values - represented as symbols.
Constant expressions are used to represent
- literals in abstract syntax trees (see
scala.reflect.api.Trees#Literal
), and - literal arguments for Java class file annotations (see
scala.reflect.api.Annotations#LiteralArgument
).
Example:
scala> Literal(Constant(5))
val res6: reflect.runtime.universe.Literal = 5
The above expression creates an AST representing the integer literal 5
in
Scala source code.
Constant
is an example of a “virtual case class”, i.e., a class whose
instances can be constructed and matched against as if it were a case class.
Both types Literal
and LiteralArgument
have a value
method returning the
compile-time constant underlying the literal.
Examples:
Constant(true) match {
case Constant(s: String) => println("A string: " + s)
case Constant(b: Boolean) => println("A Boolean value: " + b)
case Constant(x) => println("Something else: " + x)
}
assert(Constant(true).value == true)
Class references are represented as instances of
scala.reflect.api.Types#Type
. Such a reference can be converted to a runtime
class using the runtimeClass
method of a RuntimeMirror
such as
scala.reflect.runtime.currentMirror
. (This conversion from a type to a
runtime class is necessary, because when the Scala compiler processes a class
reference, the underlying runtime class might not yet have been compiled.)
Java enumeration value references are represented as symbols (instances of
scala.reflect.api.Symbols#Symbol
), which on the JVM point to methods that
return the underlying enumeration values. A RuntimeMirror
can be used to
inspect an underlying enumeration or to get the runtime value of a reference
to an enumeration.
Example:
// Java source:
enum JavaSimpleEnumeration { FOO, BAR }
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface JavaSimpleAnnotation {
Class<?> classRef();
JavaSimpleEnumeration enumRef();
}
@JavaSimpleAnnotation(
classRef = JavaAnnottee.class,
enumRef = JavaSimpleEnumeration.BAR
)
public class JavaAnnottee {}
// Scala source:
import scala.reflect.runtime.universe._
import scala.reflect.runtime.{currentMirror => cm}
object Test extends App {
val jann = typeOf[JavaAnnottee].typeSymbol.annotations(0).javaArgs
def jarg(name: String) = jann(TermName(name)) match {
// Constant is always wrapped in a Literal or LiteralArgument tree node
case LiteralArgument(ct: Constant) => value
case _ => sys.error("Not a constant")
}
val classRef = jarg("classRef").value.asInstanceOf[Type]
println(showRaw(classRef)) // TypeRef(ThisType(), JavaAnnottee, List())
println(cm.runtimeClass(classRef)) // class JavaAnnottee
val enumRef = jarg("enumRef").value.asInstanceOf[Symbol]
println(enumRef) // value BAR
val siblings = enumRef.owner.typeSignature.decls
val enumValues = siblings.filter(sym => sym.isVal && sym.isPublic)
println(enumValues) // Scope {
// final val FOO: JavaSimpleEnumeration;
// final val BAR: JavaSimpleEnumeration
// }
val enumClass = cm.runtimeClass(enumRef.owner.asClass)
val enumValue = enumClass.getDeclaredField(enumRef.name.toString).get(null)
println(enumValue) // BAR
}
Printers
Utilities for nicely printing
Trees
and
Types
.
Printing Trees
The method show
displays the “prettified” representation of reflection
artifacts. This representation provides one with the desugared Java
representation of Scala code. For example:
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> def tree = reify { final class C { def x = 2 } }.tree
tree: scala.reflect.runtime.universe.Tree
scala> show(tree)
res0: String =
{
final class C extends AnyRef {
def <init>() = {
super.<init>();
()
};
def x = 2
};
()
}
The method showRaw
displays the internal structure of a given reflection
object as a Scala abstract syntax tree (AST), the representation that the
Scala typechecker operates on.
Note that while this representation appears to generate correct trees that one might think would be possible to use in a macro implementation, this is not usually the case. Symbols aren’t fully represented (only their names are). Thus, this method is best-suited for use simply inspecting ASTs given some valid Scala code.
scala> showRaw(tree)
res1: String = Block(List(
ClassDef(Modifiers(FINAL), TypeName("C"), List(), Template(
List(Ident(TypeName("AnyRef"))),
emptyValDef,
List(
DefDef(Modifiers(), termNames.CONSTRUCTOR, List(), List(List()), TypeTree(),
Block(List(
Apply(Select(Super(This(typeNames.EMPTY), typeNames.EMPTY), termNames.CONSTRUCTOR), List())),
Literal(Constant(())))),
DefDef(Modifiers(), TermName("x"), List(), List(), TypeTree(),
Literal(Constant(2))))))),
Literal(Constant(())))
The method showRaw
can also print scala.reflect.api.Types
next to the artifacts being inspected.
scala> import scala.tools.reflect.ToolBox // requires scala-compiler.jar
import scala.tools.reflect.ToolBox
scala> import scala.reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.{currentMirror=>cm}
scala> showRaw(cm.mkToolBox().typeCheck(tree), printTypes = true)
res2: String = Block[1](List(
ClassDef[2](Modifiers(FINAL), TypeName("C"), List(), Template[3](
List(Ident[4](TypeName("AnyRef"))),
emptyValDef,
List(
DefDef[2](Modifiers(), termNames.CONSTRUCTOR, List(), List(List()), TypeTree[3](),
Block[1](List(
Apply[4](Select[5](Super[6](This[3](TypeName("C")), typeNames.EMPTY), ...))),
Literal[1](Constant(())))),
DefDef[2](Modifiers(), TermName("x"), List(), List(), TypeTree[7](),
Literal[8](Constant(2))))))),
Literal[1](Constant(())))
[1] TypeRef(ThisType(scala), scala.Unit, List())
[2] NoType
[3] TypeRef(NoPrefix, TypeName("C"), List())
[4] TypeRef(ThisType(java.lang), java.lang.Object, List())
[5] MethodType(List(), TypeRef(ThisType(java.lang), java.lang.Object, List()))
[6] SuperType(ThisType(TypeName("C")), TypeRef(... java.lang.Object ...))
[7] TypeRef(ThisType(scala), scala.Int, List())
[8] ConstantType(Constant(2))
Printing Types
The method show
can be used to produce a readable string representation of a type:
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> def tpe = typeOf[{ def x: Int; val y: List[Int] }]
tpe: scala.reflect.runtime.universe.Type
scala> show(tpe)
res0: String = scala.AnyRef{def x: Int; val y: scala.List[Int]}
Like the method showRaw
for scala.reflect.api.Trees
, showRaw
for
scala.reflect.api.Types
provides a visualization of the Scala AST operated
on by the Scala typechecker.
scala> showRaw(tpe)
res1: String = RefinedType(
List(TypeRef(ThisType(scala), TypeName("AnyRef"), List())),
Scope(
TermName("x"),
TermName("y")))
The showRaw
method also has named parameters printIds
and printKinds
,
both with default argument false
. When passing true
to these, showRaw
additionally shows the unique identifiers of symbols, as well as their kind
(package, type, method, getter, etc.).
scala> showRaw(tpe, printIds = true, printKinds = true)
res2: String = RefinedType(
List(TypeRef(ThisType(scala#2043#PK), TypeName("AnyRef")#691#TPE, List())),
Scope(
TermName("x")#2540#METH,
TermName("y")#2541#GET))
Positions
Positions (instances of the
Position trait)
are used to track the origin of symbols and tree nodes. They are commonly used when
displaying warnings and errors, to indicate the incorrect point in the
program. Positions indicate a column and line in a source file (the offset
from the beginning of the source file is called its “point”, which is
sometimes less convenient to use). They also carry the content of the line
they refer to. Not all trees or symbols have a position; a missing position is
indicated using the NoPosition
object.
Positions can refer either to only a single character in a source file, or to
a range. In the latter case, a range position is used (positions that are
not range positions are also called offset positions). Range positions have
in addition start
and end
offsets. The start
and end
offsets can be
“focused” on using the focusStart
and focusEnd
methods which return
positions (when called on a position which is not a range position, they just
return this
).
Positions can be compared using methods such as precedes
, which holds if
both positions are defined (i.e., the position is not NoPosition
) and the
end point of this
position is not larger than the start point of the given
position. In addition, range positions can be tested for inclusion (using
method includes
) and overlapping (using method overlaps
).
Range positions are either transparent or opaque (not transparent). The fact whether a range position is opaque or not has an impact on its permitted use, because trees containing range positions must satisfy the following invariants:
- A tree with an offset position never contains a child with a range position
- If the child of a tree with a range position also has a range position, then the child’s range is contained in the parent’s range.
- Opaque range positions of children of the same node are non-overlapping (this means their overlap is at most a single point).
Using the makeTransparent
method, an opaque range position can be converted
to a transparent one; all other positions are returned unchanged.