Environment, Universes, and Mirrors

Environment

The reflection environment differs based on whether the reflective task is to
be done at run time or at compile time. The distinction between an environment to be used at
run time or compile time is encapsulated in a so-called universe. Another
important aspect of the reflective environment is the set of entities that we
have reflective access to. This set of entities is determined by a so-called
mirror.

For example, the entities accessible through runtime
reflection are made available by a ClassloaderMirror. This mirror provides
only access to entities (packages, types, and members) loaded by a specific
classloader.

Mirrors not only determine the set of entities that can be accessed
reflectively. They also provide reflective operations to be performed on those
entities. For example, in runtime reflection an invoker mirror can be used
to invoke a method or constructor of a class.

Universes

There are two principal
types of universes– since there exists both runtime and compile-time
reflection capabilities, one must use the universe that corresponds to
whatever the task is at hand. Either:

scala.reflect.runtime.universe for runtime reflection, or

scala.reflect.macros.Universe for compile-time reflection.

A universe provides an interface to all the principal concepts used in
reflection, such as Types, Trees, and Annotations.

Mirrors

All information provided by
reflection is made accessible through mirrors. Depending on
the type of information to be obtained, or the reflective action to be taken,
different flavors of mirrors must be used. Classloader mirrors can be used to obtain representations of types and
members. From a classloader mirror, it’s possible to obtain more specialized invoker mirrors (the most commonly-used mirrors), which implement reflective
invocations, such as method or constructor calls and field accesses.

Compile-Time Mirrors

Compile-time mirrors make use of only classloader mirrors to load symbols by name.

The entry point to classloader mirrors is via scala.reflect.macros.Context#mirror. Typical methods which use classloader mirrors include scala.reflect.api.Mirror#staticClass, scala.reflect.api.Mirror#staticModule, and scala.reflect.api.Mirror#staticPackage. For example:

Of note: There are several high-level alternatives that one can use to avoid having to manually lookup symbols. For example, typeOf[Location.type].termSymbol (or typeOf[Location].typeSymbol if we needed a ClassSymbol), which are typesafe since we don’t have to use strings to lookup the symbol.