scala.reflect.internal.AnnotationInfos.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-reflect Show documentation
Show all versions of scala-reflect Show documentation
Reflection Library for the Scala Programming Language
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala
package reflect
package internal
import scala.annotation.tailrec
import scala.collection.immutable.ListMap
import scala.language.postfixOps
/** AnnotationInfo and its helpers */
trait AnnotationInfos extends api.Annotations { self: SymbolTable =>
import definitions._
// Common annotation code between Symbol and Type.
// For methods altering the annotation list, on Symbol it mutates
// the Symbol's field directly. For Type, a new AnnotatedType is
// created which wraps the original type.
trait Annotatable[Self] {
/** The annotations on this type. */
def annotations: List[AnnotationInfo] // Annotations on this type.
def setAnnotations(annots: List[AnnotationInfo]): Self // Replace annotations with argument list.
def withAnnotations(annots: List[AnnotationInfo]): Self // Add annotations to this type.
def filterAnnotations(p: AnnotationInfo => Boolean): Self // Retain only annotations meeting the condition.
def withoutAnnotations: Self // Remove all annotations from this type.
def staticAnnotations = annotations filter (_.isStatic)
def addThrowsAnnotation(throwableSym: Symbol): Self = {
val throwableTpe = if (throwableSym.isMonomorphicType) throwableSym.tpe else {
debuglog(s"Encountered polymorphic exception `${throwableSym.fullName}` while parsing class file.")
// in case we encounter polymorphic exception the best we can do is to convert that type to
// monomorphic one by introducing existentials, see scala/bug#7009 for details
existentialAbstraction(throwableSym.typeParams, throwableSym.tpe)
}
this withAnnotation AnnotationInfo(appliedType(ThrowsClass, throwableTpe :: Nil), List(Literal(Constant(throwableTpe))), Nil)
}
/** Tests for, get, or remove an annotation */
def hasAnnotation(cls: Symbol): Boolean =
//OPT inlined from exists to save on #closures; was: annotations exists (_ matches cls)
dropOtherAnnotations(annotations, cls) ne Nil
def getAnnotation(cls: Symbol): Option[AnnotationInfo] =
//OPT inlined from exists to save on #closures; was: annotations find (_ matches cls)
dropOtherAnnotations(annotations, cls) match {
case ann :: _ => Some(ann)
case _ => None
}
def removeAnnotation(cls: Symbol): Self = filterAnnotations(ann => !(ann matches cls))
final def withAnnotation(annot: AnnotationInfo): Self = withAnnotations(List(annot))
@tailrec private
def dropOtherAnnotations(anns: List[AnnotationInfo], cls: Symbol): List[AnnotationInfo] = anns match {
case ann :: rest => if (ann matches cls) anns else dropOtherAnnotations(rest, cls)
case Nil => Nil
}
}
/** Arguments to classfile annotations (which are written to
* bytecode as java annotations) are either:
*
* - constants
* - arrays of constants
* - or nested classfile annotations
*/
sealed abstract class ClassfileAnnotArg extends Product with JavaArgumentApi
implicit val JavaArgumentTag: ClassTag[ClassfileAnnotArg] = ClassTag[ClassfileAnnotArg](classOf[ClassfileAnnotArg])
case object UnmappableAnnotArg extends ClassfileAnnotArg
/** Represents a compile-time Constant (`Boolean`, `Byte`, `Short`,
* `Char`, `Int`, `Long`, `Float`, `Double`, `String`, `java.lang.Class` or
* an instance of a Java enumeration value).
*/
case class LiteralAnnotArg(const: Constant)
extends ClassfileAnnotArg with LiteralArgumentApi {
def value = const
override def toString = const.escapedStringValue
}
object LiteralAnnotArg extends LiteralArgumentExtractor
/** Represents an array of classfile annotation arguments */
case class ArrayAnnotArg(args: Array[ClassfileAnnotArg])
extends ClassfileAnnotArg with ArrayArgumentApi {
override def toString = args.mkString("[", ", ", "]")
}
object ArrayAnnotArg extends ArrayArgumentExtractor
/** Represents a nested classfile annotation */
case class NestedAnnotArg(annInfo: AnnotationInfo)
extends ClassfileAnnotArg with NestedArgumentApi {
// The nested annotation should not have any Scala annotation arguments
assert(annInfo.args.isEmpty, annInfo.args)
def annotation = annInfo
override def toString = annInfo.toString
}
object NestedAnnotArg extends NestedArgumentExtractor
type JavaArgument = ClassfileAnnotArg
type LiteralArgument = LiteralAnnotArg
val LiteralArgument = LiteralAnnotArg
implicit val LiteralArgumentTag: ClassTag[LiteralAnnotArg] = ClassTag[LiteralAnnotArg](classOf[LiteralAnnotArg])
type ArrayArgument = ArrayAnnotArg
val ArrayArgument = ArrayAnnotArg
implicit val ArrayArgumentTag: ClassTag[ArrayAnnotArg] = ClassTag[ArrayAnnotArg](classOf[ArrayAnnotArg])
type NestedArgument = NestedAnnotArg
val NestedArgument = NestedAnnotArg
implicit val NestedArgumentTag: ClassTag[NestedAnnotArg] = ClassTag[NestedAnnotArg](classOf[NestedAnnotArg])
/** A specific annotation argument that encodes an array of bytes as an
* array of `Long`. The type of the argument declared in the annotation
* must be `String`. This specialised class is used to encode Scala
* signatures for reasons of efficiency, both in term of class-file size
* and in term of compiler performance.
* Details about the storage format of pickles at the bytecode level (classfile annotations) can be found in SIP-10.
*/
case class ScalaSigBytes(bytes: Array[Byte]) extends ClassfileAnnotArg {
override def toString = (bytes map { byte => (byte & 0xff).toHexString }).mkString("[ ", " ", " ]")
lazy val sevenBitsMayBeZero: Array[Byte] = {
mapToNextModSevenBits(scala.reflect.internal.pickling.ByteCodecs.encode8to7(bytes))
}
/* In order to store a byte array (the pickle) using a bytecode-level annotation,
* the most compact representation is used (which happens to be string-constant and not byte array as one would expect).
* However, a String constant in a classfile annotation is limited to a maximum of 65535 characters.
* Method `fitsInOneString` tells us whether the pickle can be held by a single classfile-annotation of string-type.
* Otherwise an array of strings will be used.
*/
def fitsInOneString: Boolean = {
// due to escaping, a zero byte in a classfile-annotation of string-type takes actually two characters.
var i = 0
var numZeros = 0
while (i < sevenBitsMayBeZero.length) {
if (sevenBitsMayBeZero(i) == 0) numZeros += 1
i += 1
}
(sevenBitsMayBeZero.length + numZeros) <= 65535
}
def sigAnnot: Type =
if (fitsInOneString)
definitions.ScalaSignatureAnnotation.tpe
else
definitions.ScalaLongSignatureAnnotation.tpe
private def mapToNextModSevenBits(src: Array[Byte]): Array[Byte] = {
var i = 0
val srclen = src.length
while (i < srclen) {
val in = src(i)
src(i) = (if (in == 0x7f) 0.toByte else (in + 1).toByte)
i += 1
}
src
}
}
object AnnotationInfo {
def marker(atp: Type): AnnotationInfo =
apply(atp, Nil, Nil)
def lazily(lazyInfo: => AnnotationInfo) =
new LazyAnnotationInfo(lazyInfo)
def lazily(lazySymbol: => Symbol, lazyInfo: => AnnotationInfo) =
new ExtraLazyAnnotationInfo(lazySymbol, lazyInfo)
def apply(atp: Type, args: List[Tree], assocs: List[(Name, ClassfileAnnotArg)]): AnnotationInfo =
new CompleteAnnotationInfo(atp, args, assocs)
def unapply(info: AnnotationInfo): Option[(Type, List[Tree], List[(Name, ClassfileAnnotArg)])] =
Some((info.atp, info.args, info.assocs))
def mkFilter(category: Symbol, defaultRetention: Boolean)(ann: AnnotationInfo) =
(ann.metaAnnotations, ann.defaultTargets) match {
case (Nil, Nil) => defaultRetention
case (Nil, defaults) => defaults contains category
case (metas, _) => metas exists (_ matches category)
}
}
class CompleteAnnotationInfo(
val atp: Type,
val args: List[Tree],
val assocs: List[(Name, ClassfileAnnotArg)]
) extends AnnotationInfo {
// Classfile annot: args empty. Scala annot: assocs empty.
assert(args.isEmpty || assocs.isEmpty, atp)
// necessary for reification, see Reifiers.scala for more info
private var orig: Tree = EmptyTree
def original = orig
def setOriginal(t: Tree): this.type = {
orig = t
this setPos t.pos
this
}
override def toString = completeAnnotationToString(this)
}
private[scala] def completeAnnotationToString(annInfo: AnnotationInfo) = {
import annInfo._
val s_args = if (!args.isEmpty) args.mkString("(", ", ", ")") else ""
val s_assocs = if (!assocs.isEmpty) (assocs map { case (x, y) => x+" = "+y } mkString ("(", ", ", ")")) else ""
s"${atp}${s_args}${s_assocs}"
}
/** Symbol annotations parsed in `Namer` (typeCompleter of
* definitions) have to be lazy (#1782)
*/
class LazyAnnotationInfo(lazyInfo: => AnnotationInfo) extends AnnotationInfo {
private var forced = false
private lazy val forcedInfo = try lazyInfo finally forced = true
def atp: Type = forcedInfo.atp
def args: List[Tree] = forcedInfo.args
def assocs: List[(Name, ClassfileAnnotArg)] = forcedInfo.assocs
def original: Tree = forcedInfo.original
def setOriginal(t: Tree): this.type = { forcedInfo.setOriginal(t); this }
// We should always be able to print things without forcing them.
override def toString = if (forced) forcedInfo.toString else "@>"
override def pos: Position = if (forced) forcedInfo.pos else NoPosition
override def completeInfo(): Unit = forcedInfo
}
final class ExtraLazyAnnotationInfo(sym: => Symbol, lazyInfo: => AnnotationInfo) extends LazyAnnotationInfo(lazyInfo) {
private[this] lazy val typeSymbol = sym
override def symbol: Symbol = typeSymbol
}
/** Typed information about an annotation. It can be attached to either
* a symbol or an annotated type.
*
* Annotations are written to the classfile as Java annotations
* if `atp` conforms to `ClassfileAnnotation` (the classfile parser adds
* this interface to any Java annotation class).
*
* Annotations are pickled (written to scala symtab attribute in the
* classfile) if `atp` inherits form `StaticAnnotation`.
*
* `args` stores arguments to Scala annotations, represented as typed
* trees. Note that these trees are not transformed by any phases
* following the type-checker.
*
* `assocs` stores arguments to classfile annotations as name-value pairs.
*/
abstract class AnnotationInfo extends AnnotationApi {
def atp: Type
def args: List[Tree]
def assocs: List[(Name, ClassfileAnnotArg)]
def tpe = atp
def scalaArgs = args
def javaArgs = ListMap(assocs: _*)
// necessary for reification, see Reifiers.scala for more info
def original: Tree
def setOriginal(t: Tree): this.type
// see annotationArgRewriter
lazy val isTrivial = atp.isTrivial && !hasArgWhich(_.isInstanceOf[This])
private var rawpos: Position = NoPosition
def pos = rawpos
def setPos(pos: Position): this.type = { // Syncnote: Setpos inaccessible to reflection, so no sync in rawpos necessary.
rawpos = pos
this
}
// Forces LazyAnnotationInfo, no op otherwise
def completeInfo(): Unit = ()
/** Annotations annotating annotations are confusing so I drew
* an example. Given the following code:
*
* class A {
* @(deprecated @setter) @(inline @getter)
* var x: Int = 0
* }
*
* For the setter `x_=` in A, annotations contains one AnnotationInfo =
* List(deprecated @setter)
* The single AnnotationInfo in that list, i.e. `@(deprecated @setter)`, has metaAnnotations =
* List(setter)
*
* Similarly, the getter `x` in A has an @inline annotation, which has
* metaAnnotations = List(getter).
*/
def symbol = atp.typeSymbol
/** These are meta-annotations attached at the use site; they
* only apply to this annotation usage. For instance, in
* `@(deprecated @setter @field) val ...`
* metaAnnotations = List(setter, field).
*/
def metaAnnotations: List[AnnotationInfo] = atp match {
case AnnotatedType(metas, _) => metas
case _ => Nil
}
/** The default kind of members to which this annotation is attached.
* For instance, for scala.deprecated defaultTargets =
* List(getter, setter, beanGetter, beanSetter).
*
* NOTE: have to call symbol.initialize, since we won't get any annotations if the symbol hasn't yet been completed
*/
def defaultTargets = symbol.initialize.annotations map (_.symbol) filter isMetaAnnotation
// Test whether the typeSymbol of atp conforms to the given class.
def matches(clazz: Symbol) = !symbol.isInstanceOf[StubSymbol] && (symbol isNonBottomSubClass clazz)
// All subtrees of all args are considered.
def hasArgWhich(p: Tree => Boolean) = args exists (_ exists p)
/** Check whether the type or any of the arguments are erroneous */
def isErroneous = atp.isErroneous || args.exists(_.isErroneous)
def isStatic = symbol.isNonBottomSubClass(StaticAnnotationClass) && symbol != NowarnClass
/** Check whether any of the arguments mention a symbol */
def refsSymbol(sym: Symbol) = hasArgWhich(_.symbol == sym)
def stringArg(index: Int) = constantAtIndex(index) map (_.stringValue)
def intArg(index: Int) = constantAtIndex(index) map (_.intValue)
def booleanArg(index: Int) = constantAtIndex(index) map (_.booleanValue)
def symbolArg(index: Int) = argAtIndex(index) collect {
case Apply(fun, Literal(str) :: Nil) if fun.symbol == definitions.Symbol_apply =>
newTermName(str.stringValue)
}
// !!! when annotation arguments are not literals, but any sort of
// expression, there is a fair chance they will turn up here not as
// Literal(const) but some arbitrary AST.
//
// We recurse over Typed / Annotated trees to allow things like:
// `@implicitNotFound("$foo": @nowarn)`
def constantAtIndex(index: Int): Option[Constant] = {
@tailrec
def lit(tree: Tree): Option[Constant] = tree match {
case Literal(c) => Some(c)
case Typed(t, _) => lit(t)
case Annotated(_, t) => lit(t)
case _ => None
}
argAtIndex(index).flatMap(lit)
}
def argAtIndex(index: Int): Option[Tree] =
if (index < args.size) Some(args(index)) else None
def transformArgs(f: List[Tree] => List[Tree]): AnnotationInfo =
new CompleteAnnotationInfo(atp, f(args), assocs)
override def hashCode = atp.## + args.## + assocs.##
override def equals(other: Any) = other match {
case x: AnnotationInfo => (atp == x.atp) && (args == x.args) && (assocs == x.assocs)
case _ => false
}
}
type Annotation = AnnotationInfo
object Annotation extends AnnotationExtractor {
def apply(tpe: Type, scalaArgs: List[Tree], javaArgs: ListMap[Name, ClassfileAnnotArg]): Annotation =
AnnotationInfo(tpe, scalaArgs, javaArgs.toList)
def unapply(annotation: Annotation): Option[(Type, List[Tree], ListMap[Name, ClassfileAnnotArg])] =
Some((annotation.tpe, annotation.scalaArgs, annotation.javaArgs))
}
implicit val AnnotationTag: ClassTag[AnnotationInfo] = ClassTag[AnnotationInfo](classOf[AnnotationInfo])
protected[scala] def annotationToTree(ann: Annotation): Tree = {
def reverseEngineerArgs(): List[Tree] = {
def reverseEngineerArg(jarg: ClassfileAnnotArg): Tree = jarg match {
case LiteralAnnotArg(const) =>
val tpe = if (const.tag == UnitTag) UnitTpe else ConstantType(const)
Literal(const) setType tpe
case ArrayAnnotArg(jargs) =>
val args = jargs map reverseEngineerArg
// TODO: I think it would be a good idea to typecheck Java annotations using a more traditional algorithm
// sure, we can't typecheck them as is using the `new jann(foo = bar)` syntax (because jann is going to be an @interface)
// however we can do better than `typedAnnotation` by desugaring the aforementioned expression to
// something like `new jann() { override def annotatedType() = ...; override def foo = bar }`
// and then using the results of that typecheck to produce a Java-compatible classfile entry
// in that case we're going to have correctly typed Array.apply calls, however that's 2.12 territory
// and for 2.11 exposing an untyped call to ArrayModule should suffice
Apply(Ident(ArrayModule), args.toList)
case NestedAnnotArg(ann: Annotation) =>
annotationToTree(ann)
case _ =>
EmptyTree
}
def reverseEngineerArgs(jargs: List[(Name, ClassfileAnnotArg)]): List[Tree] = jargs match {
case (name, jarg) :: rest => AssignOrNamedArg(Ident(name), reverseEngineerArg(jarg)) :: reverseEngineerArgs(rest)
case Nil => Nil
}
if (ann.javaArgs.isEmpty) ann.scalaArgs
else reverseEngineerArgs(ann.javaArgs.toList)
}
// TODO: at the moment, constructor selection is unattributed, because AnnotationInfos lack necessary information
// later on, in 2.12, for every annotation we could save an entire tree instead of just bits and pieces
// but for 2.11 the current situation will have to do
val ctorSelection = Select(New(TypeTree(ann.atp)), nme.CONSTRUCTOR)
Apply(ctorSelection, reverseEngineerArgs()) setType ann.atp
}
protected[scala] def treeToAnnotation(tree: Tree): Annotation = tree match {
case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) =>
def encodeJavaArg(arg: Tree): ClassfileAnnotArg = arg match {
case Literal(const) => LiteralAnnotArg(const)
case Apply(ArrayModule, args) => ArrayAnnotArg(args map encodeJavaArg toArray)
case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => NestedAnnotArg(treeToAnnotation(arg))
case _ => throw new Exception(s"unexpected java argument shape $arg: literals, arrays and nested annotations are supported")
}
def encodeJavaArgs(args: List[Tree]): List[(Name, ClassfileAnnotArg)] = args match {
case AssignOrNamedArg(Ident(name), arg) :: rest => (name, encodeJavaArg(arg)) :: encodeJavaArgs(rest)
case arg :: rest => throw new Exception(s"unexpected java argument shape $arg: only AssignOrNamedArg trees are supported")
case Nil => Nil
}
val atp = tpt.tpe
if (atp != null && (atp.typeSymbol isNonBottomSubClass StaticAnnotationClass)) AnnotationInfo(atp, args, Nil)
else if (atp != null && (atp.typeSymbol isNonBottomSubClass ClassfileAnnotationClass)) AnnotationInfo(atp, Nil, encodeJavaArgs(args))
else throw new Exception(s"unexpected annotation type $atp: only subclasses of StaticAnnotation and ClassfileAnnotation are supported")
case _ =>
throw new Exception("""unexpected tree shape: only q"new $annType(..$args)" is supported""")
}
object UnmappableAnnotation extends CompleteAnnotationInfo(NoType, Nil, Nil)
class ErroneousAnnotation() extends CompleteAnnotationInfo(ErrorType, Nil, Nil)
/** Extracts the type of the thrown exception from an AnnotationInfo.
*
* Supports both “old-style” `@throws(classOf[Exception])`
* as well as “new-style” `@throws[Exception]("cause")` annotations.
*/
object ThrownException {
def unapply(ann: AnnotationInfo): Option[Type] = {
ann match {
case AnnotationInfo(tpe, _, _) if tpe.typeSymbol != ThrowsClass =>
None
// old-style: @throws(classOf[Exception]) (which is throws[T](classOf[Exception]))
case AnnotationInfo(_, List(Literal(Constant(tpe: Type))), _) =>
Some(tpe)
// new-style: @throws[Exception], @throws[Exception]("cause")
case AnnotationInfo(TypeRef(_, _, arg :: _), _, _) =>
Some(arg)
case AnnotationInfo(TypeRef(_, _, Nil), _, _) =>
Some(ThrowableTpe)
}
}
}
}