Skip to content

Commit

Permalink
docs for reflection and macros
Browse files Browse the repository at this point in the history
  • Loading branch information
xeno-by committed Oct 11, 2012
1 parent 553ee01 commit 6eb48f9
Show file tree
Hide file tree
Showing 40 changed files with 3,051 additions and 385 deletions.
133 changes: 113 additions & 20 deletions src/reflect/scala/reflect/api/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,133 @@ package api

import scala.collection.immutable.ListMap

/**
* Defines the type hierarchy for annotations.
/** A slice of [[scala.reflect.api.Universe the Scala reflection cake]] that defines annotations and operations on them.
* See [[scala.reflect.api.Universe]] for a description of how the reflection API is encoded with the cake pattern.
*
* Scala reflection supports:
* 1. Annotations on definitions or types produced by the Scala compiler, i.e. subtypes of both
* [[scala.annotation.StaticAnnotation]] and [[scala.annotation.ClassfileAnnotation]] attached to program definitions or types
* (note: subclassing just [[scala.annotation.Annotation]] is not enough to have the corresponding
* metadata persisted for runtime reflection).
* 1. 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, the [[scala.annotation.ClassfileAnnotation]] trait
* is automatically added as a subclass to every Java annotation.
*
* First of all [[scala.reflect.api.Annotations#Annotation]] provides `tpe`, which describes the type of the annotation.
* Depending on the superclasses of `tpe`, there are two flavors of annotations.
*
* When annotations that subclass of [[scala.annotation.StaticAnnotation]] (dubbed ''Scala annotations'') are compiled by the Scala compiler,
* the information about them is ''pickled'', i.e. stored in special attributes in class files. To the contrast,
* annotations subclassing [[scala.annotation.ClassfileAnnotation]] (called ''Java annotations'') are written to class files as Java annotations.
* This distinction is manifested in the contract of [[scala.reflect.api.Annotations#Annotation]], which exposes
* both `scalaArgs` and `javaArgs`.
*
* For Scala annotations, 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.
*
* For Java annotations, `scalaArgs` is empty and arguments are stored in `javaArgs`.
* In this case, unlike in Java, Scala reflection only provides a key-value store of type [[scala.collection.immutable.ListMap]] from [[scala.reflect.api.Names#Name]] to
* [[scala.reflect.api.Annotations#JavaArgument]] that describes the annotations. Instances of `JavaArgument`
* represent different kinds of Java annotation arguments: literals (primitive and string constants), arrays and nested annotations.
* One shoud match against [[scala.reflect.api.Annotations#LiteralArgument]], [[scala.reflect.api.Annotations#ArrayArgument]] and [[scala.reflect.api.Annotations#NestedArgument]]
* to analyze them. We acknowledge that this process can be made more convenient and created [[https://issues.scala-lang.org/browse/SI-6423 an issue]] in the issue tracker
* to discuss possible improvements and track progress.
*
* === Example ===
*
* Entry points to the annotation API are [[scala.reflect.api.Symbols#Symbol.annotations]] (for definition annotations)
* and [[scala.reflect.api.Types#AnnotatedType]] (for type annotations).
*
* To get annotations attached to a definition, first load the corresponding symbol (either explicitly using a [[scala.reflect.api.Mirror]]
* such as [[scala.reflect.runtime.package#currentMirror]]
* or implicitly using [[scala.reflect.api.TypeTags#typeOf]] and then either acquiring its `typeSymbol` or navigating its `members`).
* After the symbol is loaded, call its `annotations` method.
*
* When inspecting a symbol for annotations, one should make sure that the inspected symbol is indeed the target of the annotation being looked for.
* Since single Scala definitions might produce multiple underlying definitions in bytecode, sometimes the notion of annotation's target is convoluted.
* For example, by default an annotation placed on a `val` will be attached to the private underlying field rather than to the getter
* (therefore to get such an annotation, one needs to do not `getter.annotations`, but `getter.asTerm.accessed.annotations`).
* This can get nasty with abstract vals, which don't have underlying fields and therefore ignore their annotations unless special measures are taken.
* See [[scala.annotation.meta.package]] for more information.
*
* To get annotations attached to a type, simply pattern match that type against [[scala.reflect.api.Types#AnnotatedType]].
* {{{
* import scala.reflect.runtime.universe._
*
* class S(x: Int, y: Int) extends scala.annotation.StaticAnnotation
* class J(x: Int, y: Int) extends scala.annotation.ClassfileAnnotation
*
* object Test extends App {
* val x = 2
*
* // Scala annotations are the most flexible with respect to
* // the richness of metadata they can store.
* // Arguments of such annotations are stored as abstract syntax trees,
* // so they can represent and persist arbitrary Scala expressions.
* @S(x, 2) class C
* val c = typeOf[C].typeSymbol
* println(c.annotations) // List(S(Test.this.x, 2))
* val tree = c.annotations(0).scalaArgs(0)
* println(showRaw(tree)) // Select(..., newTermName("x"))
* println(tree.symbol.owner) // object Test
* println(showRaw(c.annotations(0).scalaArgs(1))) // Literal(Constant(2))
*
* // Java annotations are limited to predefined kinds of arguments:
* // literals (primitives and strings), arrays and nested annotations.
* @J(x = 2, y = 2) class D
* val d = typeOf[D].typeSymbol
* println(d.annotations) // List(J(x = 2, y = 2))
* println(d.annotations(0).javaArgs) // Map(x -> 2, y -> 2)
* }
* }}}
*/
trait Annotations { self: Universe =>

/** Typed information about an annotation. It can be attached to either a symbol or an annotated type.
*
* Annotations are either ''Scala annotations'', which conform to [[scala.annotation.StaticAnnotation]]
* or ''Java annotations'', which conform to [[scala.annotation.ClassfileAnnotation]].
* Trait `ClassfileAnnotation` is automatically added to every Java annotation by the scalac classfile parser.
*/
/** Information about an annotation. */
type Annotation >: Null <: AnyRef with AnnotationApi

/** A tag that preserves the identity of the `Annotation` abstract type from erasure.
* Can be used for pattern matching, instance tests, serialization and likes.
*/
implicit val AnnotationTag: ClassTag[Annotation]

/** The constructor/deconstructor for `Annotation` instances. */
/** The constructor/deconstructor for `Annotation` instances. */
val Annotation: AnnotationExtractor

/** An extractor class to create and pattern match with syntax `Annotation(atp, scalaArgs, javaArgs)`.
* Here, `atp` is the annotation type, `scalaArgs` the arguments, and `javaArgs` the annotation's key-value
* pairs.
*
* Annotations are pickled, i.e. written to scala symtab attribute in the classfile.
* Annotations are written to the classfile as Java annotations if `atp` conforms to `ClassfileAnnotation`.
*
* For Scala annotations, 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. For Java annotations, `scalaArgs` is empty and arguments are stored in
* `javaArgs`.
/** An extractor class to create and pattern match with syntax `Annotation(tpe, scalaArgs, javaArgs)`.
* Here, `tpe` is the annotation type, `scalaArgs` the payload of Scala annotations, and `javaArgs` the payload of Java annotations.
*/
abstract class AnnotationExtractor {
def apply(tpe: Type, scalaArgs: List[Tree], javaArgs: ListMap[Name, JavaArgument]): Annotation
def unapply(ann: Annotation): Option[(Type, List[Tree], ListMap[Name, JavaArgument])]
}

/** The API of `Annotation` instances.
* The main source of information about annotations is the [[scala.reflect.api.Annotations]] page.
*/
trait AnnotationApi {
/** The type of the annotation. */
def tpe: Type

/** Payload of the Scala annotation: a list of abstract syntax trees that represent the argument.
* Empty for Java annotations.
*/
def scalaArgs: List[Tree]

/** Payload of the Java annotation: a list of name-value pairs.
* Empty for Scala annotations.
*/
def javaArgs: ListMap[Name, JavaArgument]
}

/** A Java annotation argument */
type JavaArgument >: Null <: AnyRef

/** A tag that preserves the identity of the `JavaArgument` abstract type from erasure.
* Can be used for pattern matching, instance tests, serialization and likes.
*/
implicit val JavaArgumentTag: ClassTag[JavaArgument]

/** A literal argument to a Java annotation as `"Use X instead"` in `@Deprecated("Use X instead")`*/
Expand All @@ -70,7 +151,11 @@ trait Annotations { self: Universe =>
def unapply(arg: LiteralArgument): Option[Constant]
}

/** The API of `LiteralArgument` instances.
* The main source of information about annotations is the [[scala.reflect.api.Annotations]] page.
*/
trait LiteralArgumentApi {
/** The underlying compile-time constant value. */
def value: Constant
}

Expand All @@ -94,7 +179,11 @@ trait Annotations { self: Universe =>
def unapply(arg: ArrayArgument): Option[Array[JavaArgument]]
}

/** API of `ArrayArgument` instances.
* The main source of information about annotations is the [[scala.reflect.api.Annotations]] page.
*/
trait ArrayArgumentApi {
/** The underlying array of Java annotation arguments. */
def args: Array[JavaArgument]
}

Expand All @@ -118,7 +207,11 @@ trait Annotations { self: Universe =>
def unapply(arg: NestedArgument): Option[Annotation]
}

/** API of `NestedArgument` instances.
* The main source of information about annotations is the [[scala.reflect.api.Annotations]] page.
*/
trait NestedArgumentApi {
/** The underlying nested annotation. */
def annotation: Annotation
}
}
84 changes: 81 additions & 3 deletions src/reflect/scala/reflect/api/Constants.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,82 @@
package scala.reflect
package api

/**
* Defines the type hierachy for compile-time constants.
/** A slice of [[scala.reflect.api.Universe the Scala reflection cake]] that defines compile-time constants and operations on them.
* See [[scala.reflect.api.Universe]] for a description of how the reflection API is encoded with the cake pattern.
*
* @see [[scala.reflect]] for a description on how the class hierarchy is encoded here.
* According to the section 6.24 "Constant Expressions" of the Scala language specification,
* certain expressions (dubbed ''constant expressions'') can be evaluated by the Scala compiler at compile-time.
*
* [[scala.reflect.api.Constants#Constant]] instances represent certain kinds of these expressions
* (with values stored in the `value` field and its strongly-typed views named `booleanValue`, `intValue` etc.), namely:
* 1. Literals of primitive value classes (bytes, shorts, ints, longs, floats, doubles, chars, booleans and voids).
* 1. String literals.
* 1. References to classes (typically constructed with [[scala.Predef#classOf]]).
* 1. References to enumeration values.
*
* Such constants are used to represent literals in abstract syntax trees (the [[scala.reflect.api.Trees#Literal]] node)
* and literal arguments for Java class file annotations (the [[scala.reflect.api.Annotations#LiteralArgument]] class).
*
* === Example ===
*
* The `value` field deserves some explanation. Primitive and string values are represented as themselves, whereas
* references to classes and enums are a bit roundabout.
*
* Class references are represented as instances of [[scala.reflect.api.Types#Type]]
* (because when the Scala compiler processes a class reference, the underlying runtime class might not yet have been compiled).
* To convert such a reference to a runtime class, one should use the `runtimeClass` method of a mirror such as [[scala.reflect.api.Mirrors#RuntimeMirror]]
* (the simplest way to get such a mirror is using [[scala.reflect.runtime.package#currentMirror]]).
*
* Enumeration value references are represented as instances of [[scala.reflect.api.Symbols#Symbol]], which on JVM point to methods
* that return underlying enum values. To inspect an underlying enumeration or to get runtime value of a reference to an enum,
* one should use a [[scala.reflect.api.Mirrors#RuntimeMirror]] (the simplest way to get such a mirror is again [[scala.reflect.runtime.package#currentMirror]]).
* {{{
* 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 {}
* }}}
* {{{
* 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(newTermName(name)).asInstanceOf[LiteralArgument].value
*
* val classRef = jarg("classRef").typeValue
* println(showRaw(classRef)) // TypeRef(ThisType(<empty>), JavaAnnottee, List())
* println(cm.runtimeClass(classRef)) // class JavaAnnottee
*
* val enumRef = jarg("enumRef").symbolValue
* println(enumRef) // value BAR
*
* val siblings = enumRef.owner.typeSignature.declarations
* val enumValues = siblings.filter(sym => sym.isVal && sym.isPublic)
* println(enumValues) // Scope{
* // final val FOO: JavaSimpleEnumeration;
* // final val BAR: JavaSimpleEnumeration
* // }
*
* // doesn't work because of https://issues.scala-lang.org/browse/SI-6459
* // val enumValue = mirror.reflectField(enumRef.asTerm).get
* val enumClass = cm.runtimeClass(enumRef.owner.asClass)
* val enumValue = enumClass.getDeclaredField(enumRef.name.toString).get(null)
* println(enumValue) // BAR
* }
* }}}
*/
trait Constants {
self: Universe =>
Expand All @@ -34,8 +106,14 @@ trait Constants {
def unapply(arg: Constant): Option[Any]
}

/** The API of `Constant` instances.
* The main source of information about constants is the [[scala.reflect.api.Constants]] page.
*/
abstract class ConstantApi {
/** Payload of the constant. */
val value: Any

/** Scala type that describes the constant. */
def tpe: Type
}
}
55 changes: 47 additions & 8 deletions src/reflect/scala/reflect/api/Exprs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,38 @@ package api

import scala.reflect.runtime.{universe => ru}

/** A slice of [[scala.reflect.api.Universe the Scala reflection cake]] that defines strongly-typed tree wrappers and operations on them.
* See [[scala.reflect.api.Universe]] for a description of how the reflection API is encoded with the cake pattern.
*
* Expr wraps an abstract syntax tree ([[scala.reflect.api.Trees#Tree]]) and tags it with its type ([[scala.reflect.api.Types#Type]]).
*
* Usually exprs are created via [[scala.reflect.api.Universe#reify]], in which case a compiler
* produces a [[scala.reflect.api.TreeCreator]] for the provided expression and also
* creates a complementary [[scala.reflect.api.TypeTags#WeakTypeTag]] that corresponds to the type of that expression.
*
* Thanks to using TreeCreators, exprs are essentially tree factories, capable of instantiating
* themselves in any universe and mirror. This is achieved by the `in` method, which performs
* migration of a given expression to another mirror. Migration means that all symbolic references
* to classes/objects/packages in the expression are re-resolved within the new mirror
* (typically using that mirror's classloader). Default universe of an expr is typically
* [[scala.reflect.runtime.package#universe]], default mirror is typically [[scala.reflect.runtime.package#currentMirror]].
*
* Exprs can also be created manually, but then the burden of providing a TreeCreator lies on the programmer.
* However, on the one hand, manual creation is very rarely needed when working with runtime reflection,
* while, on the other hand, compile-time reflection via macros provides an easier way to instantiate exprs,
* described in [[scala.reflect.macros.Aliases]].
*
* === Known issues ===
*
* Exprs are marked as serializable, but this functionality is not yet implemented.
* An issue tracker entry: [[https://issues.scala-lang.org/browse/SI-5919 https://issues.scala-lang.org/browse/SI-5919]]
* has been created to track the implementation of this feature.
*/
trait Exprs { self: Universe =>

/** Expr wraps an expression tree and tags it with its type. */
/** Expr wraps an abstract syntax tree and tags it with its type.
* The main source of information about exprs is the [[scala.reflect.api.Exprs]] page.
*/
trait Expr[+T] extends Equals with Serializable {
/**
* Underlying mirror of this expr.
Expand All @@ -19,23 +48,24 @@ trait Exprs { self: Universe =>

/**
* Migrates the expression into another mirror, jumping into a different universe if necessary.
*
* This means that all symbolic references to classes/objects/packages in the expression
* will be re-resolved within the new mirror (typically using that mirror's classloader).
*/
def in[U <: Universe with Singleton](otherMirror: scala.reflect.api.Mirror[U]): U # Expr[T]

/**
* The Scala syntax tree representing the wrapped expression.
* The Scala abstract syntax tree representing the wrapped expression.
*/
def tree: Tree

/**
* Representation of the type of the wrapped expression tree as found via type tags.
* Type of the wrapped expression tree as provided during creation.
*
* When exprs are created by the compiler, `staticType` represents
* a statically known type of the tree as calculated at that point by the compiler.
*/
def staticType: Type

/**
* Representation of the type of the wrapped expression tree as found in the tree.
* Type of the wrapped expression tree as found in the underlying tree.
*/
def actualType: Type

Expand Down Expand Up @@ -65,6 +95,7 @@ trait Exprs { self: Universe =>
* because expr of type Expr[T] itself does not have a method foo.
*/
def splice: T

/**
* A dummy value to denote cross-stage path-dependent type dependencies.
*
Expand All @@ -81,10 +112,16 @@ trait Exprs { self: Universe =>
*/
val value: T

/** case class accessories */
/** TODO how do I doc this? */
override def canEqual(x: Any) = x.isInstanceOf[Expr[_]]

/** TODO how do I doc this? */
override def equals(x: Any) = x.isInstanceOf[Expr[_]] && this.mirror == x.asInstanceOf[Expr[_]].mirror && this.tree == x.asInstanceOf[Expr[_]].tree

/** TODO how do I doc this? */
override def hashCode = mirror.hashCode * 31 + tree.hashCode

/** TODO how do I doc this? */
override def toString = "Expr["+staticType+"]("+tree+")"
}

Expand All @@ -93,6 +130,8 @@ trait Exprs { self: Universe =>
*
* Can be useful, when having a tree and wanting to splice it in reify call,
* in which case the tree first needs to be wrapped in an expr.
* The main source of information about exprs is the [[scala.reflect.api.Exprs]] page.
*/
object Expr {
def apply[T: WeakTypeTag](mirror: scala.reflect.api.Mirror[self.type], treec: TreeCreator): Expr[T] = new ExprImpl[T](mirror.asInstanceOf[Mirror], treec)
Expand Down

0 comments on commit 6eb48f9

Please sign in to comment.