summaryrefslogtreecommitdiff
path: root/src/reflect/scala/reflect/api/Symbols.scala
diff options
context:
space:
mode:
authorEugene Burmako <xeno.by@gmail.com>2012-10-04 07:37:41 +0200
committerEugene Burmako <xeno.by@gmail.com>2012-10-11 19:53:52 +0200
commit6eb48f9602c3a21c85a38651c2e0b887e06b8d18 (patch)
tree641403b5a61dcc12d3419c0028a786ffc21cafff /src/reflect/scala/reflect/api/Symbols.scala
parent553ee0118dbc052bed8c4580376b48cd9cb5d0f9 (diff)
downloadscala-6eb48f9602c3a21c85a38651c2e0b887e06b8d18.tar.gz
scala-6eb48f9602c3a21c85a38651c2e0b887e06b8d18.tar.bz2
scala-6eb48f9602c3a21c85a38651c2e0b887e06b8d18.zip
docs for reflection and macros
Diffstat (limited to 'src/reflect/scala/reflect/api/Symbols.scala')
-rw-r--r--src/reflect/scala/reflect/api/Symbols.scala243
1 files changed, 229 insertions, 14 deletions
diff --git a/src/reflect/scala/reflect/api/Symbols.scala b/src/reflect/scala/reflect/api/Symbols.scala
index e456428338..371e20cdd4 100644
--- a/src/reflect/scala/reflect/api/Symbols.scala
+++ b/src/reflect/scala/reflect/api/Symbols.scala
@@ -1,10 +1,206 @@
package scala.reflect
package api
-/**
- * Defines the type hierachy for symbols
+/** A slice of [[scala.reflect.api.Universe the Scala reflection cake]] that defines symbols 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.
+ * === Symbols from a compile-time perspective ===
+ *
+ * [[http://dcsobral.blogspot.ch/2012/08/json-serialization-with-reflection-in.html To quote Daniel Sobral]],
+ * anything you define in Scala has a symbol. If you give something a name,
+ * then it has a symbol associated with it. If you didn't give it a name, but you could have, then it has a symbol.
+ *
+ * Symbols are used by the Scala compiler to establish bindings. When typechecking a Scala program,
+ * the compiler populates [[scala.reflect.api.Trees#RefTrees ref trees]], such as [[scala.reflect.api.Trees#Ident Ident]]
+ * (references to identifiers) and [[scala.reflect.api.Trees#Select Select]] (references to members)
+ * with symbols that represent the declarations being referred to. Populating means setting the `symbol`
+ * field to a non-empty value.
+ *
+ * Here's an example of how trees look after the `typer` phase of the Scala compiler (this phase performs the typechecking).
+ * {{{
+ * >cat Test.scala
+ * def foo[T: TypeTag](x: Any) = x.asInstanceOf[T]
+ *
+ * >scalac -Xprint:typer -uniqid Test.scala
+ * [[syntax trees at end of typer]]// Scala source: Test.scala
+ * def foo#8339
+ * [T#8340 >: Nothing#4658 <: Any#4657]
+ * (x#9529: Any#4657)
+ * (implicit evidence$1#9530: TypeTag#7861[T#8341])
+ * : T#8340 =
+ * x#9529.asInstanceOf#6023[T#8341];
+ * }}}
+ *
+ * Shortly put, we write a small snippet and then compile it with scalac, asking the compiler to dump the trees
+ * after the typer phase, printing unique ids of the symbols assigned to trees (if any).
+ *
+ * The resulting printout shows that identifiers have been linked to corresponding definitions.
+ * For example, on the one hand, the `ValDef("x", ...)`, which represents the parameter of the method `foo`,
+ * defines a method symbol with `id=9529`. On the other hand, the `Ident("x")` in the body of the method
+ * got its `symbol` field set to the same symbol, which establishes the binding.
+ *
+ * In the light of this discussion, it might come as a surprise that the definition of the type parameter `T`
+ * has a symbol with `id=8340`, whereas references to this type parameter all have a symbol with `id=8341`.
+ * This is the only exception from the general principe outlined above. It happens because the Scala compiler
+ * skolemizes type parameters (creates new symbols very similar to the original ones) before entering scopes
+ * that define these parameters. This is an advanced feature of Scala, and the knowledge of it is needed only
+ * when writing complex macros, but symbols in the macro universe [[scala.reflect.macros.Universe]] have the
+ * `deskolemize` method, which goes back from skolems to the originating type parameters.
+ *
+ * === Symbols from a runtime perspective ===
+ *
+ * From the point of view of a runtime reflection framework, symbols are akin to [[java.lang.reflect.Member]] from Java
+ * and [[System.Reflection.MemberInfo]] from .NET. But not only they represent members - they also represent
+ * classes, objects and even packages.
+ *
+ * Also similarly to the base classes in the reflection facilities of JVM and .NET, Scala symbols have subclasses
+ * that describe particular flavors of definitions. [[scala.reflect.api.Symbols#TermSymbol]] models term definitions
+ * (such as lazy and eager vals, vars and parameters of methods). Its subclasses are [[scala.reflect.api.Symbols#MethodSymbol]]
+ * and [[scala.reflect.api.Symbols#ModuleSymbol]] (representing "modules", which in Scala compiler speak mean "objects").
+ * [[scala.reflect.api.Symbols#TypeSymbol]] along with its subclass [[scala.reflect.api.Symbols#ClassSymbol]]
+ * describes type definitions in Scala (type aliases, type members, type parameters, classes and traits).
+ *
+ * Most reflection APIs that return symbols return non-specific [[scala.reflect.api.Symbols#Symbol]], because upon failure
+ * they don't raise exceptions, but rather produce `NoSymbol`, a special singleton, which is a null object for symbols.
+ * Therefore to use such APIs one has to first check whether a callee returned a valid symbol and, if yes, then perform
+ * a cast using one of the `asTerm`, `asMethod`, `asModule`, `asType` or `asClass` methods. This is arguably inconvenient
+ * and might be improved in the future.
+ *
+ * Unlike [[scala.reflect.api.Trees trees]] and [[scala.reflect.api.Types types]], symbols should not be created directly.
+ * Instead one should load the symbols from the global symbol table maintained by the compiler.
+ * To get a symbol that corresponds to a top-level class or object, one can use the `staticClass` and `staticModule`
+ * methods of [[scala.reflect.api.Mirror]]. To get a symbol that corresponds to a certain member, there are `members`
+ * and `declarations` methods of [[scala.reflect.api.Types#Type]], which brings the discussion to the next point: type signatures.
+ *
+ * Each symbol has a type signature, which describes its type and is available via the `typeSignature` method
+ * on [[scala.reflect.api.Symbols#Symbol]]. Classes have signatures of the [[scala.reflect.api.Types#ClassInfoType]] type,
+ * which knows the list of its members and declarations. Modules per se don't have interesting signatures. To access members
+ * of modules, one first has to obtain a module class (using the `moduleClass` method) and then inspect its signature.
+ * Members have type signatures of their own: method signatures feature information about parameters and result types,
+ * type member signatures store upper and lower bounds and so on.
+ *
+ * One thing to know about type signatures is that `typeSignature` method always returns signatures in the most generic
+ * way possible, even if the underlying symbol is obtained from an instantiation of a generic type. For example, signature
+ * of the method `def map[B](f: (A) ⇒ B): List[B]`, which refers to the type parameter `A` of the declaring class `List[A]`,
+ * will always feature `A`, regardless of whether `map` is loaded from the `List[_]` or from `List[Int]`. To get a signature
+ * with type parameters appropriately instantiated, one should use `typeSignatureIn`.
+ *
+ * Symbols are at the heart of the reflection API. Along with the type signatures, which are arguably the most important
+ * use of reflection, they provide comprehensive information about the underlying definitions. This includes various
+ * `isXXX` test methods such as `isPublic` or `isFinal`, `params` and `returnType` methods for method symbols,
+ * `baseClasses` for class symbols and so on. Be prepared - some of these methods don't make sense on the ultimate
+ * base class Symbol, so they are declared in subclasses.
+ *
+ * === Exploring symbols ===
+ *
+ * In this example we'll try to get a hold on a symbol that represents the `map` method of `List`,
+ * and then do something interesting with it.
+ *
+ * First of all, to obtain a symbol, one needs to load its enclosing top-level class or module.
+ * There are two ways of doing that. The first one is getting a symbol by its name using a mirror
+ * (refer to [[scala.reflect.api.package the reflection overview]] for information about mirrors).
+ * Another one is getting a type with [[scaa.reflect.api.Types#typeOf]] and using its `typeSymbol` method.
+ * The second approach is preferable, because it's typesafe, but sometimes it's unavailable.
+ *
+ * {{{
+ * scala> import scala.reflect.runtime.universe._
+ * import scala.reflect.runtime.universe._
+ *
+ * scala> val cm = runtimeMirror(getClass.getClassLoader)
+ * cm: reflect.runtime.universe.Mirror = JavaMirror with ...
+ *
+ * scala> val list = cm.staticClass("scala.List")
+ * list: reflect.runtime.universe.ClassSymbol = class List
+ *
+ * scala> val list = typeOf[List[_]].typeSymbol
+ * list: reflect.runtime.universe.Symbol = class List
+ * }}}
+ *
+ * Now when the enclosing class is obtained, there's a straight path to getting its member
+ * using `typeSignature` and `member` methods discussed above:
+ *
+ * {{{
+ * scala> val map = list.typeSignature.member("map": TermName).asMethod
+ * map: reflect.runtime.universe.MethodSymbol = method map
+ *
+ * scala> map.typeSignature
+ * res0: reflect.runtime.universe.Type = [B, That](f: A => B)(implicit bf:
+ * scala.collection.generic.CanBuildFrom[Repr,B,That])That
+ *
+ * scala> map.typeSignatureIn(typeOf[List[Int]])
+ * res1: reflect.runtime.universe.Type = [B, That](f: Int => B)(implicit bf:
+ * scala.collection.generic.CanBuildFrom[List[Int],B,That])That
+ *
+ * scala> map.params
+ * res2: List[List[reflect.runtime.universe.Symbol]] = List(List(value f), List(value bf))
+ *
+ * scala> val filter = map.params(0)(0)
+ * filter: reflect.runtime.universe.Symbol = value f
+ *
+ * scala> filter.name
+ * res3: reflect.runtime.universe.Name = f
+ *
+ * scala> filter.typeSignature
+ * res4: reflect.runtime.universe.Type = A => B
+ * }}}
+ *
+ * === Gotcha #1: Overloaded methods ===
+ *
+ * Be careful though, because overloaded methods are represented as instances of TermSymbol
+ * with multiple `alternatives` that have to be resolved manually. For example, a lookup
+ * for a member named `mkString` will produce not a MethodSymbol, but a TermSymbol:
+ *
+ * {{{
+ * scala> list.typeSignature.member("mkString": TermName)
+ * res1: reflect.runtime.universe.Symbol = value mkString
+ *
+ * scala> val mkString = list.typeSignature.member("mkString": TermName).asTerm
+ * mkString: reflect.runtime.universe.TermSymbol = value mkString
+ *
+ * scala> mkString.isMethod
+ * res0: Boolean = false
+ *
+ * scala> mkString.alternatives
+ * res1: List[reflect.runtime.universe.Symbol] = List(method mkString, method mkString, method mkString)
+ *
+ * scala> mkString.alternatives foreach println
+ * method mkString
+ * method mkString
+ * method mkString
+ *
+ * scala> mkString.alternatives foreach (alt => println(alt.typeSignature))
+ * => String
+ * (sep: String)String
+ * (start: String, sep: String, end: String)String
+ * }}}
+ *
+ * Once one has a symbol, that symbol can be used for reflective invocations. For example,
+ * having a TermSymbol corresponding to a field it's possible to get or set a value of that field.
+ * Having a MethodSymbol makes it possible to invoke the corresponding methods. ClassSymbols
+ * can be instantiated. ModuleSymbols can provide corresponding singleton instances. This is described
+ * in detail on [[scala.reflect.api.package the reflection overview page]].
+ *
+ * === Gotcha #2: Module classes ===
+ *
+ * Internally the Scala compiler represents objects with two symbols: a module symbol and a module class symbol.
+ * The former is a term symbol, used everywhere a module is referenced (e.g. in singleton types or in expressions),
+ * while the latter is a type symbol, which carries the type signature (i.e. the member list) of the module.
+ * This implementation detail can be easily seen by compiling a trivial snippet of code. Invoking the Scala
+ * compiler on `object C` will generate C$.class. That's exactly the module class.
+ *
+ * Note that module classes are different from companion classes. Say, for `case class C`, the compiler
+ * will generate three symbols: `type C`, `term C` and (another one) `type C`, where the first type `C`
+ * represents the class `C` (which contains auto-generated `copy`, `productPrefix`, `productArity` etc) and
+ * the second type `C` represents the signature of object `C` (which contains auto-generated factory,
+ * extractor etc). There won't be any name clashes, because the module class isn't added to the symbol table
+ * directly and is only available through `<module>.moduleClass`. For the sake of completeness, it is possible
+ * to go back from a module class to a module via `<module class>.module`.
+ *
+ * Separation between modules and module classes is something that we might eliminate in the future, but for now
+ * this obscure implementation detail has to be taken into account when working with reflection. On the one hand,
+ * it is necessary to go to a module class to get a list of members for an object. On the other hand, it is
+ * necessary to go from a module class back to a module to get a singleton instance of an object. The latter
+ * scenario is described at Stack Overflow: [[http://stackoverflow.com/questions/12128783 How can I get the actual object referred to by Scala 2.10 reflection?]].
*/
trait Symbols { self: Universe =>
@@ -79,7 +275,9 @@ trait Symbols { self: Universe =>
/** A special "missing" symbol */
val NoSymbol: Symbol
- /** The API of symbols */
+ /** The API of symbols.
+ * The main source of information about symbols is the [[scala.reflect.api.Symbols]] page.
+ */
trait SymbolApi { this: Symbol =>
/** The owner of this symbol. This is the symbol
@@ -357,11 +555,14 @@ trait Symbols { self: Universe =>
/******************* helpers *******************/
- /** ...
+ /** Provides an alternate if symbol is a NoSymbol.
*/
def orElse(alt: => Symbol): Symbol
- /** ...
+ /** Filters the underlying alternatives (or a single-element list
+ * composed of the symbol itself if the symbol is not overloaded).
+ * Returns an overloaded symbol is there are multiple matches.
+ * Returns a NoSymbol if there are no matches.
*/
def filter(cond: Symbol => Boolean): Symbol
@@ -370,12 +571,14 @@ trait Symbols { self: Universe =>
*/
def map(f: Symbol => Symbol): Symbol
- /** ...
+ /** Does the same as `filter`, but crashes if there are multiple matches.
*/
def suchThat(cond: Symbol => Boolean): Symbol
}
- /** The API of term symbols */
+ /** The API of term symbols.
+ * The main source of information about symbols is the [[scala.reflect.api.Symbols]] page.
+ */
trait TermSymbolApi extends SymbolApi { this: TermSymbol =>
/** Term symbols have their names of type `TermName`.
*/
@@ -456,7 +659,9 @@ trait Symbols { self: Universe =>
def isByNameParam: Boolean
}
- /** The API of type symbols */
+ /** The API of type symbols.
+ * The main source of information about symbols is the [[scala.reflect.api.Symbols]] page.
+ */
trait TypeSymbolApi extends SymbolApi { this: TypeSymbol =>
/** Type symbols have their names of type `TypeName`.
*/
@@ -521,7 +726,9 @@ trait Symbols { self: Universe =>
def typeParams: List[Symbol]
}
- /** The API of method symbols */
+ /** The API of method symbols.
+ * The main source of information about symbols is the [[scala.reflect.api.Symbols]] page.
+ */
trait MethodSymbolApi extends TermSymbolApi { this: MethodSymbol =>
final override def isMethod = true
final override def asMethod = this
@@ -556,7 +763,9 @@ trait Symbols { self: Universe =>
def returnType: Type
}
- /** The API of module symbols */
+ /** The API of module symbols.
+ * The main source of information about symbols is the [[scala.reflect.api.Symbols]] page.
+ */
trait ModuleSymbolApi extends TermSymbolApi { this: ModuleSymbol =>
/** The class implicitly associated with the object definition.
* One can go back from a module class to the associated module symbol
@@ -569,7 +778,9 @@ trait Symbols { self: Universe =>
final override def asModule = this
}
- /** The API of class symbols */
+ /** The API of class symbols.
+ * The main source of information about symbols is the [[scala.reflect.api.Symbols]] page.
+ */
trait ClassSymbolApi extends TypeSymbolApi { this: ClassSymbol =>
final override def isClass = true
final override def asClass = this
@@ -635,7 +846,9 @@ trait Symbols { self: Universe =>
def typeParams: List[Symbol]
}
- /** The API of free term symbols */
+ /** The API of free term symbols.
+ * The main source of information about symbols is the [[scala.reflect.api.Symbols]] page.
+ */
trait FreeTermSymbolApi extends TermSymbolApi { this: FreeTermSymbol =>
final override def isFreeTerm = true
final override def asFreeTerm = this
@@ -647,7 +860,9 @@ trait Symbols { self: Universe =>
def value: Any
}
- /** The API of free term symbols */
+ /** The API of free type symbols.
+ * The main source of information about symbols is the [[scala.reflect.api.Symbols]] page.
+ */
trait FreeTypeSymbolApi extends TypeSymbolApi { this: FreeTypeSymbol =>
final override def isFreeType = true
final override def asFreeType = this