scala.reflect.macros.Attachments.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 macros
import reflect.internal.util.Position
import scala.runtime.ClassValueCompat
/**
* EXPERIMENTAL
*
* Attachments provide a way to associate custom metadata with symbols and trees.
*
* Along with `symbol` and `tpe`, which represent core metadata of trees, each tree
* carries the `attachments` field that can store other metadata: compiler-defined (e.g. positions) or user-defined.
* Same story is true for symbols, which also have extensible metadata by the virtue
* of the same `attachments` field.
*
* Typically attachments just store a [[scala.reflect.api.Position]], but they can be extended to
* encompass arbitrary payloads. Payloads are stored in type-indexed slots, which can be read with `get[T]` and written
* with `update[T]` and `remove[T]`.
*
* This API doesn't have much use in the runtime reflection API (the [[scala.reflect.api]] package), but it might be of help
* for macro writers, providing a way to coordinate multiple macros operating on the same code. Therefore the `attachments`
* field is only declared in trees and symbols belonging to [[scala.reflect.macros.Universe]].
*/
abstract class Attachments { self =>
/** The position type of this attachment */
type Pos >: Null // <: api.Position
/** The underlying position */
def pos: Pos
/** Creates a copy of this attachment with the position replaced by `newPos` */
def withPos(newPos: Pos): Attachments { type Pos = self.Pos }
/** The underlying payload with the guarantee that no two elements have the same type. */
def all: Set[Any]
private def matchesTag[T: ClassTag]: (Any => Boolean) = {
// OPT: avoid lambda allocation for each call to `remove`, etc.
Attachments.matchesTagCache.get(classTag[T].runtimeClass)
}
/** An underlying payload of the given class type `T`. */
def get[T: ClassTag]: Option[T] = {
val it = all.iterator
val matchesTagFn = matchesTag[T]
while (it.hasNext) { // OPT: hotspot, hand roll `Set.find`.
val datum = it.next()
if (matchesTagFn(datum)) return Some(datum.asInstanceOf[T])
}
None
}
/** Check underlying payload contains an instance of type `T`. */
def contains[T: ClassTag]: Boolean =
!isEmpty && (all exists matchesTag[T])
/** Creates a copy of this attachment with the payload slot of T added/updated with the provided value.
* Replaces an existing payload of the same type, if exists.
*/
def update[T: ClassTag](attachment: T): Attachments { type Pos = self.Pos } =
new NonemptyAttachments[Pos](this.pos, remove[T].all + attachment)
/** Creates a copy of this attachment with the payload of the given class type `T` removed. */
def remove[T: ClassTag]: Attachments { type Pos = self.Pos } = {
if (!all.exists(matchesTag[T])) this // OPT immutable.Set.filter doesn't structurally share on 2.12 collections.
else {
val newAll = all filterNot matchesTag[T]
if (newAll.isEmpty) pos.asInstanceOf[Attachments { type Pos = self.Pos }]
else if (newAll.size == 1) new SingleAttachment[Pos](pos, newAll.head)
else new NonemptyAttachments[Pos](this.pos, newAll)
}
}
/** Creates a copy of this attachment with the given element removed. */
final def removeElement[T](attachment: T): Attachments { type Pos = self.Pos } = {
val newAll = all - attachment
if (newAll eq all) this
else if (newAll.isEmpty) pos.asInstanceOf[Attachments { type Pos = self.Pos }]
else new NonemptyAttachments[Pos](this.pos, newAll)
}
/** Creates a copy of this attachment with the given element added. */
final def addElement[T](attachment: T): Attachments { type Pos = self.Pos } = {
val newAll = all + attachment
if (newAll eq all) this // i.e., this was the same attachment as before
else new NonemptyAttachments[Pos](this.pos, newAll)
}
/** Tests if the given element is attached. */
final def containsElement(element: Any): Boolean = {
all.contains(element)
}
def isEmpty: Boolean
def cloneAttachments: Attachments { type Pos = self.Pos } = this
}
private object Attachments {
private val matchesTagCache = new ClassValueCompat[Function1[Any, Boolean]] {
override def computeValue(cls: Class[_]): Function[Any, Boolean] = cls.isInstance(_)
}
}
private[reflect] abstract class EmptyAttachments extends Attachments { self: Position =>
final override def all: Set[Any] = Set.empty
final override def get[T: ClassTag]: Option[T] = None
final override def contains[T: ClassTag]: Boolean = false
final override def update[T: ClassTag](newAtt: T): Attachments { type Pos = self.Pos } =
new SingleAttachment[Pos](pos, newAtt)
final override def remove[T: ClassTag]: Attachments { type Pos = self.Pos } = this
final override def isEmpty: Boolean = true
}
private final class SingleAttachment[P >: Null](override val pos: P, val att: Any) extends Attachments {
type Pos = P
def withPos(newPos: Pos) = new SingleAttachment[Pos](newPos, att)
override def isEmpty: Boolean = false
override def cloneAttachments: Attachments { type Pos = P } = new SingleAttachment[P](pos, att)
override def all = Set.empty[Any] + att
override def contains[T](implicit tt: ClassTag[T]) = tt.runtimeClass.isInstance(att)
override def get[T](implicit tt: ClassTag[T]) = if (contains(tt)) Some(att.asInstanceOf[T]) else None
override def update[T](newAtt: T)(implicit tt: ClassTag[T]) =
if (contains(tt)) new SingleAttachment[P](pos, newAtt)
else new NonemptyAttachments[P](pos, Set.empty[Any] + att + newAtt)
override def remove[T](implicit tt: ClassTag[T]) =
if (contains(tt)) pos.asInstanceOf[Attachments { type Pos = P }] else this
}
// scala/bug#7018: This used to be an inner class of `Attachments`, but that led to a memory leak in the
// IDE via $outer pointers.
private final class NonemptyAttachments[P >: Null](override val pos: P, override val all: Set[Any]) extends Attachments {
type Pos = P
def withPos(newPos: Pos) = new NonemptyAttachments(newPos, all)
override def isEmpty: Boolean = false
override def cloneAttachments: Attachments { type Pos = P } = new NonemptyAttachments[P](pos, all)
}