scala.reflect.internal.Scopes.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.{AbstractIterable, AbstractIterator}
import scala.collection.mutable.Clearable
import scala.reflect.internal.util.Statistics
trait Scopes extends api.Scopes { self: SymbolTable =>
// Reset `scopeCount` per every run
private[scala] var scopeCount = 0
perRunCaches.recordCache {
val clearCount: Clearable = () => {scopeCount = 0}
clearCount
}
/** An ADT to represent the results of symbol name lookups.
*/
sealed trait NameLookup { def symbol: Symbol ; def isSuccess = false }
case class LookupSucceeded(qualifier: Tree, symbol: Symbol) extends NameLookup { override def isSuccess = true }
case class LookupAmbiguous(msg: String) extends NameLookup { def symbol = NoSymbol }
case class LookupInaccessible(symbol: Symbol, msg: String) extends NameLookup
case object LookupNotFound extends NameLookup { def symbol = NoSymbol }
class ScopeEntry(var sym: Symbol, val owner: Scope) {
/** the next entry in the hash bucket
*/
var tail: ScopeEntry = null
/** the next entry in this scope
*/
var next: ScopeEntry = null
def depth = owner.nestingLevel
override def hashCode(): Int = sym.name.start
override def toString() = s"$sym (depth=$depth)"
// OPT: compare raw names when pre-flatten, saving needsFlatClasses within the loop
final def name(flat: Boolean): Name = if (flat) sym.name else sym.rawname
}
private def newScopeEntry(sym: Symbol, owner: Scope): ScopeEntry = {
val e = new ScopeEntry(sym, owner)
e.next = owner.elems
owner.elems = e
e
}
object Scope {
def unapplySeq(decls: Scope): Some[Seq[Symbol]] = Some(decls.toList)
}
/** A default Scope iterator, that retrieves elements in the order given by ScopeEntry. */
private[Scopes] class ScopeIterator(owner: Scope) extends AbstractIterator[Symbol] {
private[this] var elem: ScopeEntry = owner.elems
def hasNext: Boolean = (elem ne null) && (elem.owner == this.owner)
def next(): Symbol =
if (hasNext) {
val res = elem
elem = elem.next
res.sym
} else throw new NoSuchElementException
}
/** Note: constructor is protected to force everyone to use the factory methods newScope or newNestedScope instead.
* This is necessary because when run from reflection every scope needs to have a
* SynchronizedScope as mixin.
*/
class Scope protected[Scopes]() extends AbstractIterable[Symbol] with ScopeApi with MemberScopeApi {
scopeCount += 1
private[scala] var elems: ScopeEntry = _
/** The number of times this scope is nested in another
*/
private[Scopes] var nestinglevel = 0
/** the hash table
*/
private[Scopes] var hashtable: Array[ScopeEntry] = null
/** a cache for all elements, to be used by symbol iterator.
*/
private[this] var elemsCache: List[Symbol] = null
private[this] var cachedSize = -1
private def flushElemsCache(): Unit = {
elemsCache = null
cachedSize = -1
}
/** size and mask of hash tables
* todo: make hashtables grow?
*/
private final val HASHSIZE = 0x80
private final val HASHMASK = 0x7f
/** the threshold number of entries from which a hashtable is constructed.
*/
private final val MIN_HASH = 8
/** Returns a new scope with the same content as this one. */
def cloneScope: Scope = newScopeWith(this.toList: _*)
/** is the scope empty? */
override def isEmpty: Boolean = elems eq null
/** the number of entries in this scope */
override def size: Int = {
if (cachedSize < 0)
cachedSize = directSize
cachedSize
}
private def directSize: Int = {
var s = 0
var e = elems
while (e ne null) {
s += 1
e = e.next
}
s
}
/** enter a scope entry
*/
protected def enterEntry(e: ScopeEntry): Unit = {
flushElemsCache()
if (hashtable ne null)
enterInHash(e)
else if (size >= MIN_HASH)
createHash()
}
private def enterInHash(e: ScopeEntry): Unit = {
val i = e.sym.name.start & HASHMASK
e.tail = hashtable(i)
hashtable(i) = e
}
/** enter a symbol
*/
def enter[T <: Symbol](sym: T): sym.type = {
enterEntry(newScopeEntry(sym, this))
sym
}
final def enterBefore(sym: Symbol, next: ScopeEntry): sym.type = {
assert(this != EmptyScope, sym)
require(sym.name.hashCode() == next.sym.name.hashCode(), (sym, next.sym))
require(sym != next.sym, (sym, next.sym))
val newNext = new ScopeEntry(next.sym, this)
val hasHashTable = hashtable ne null
newNext.next = next.next
if (hasHashTable) newNext.tail = next.tail
next.sym = sym
next.next = newNext
if (hasHashTable) next.tail = newNext
flushElemsCache()
sym
}
/** enter a symbol, asserting that no symbol with same name exists in scope
*/
def enterUnique(sym: Symbol): Unit = {
assert(lookup(sym.name) == NoSymbol, (sym.fullLocationString, lookup(sym.name).fullLocationString))
enter(sym)
}
def enterIfNew[T <: Symbol](sym: T): T = {
val existing = lookupEntry(sym.name)
if (existing == null) enter(sym)
else existing.sym.asInstanceOf[T]
}
private def createHash(): Unit = {
hashtable = new Array[ScopeEntry](HASHSIZE)
enterAllInHash(elems)
}
private def enterAllInHash(e: ScopeEntry, n: Int = 0): Unit = {
if (e ne null) {
if (n < maxRecursions) {
enterAllInHash(e.next, n + 1)
enterInHash(e)
} else {
var entries: List[ScopeEntry] = List()
var ee = e
while (ee ne null) {
entries = ee :: entries
ee = ee.next
}
entries foreach enterInHash
}
}
}
def rehash(sym: Symbol, newname: Name): Unit = {
if (hashtable ne null) {
val index = sym.name.start & HASHMASK
var e1 = hashtable(index)
var e: ScopeEntry = null
if (e1 != null) {
if (e1.sym == sym) {
hashtable(index) = e1.tail
e = e1
} else {
while (e1.tail != null && e1.tail.sym != sym) e1 = e1.tail
if (e1.tail != null) {
e = e1.tail
e1.tail = e.tail
}
}
}
if (e != null) {
val newindex = newname.start & HASHMASK
e.tail = hashtable(newindex)
hashtable(newindex) = e
}
}
}
/** remove entry
*/
def unlink(e: ScopeEntry): Unit = {
if (elems == e) {
elems = e.next
} else {
var e1 = elems
while (e1.next != e) e1 = e1.next
e1.next = e.next
}
if (hashtable ne null) {
val index = e.sym.name.start & HASHMASK
var e1 = hashtable(index)
if (e1 == e) {
hashtable(index) = e.tail
} else {
while (e1.tail != e) e1 = e1.tail
e1.tail = e.tail
}
}
flushElemsCache()
}
/** remove symbol */
def unlink(sym: Symbol): Unit = {
var e = lookupEntry(sym.name)
while (e ne null) {
if (e.sym == sym) unlink(e)
e = lookupNextEntry(e)
}
}
/** Lookup a module or a class, filtering out matching names in scope
* which do not match that requirement.
*/
def lookupModule(name: Name): Symbol = findSymbol(lookupAll(name.toTermName))(_.isModule)
def lookupClass(name: Name): Symbol = findSymbol(lookupAll(name.toTypeName))(_.isClass)
/** True if the name exists in this scope, false otherwise. */
def containsName(name: Name) = lookupEntry(name) != null
/** Lookup a symbol.
*/
def lookup(name: Name): Symbol = {
val e = lookupEntry(name)
if (e eq null) NoSymbol
else if (lookupNextEntry(e) eq null) e.sym
else {
// We shouldn't get here: until now this method was picking a random
// symbol when there was more than one with the name, so this should
// only be called knowing that there are 0-1 symbols of interest. So, we
// can safely return an overloaded symbol rather than throwing away the
// rest of them. Most likely we still break, but at least we will break
// in an understandable fashion (unexpectedly overloaded symbol) rather
// than a non-deterministic bizarre one (see any bug involving overloads
// in package objects.)
val alts = lookupAll(name).toList
def alts_s = alts map (s => s.defString) mkString " "
devWarning(s"scope lookup of $name found multiple symbols: $alts_s")
// FIXME - how is one supposed to create an overloaded symbol without
// knowing the correct owner? Using the symbol owner is not correct;
// say for instance this is List's scope and the symbols are its three
// mkString members. Those symbols are owned by TraversableLike, which
// is no more meaningful an owner than NoSymbol given that we're in
// List. Maybe it makes no difference who owns the overloaded symbol, in
// which case let's establish that and have a canonical creation method.
//
// FIXME - a similar question for prefix, although there are more
// clues from the symbols on that one, as implemented here. In general
// the distinct list is one type and lub becomes the identity.
// val prefix = lub(alts map (_.info.prefix) distinct)
// Now using NoSymbol and NoPrefix always to avoid forcing info (scala/bug#6664)
NoSymbol.newOverloaded(NoPrefix, alts)
}
}
/** Returns an iterator yielding every symbol with given name in this scope.
*/
def lookupAll(name: Name): Iterator[Symbol] = new AbstractIterator[Symbol] {
private[this] var e = lookupEntry(name)
def hasNext: Boolean = e ne null
def next(): Symbol = try e.sym finally e = lookupNextEntry(e)
}
def lookupAllEntries(name: Name): Iterator[ScopeEntry] = new AbstractIterator[ScopeEntry] {
private[this] var e = lookupEntry(name)
def hasNext: Boolean = e ne null
def next(): ScopeEntry = try e finally e = lookupNextEntry(e)
}
def lookupUnshadowedEntries(name: Name): Iterator[ScopeEntry] = {
lookupEntry(name) match {
case null => Iterator.empty
case e => lookupAllEntries(name) filter (e1 => (e eq e1) || (e.depth == e1.depth && e.sym != e1.sym))
}
}
final def lookupSymbolEntry(sym: Symbol): ScopeEntry = {
var e = lookupEntry(sym.name)
while (e ne null) {
if (e.sym == sym) return e
e = lookupNextEntry(e)
}
null
}
/** lookup a symbol entry matching given name.
* @note from Martin: I believe this is a hotspot or will be one
* in future versions of the type system. I have reverted the previous
* change to use iterators as too costly.
*/
def lookupEntry(name: Name): ScopeEntry = {
var e: ScopeEntry = null
val flat = phase.flatClasses
if (hashtable ne null) {
e = hashtable(name.start & HASHMASK)
while ((e ne null) && (e.name(flat) ne name)) {
e = e.tail
}
} else {
e = elems
while ((e ne null) && (e.name(flat) ne name)) {
e = e.next
}
}
e
}
/** lookup next entry with same name as this one
* @note from Martin: I believe this is a hotspot or will be one
* in future versions of the type system. I have reverted the previous
* change to use iterators as too costly.
*/
def lookupNextEntry(entry: ScopeEntry): ScopeEntry = {
var e = entry
val flat = phase.flatClasses
val entryName = entry.name(flat)
if (hashtable ne null)
do { e = e.tail } while ((e ne null) && e.name(flat) != entryName)
else
do { e = e.next } while ((e ne null) && e.name(flat) != entryName)
e
}
final def lookupNameInSameScopeAs(original: Symbol, companionName: Name): Symbol = {
lookupSymbolEntry(original) match {
case null =>
case entry =>
var e = lookupEntry(companionName)
while (e != null) {
if (e.owner eq entry.owner) return e.sym
e = lookupNextEntry(e)
}
}
NoSymbol
}
/** TODO - we can test this more efficiently than checking isSubScope
* in both directions. However the size test might be enough to quickly
* rule out most failures.
*/
def isSameScope(other: Scope) = (
(size == other.size) // optimization - size is cached
&& (this isSubScope other)
&& (other isSubScope this)
)
def isSubScope(other: Scope) = {
def scopeContainsSym(sym: Symbol): Boolean = {
@tailrec def entryContainsSym(e: ScopeEntry): Boolean = e match {
case null => false
case _ =>
val comparableInfo = sym.info.substThis(sym.owner, e.sym.owner)
(e.sym.info =:= comparableInfo) || entryContainsSym(lookupNextEntry(e))
}
entryContainsSym(this lookupEntry sym.name)
}
other.reverseIterator.forall(scopeContainsSym)
}
/** Return all symbols as a list in the order they were entered in this scope.
*/
override def toList: List[Symbol] = {
if (elemsCache eq null) {
var symbols: List[Symbol] = Nil
var count = 0
var e = elems
while ((e ne null) && e.owner == this) {
count += 1
symbols ::= e.sym
e = e.next
}
elemsCache = symbols
cachedSize = count
}
elemsCache
}
/** Vanilla scope - symbols are stored in declaration order.
*/
def sorted: List[Symbol] = toList
/** Return the nesting level of this scope, i.e. the number of times this scope
* was nested in another */
def nestingLevel = nestinglevel
/** Return all symbols as an iterator in the order they were entered in this scope.
*/
def iterator: Iterator[Symbol] = toList.iterator
/** Returns all symbols as an iterator, in an order reversed to that in which they
* were entered: symbols first in the scopes are last out of the iterator.
* NOTE: when using the `reverseIterator`, it is not safe to mutate the Scope.
* So, be careful not to use this when you do need to mutate this Scope. */
def reverseIterator: Iterator[Symbol] = new ScopeIterator(this)
override def foreach[U](p: Symbol => U): Unit = toList foreach p
override def filterNot(p: Symbol => Boolean): Scope = {
val result = toList
val filtered = result.filterNot(p)
if (result eq filtered) this
else newScopeWith(filtered: _*)
}
override def filter(p: Symbol => Boolean): Scope = {
val result = toList
val filtered = result.filter(p)
if (sameLength(result, filtered)) this else newScopeWith(filtered: _*)
}
@deprecated("use `toList.reverse` instead", "2.10.0") // Used in sbt 0.12.4
def reverse: List[Symbol] = toList.reverse
override def addString(sb: StringBuilder, start: String, sep: String, end: String): sb.type =
toList.map(_.defString).addString(sb, start, sep, end)
override def toString(): String = mkString("Scope{\n ", ";\n ", "\n}")
}
implicit val ScopeTag: ClassTag[Scope] = ClassTag[Scope](classOf[Scope])
type MemberScope = Scope
implicit val MemberScopeTag: ClassTag[MemberScope] = ClassTag[MemberScope](classOf[MemberScope])
/** Create a new scope */
def newScope: Scope = new Scope()
/** Create a new scope to be used in `findMembers`.
*
* But why do we need a special scope for `findMembers`?
* Let me tell you a story.
*
* `findMembers` creates a synthetic scope and then iterates over
* base classes in linearization order, and for every scrutinized class
* iterates over `decls`, the collection of symbols declared in that class.
* Declarations that fit the filter get appended to the created scope.
*
* The problem is that `decls` returns a Scope, and to iterate a scope performantly
* one needs to go from its end to its beginning.
*
* Hence the `findMembers` scope is populated in a wicked order:
* symbols that belong to the same declaring class come in reverse order of their declaration,
* however, the scope itself is ordered w.r.t the linearization of the target type.
*
* Once `members` became a public API, this has been confusing countless numbers of users.
* Therefore we introduce a special flavor of scopes to accommodate this quirk of `findMembers`
*/
private[scala] def newFindMemberScope: Scope = new Scope() {
override def sorted = {
val members = toList
val owners = members.map(_.owner).distinct
val grouped = members groupBy (_.owner)
owners.flatMap(owner => grouped(owner).reverse)
}
}
/** Create a new scope nested in another one with which it shares its elements */
final def newNestedScope(outer: Scope): Scope = {
val startTime = if (settings.areStatisticsEnabled) statistics.startTimer(statistics.scopePopulationTime) else null
val nested = newScope // not `new Scope`, we must allow the runtime reflection universe to mixin SynchronizedScopes!
nested.elems = outer.elems
nested.nestinglevel = outer.nestinglevel + 1
if (outer.hashtable ne null)
nested.hashtable = java.util.Arrays.copyOf(outer.hashtable, outer.hashtable.length)
if (settings.areStatisticsEnabled) statistics.stopTimer(statistics.scopePopulationTime, startTime)
nested
}
/** Create a new scope with given initial elements */
def newScopeWith(elems: Symbol*): Scope = {
val startTime = if (settings.areStatisticsEnabled) statistics.startTimer(statistics.scopePopulationTime) else null
val scope = newScope
elems.foreach(scope.enter(_))
if (settings.areStatisticsEnabled) statistics.stopTimer(statistics.scopePopulationTime, startTime)
scope
}
/** Create new scope for the members of package `pkg` */
def newPackageScope(pkgClass: Symbol): Scope = newScope
/** Transform scope of members of `owner` using operation `op`
* This is overridden by the reflective compiler to avoid creating new scopes for packages
*/
def scopeTransform(owner: Symbol)(op: => Scope): Scope = op
/** The empty scope (immutable).
*/
object EmptyScope extends Scope {
override def enterEntry(e: ScopeEntry): Unit = {
abort("EmptyScope.enter")
}
}
/** The error scope.
*/
class ErrorScope(owner: Symbol) extends Scope
private final val maxRecursions = 1000
}
trait ScopeStats {
self: Statistics =>
val scopeCountView = newView("#created scopes")(symbolTable.scopeCount)
val scopePopulationTime = newTimer("time spent in scope population")
}