diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc/core/Denotations.scala')
-rw-r--r-- | compiler/src/dotty/tools/dotc/core/Denotations.scala | 1217 |
1 files changed, 1217 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala new file mode 100644 index 000000000..6a39c5787 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -0,0 +1,1217 @@ +package dotty.tools +package dotc +package core + +import SymDenotations.{ SymDenotation, ClassDenotation, NoDenotation } +import Contexts.{Context, ContextBase} +import Names.{Name, PreName} +import Names.TypeName +import StdNames._ +import Symbols.NoSymbol +import Symbols._ +import Types._ +import Periods._ +import Flags._ +import DenotTransformers._ +import Decorators._ +import dotc.transform.Erasure +import printing.Texts._ +import printing.Printer +import io.AbstractFile +import config.Config +import util.common._ +import collection.mutable.ListBuffer +import Decorators.SymbolIteratorDecorator + +/** Denotations represent the meaning of symbols and named types. + * The following diagram shows how the principal types of denotations + * and their denoting entities relate to each other. Lines ending in + * a down-arrow `v` are member methods. The two methods shown in the diagram are + * "symbol" and "deref". Both methods are parameterized by the current context, + * and are effectively indexed by current period. + * + * Lines ending in a horizontal line mean subtying (right is a subtype of left). + * + * NamedType------TermRefWithSignature + * | | Symbol---------ClassSymbol + * | | | | + * | denot | denot | denot | denot + * v v v v + * Denotation-+-----SingleDenotation-+------SymDenotation-+----ClassDenotation + * | | + * +-----MultiDenotation | + * | + * +--UniqueRefDenotation + * +--JointRefDenotation + * + * Here's a short summary of the classes in this diagram. + * + * NamedType A type consisting of a prefix type and a name, with fields + * prefix: Type + * name: Name + * It has two subtypes: TermRef and TypeRef + * TermRefWithSignature A TermRef that has in addition a signature to select an overloaded variant, with new field + * sig: Signature + * Symbol A label for a definition or declaration in one compiler run + * ClassSymbol A symbol representing a class + * Denotation The meaning of a named type or symbol during a period + * MultiDenotation A denotation representing several overloaded members + * SingleDenotation A denotation representing a non-overloaded member or definition, with main fields + * symbol: Symbol + * info: Type + * UniqueRefDenotation A denotation referring to a single definition with some member type + * JointRefDenotation A denotation referring to a member that could resolve to several definitions + * SymDenotation A denotation representing a single definition with its original type, with main fields + * name: Name + * owner: Symbol + * flags: Flags + * privateWithin: Symbol + * annotations: List[Annotation] + * ClassDenotation A denotation representing a single class definition. + */ +object Denotations { + + implicit def eqDenotation: Eq[Denotation, Denotation] = Eq + + /** A denotation is the result of resolving + * a name (either simple identifier or select) during a given period. + * + * Denotations can be combined with `&` and `|`. + * & is conjunction, | is disjunction. + * + * `&` will create an overloaded denotation from two + * non-overloaded denotations if their signatures differ. + * Analogously `|` of two denotations with different signatures will give + * an empty denotation `NoDenotation`. + * + * A denotation might refer to `NoSymbol`. This is the case if the denotation + * was produced from a disjunction of two denotations with different symbols + * and there was no common symbol in a superclass that could substitute for + * both symbols. Here is an example: + * + * Say, we have: + * + * class A { def f: A } + * class B { def f: B } + * val x: A | B = if (test) new A else new B + * val y = x.f + * + * Then the denotation of `y` is `SingleDenotation(NoSymbol, A | B)`. + * + * @param symbol The referencing symbol, or NoSymbol is none exists + */ + abstract class Denotation(val symbol: Symbol) extends util.DotClass with printing.Showable { + + /** The type info of the denotation, exists only for non-overloaded denotations */ + def info(implicit ctx: Context): Type + + /** The type info, or, if this is a SymDenotation where the symbol + * is not yet completed, the completer + */ + def infoOrCompleter: Type + + /** The period during which this denotation is valid. */ + def validFor: Period + + /** Is this a reference to a type symbol? */ + def isType: Boolean + + /** Is this a reference to a term symbol? */ + def isTerm: Boolean = !isType + + /** Is this denotation overloaded? */ + final def isOverloaded = isInstanceOf[MultiDenotation] + + /** The signature of the denotation. */ + def signature(implicit ctx: Context): Signature + + /** Resolve overloaded denotation to pick the ones with the given signature + * when seen from prefix `site`. + * @param relaxed When true, consider only parameter signatures for a match. + */ + def atSignature(sig: Signature, site: Type = NoPrefix, relaxed: Boolean = false)(implicit ctx: Context): Denotation + + /** The variant of this denotation that's current in the given context. + * If no such denotation exists, returns the denotation with each alternative + * at its first point of definition. + */ + def current(implicit ctx: Context): Denotation + + /** Is this denotation different from NoDenotation or an ErrorDenotation? */ + def exists: Boolean = true + + /** A denotation with the info of this denotation transformed using `f` */ + def mapInfo(f: Type => Type)(implicit ctx: Context): Denotation + + /** If this denotation does not exist, fallback to alternative */ + final def orElse(that: => Denotation) = if (this.exists) this else that + + /** The set of alternative single-denotations making up this denotation */ + final def alternatives: List[SingleDenotation] = altsWith(alwaysTrue) + + /** The alternatives of this denotation that satisfy the predicate `p`. */ + def altsWith(p: Symbol => Boolean): List[SingleDenotation] + + /** The unique alternative of this denotation that satisfies the predicate `p`, + * or NoDenotation if no satisfying alternative exists. + * @throws TypeError if there is at more than one alternative that satisfies `p`. + */ + def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation + + /** If this is a SingleDenotation, return it, otherwise throw a TypeError */ + def checkUnique(implicit ctx: Context): SingleDenotation = suchThat(alwaysTrue) + + /** Does this denotation have an alternative that satisfies the predicate `p`? */ + def hasAltWith(p: SingleDenotation => Boolean): Boolean + + /** The denotation made up from the alternatives of this denotation that + * are accessible from prefix `pre`, or NoDenotation if no accessible alternative exists. + */ + def accessibleFrom(pre: Type, superAccess: Boolean = false)(implicit ctx: Context): Denotation + + /** Find member of this denotation with given name and + * produce a denotation that contains the type of the member + * as seen from given prefix `pre`. Exclude all members that have + * flags in `excluded` from consideration. + */ + def findMember(name: Name, pre: Type, excluded: FlagSet)(implicit ctx: Context): Denotation = + info.findMember(name, pre, excluded) + + /** If this denotation is overloaded, filter with given predicate. + * If result is still overloaded throw a TypeError. + * Note: disambiguate is slightly different from suchThat in that + * single-denotations that do not satisfy the predicate are left alone + * (whereas suchThat would map them to NoDenotation). + */ + def disambiguate(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation = this match { + case sdenot: SingleDenotation => sdenot + case mdenot => suchThat(p) orElse NoQualifyingRef(alternatives) + } + + /** Return symbol in this denotation that satisfies the given predicate. + * if generateStubs is specified, return a stubsymbol if denotation is a missing ref. + * Throw a `TypeError` if predicate fails to disambiguate symbol or no alternative matches. + */ + def requiredSymbol(p: Symbol => Boolean, source: AbstractFile = null, generateStubs: Boolean = true)(implicit ctx: Context): Symbol = + disambiguate(p) match { + case m @ MissingRef(ownerd, name) => + if (generateStubs) { + m.ex.printStackTrace() + ctx.newStubSymbol(ownerd.symbol, name, source) + } + else NoSymbol + case NoDenotation | _: NoQualifyingRef => + throw new TypeError(s"None of the alternatives of $this satisfies required predicate") + case denot => + denot.symbol + } + + def requiredMethod(name: PreName)(implicit ctx: Context): TermSymbol = + info.member(name.toTermName).requiredSymbol(_ is Method).asTerm + def requiredMethodRef(name: PreName)(implicit ctx: Context): TermRef = + requiredMethod(name).termRef + + def requiredMethod(name: PreName, argTypes: List[Type])(implicit ctx: Context): TermSymbol = + info.member(name.toTermName).requiredSymbol(x=> + (x is Method) && x.info.paramTypess == List(argTypes) + ).asTerm + def requiredMethodRef(name: PreName, argTypes: List[Type])(implicit ctx: Context): TermRef = + requiredMethod(name, argTypes).termRef + + def requiredValue(name: PreName)(implicit ctx: Context): TermSymbol = + info.member(name.toTermName).requiredSymbol(_.info.isParameterless).asTerm + def requiredValueRef(name: PreName)(implicit ctx: Context): TermRef = + requiredValue(name).termRef + + def requiredClass(name: PreName)(implicit ctx: Context): ClassSymbol = + info.member(name.toTypeName).requiredSymbol(_.isClass).asClass + + /** The alternative of this denotation that has a type matching `targetType` when seen + * as a member of type `site`, `NoDenotation` if none exists. + */ + def matchingDenotation(site: Type, targetType: Type)(implicit ctx: Context): SingleDenotation = { + def qualifies(sym: Symbol) = site.memberInfo(sym).matchesLoosely(targetType) + if (isOverloaded) { + atSignature(targetType.signature, site, relaxed = true) match { + case sd: SingleDenotation => sd.matchingDenotation(site, targetType) + case md => md.suchThat(qualifies(_)) + } + } + else if (exists && !qualifies(symbol)) NoDenotation + else asSingleDenotation + } + + /** Handle merge conflict by throwing a `MergeError` exception */ + private def mergeConflict(tp1: Type, tp2: Type)(implicit ctx: Context): Type = { + def showType(tp: Type) = tp match { + case ClassInfo(_, cls, _, _, _) => cls.showLocated + case bounds: TypeBounds => i"type bounds $bounds" + case _ => tp.show + } + if (true) throw new MergeError(s"cannot merge ${showType(tp1)} with ${showType(tp2)}", tp1, tp2) + else throw new Error(s"cannot merge ${showType(tp1)} with ${showType(tp2)}") // flip condition for debugging + } + + /** Merge two lists of names. If names in corresponding positions match, keep them, + * otherwise generate new synthetic names. + */ + def mergeNames[N <: Name](names1: List[N], names2: List[N], syntheticName: Int => N): List[N] = { + for ((name1, name2, idx) <- (names1, names2, 0 until names1.length).zipped) + yield if (name1 == name2) name1 else syntheticName(idx) + }.toList + + /** Form a denotation by conjoining with denotation `that`. + * + * NoDenotations are dropped. MultiDenotations are handled by merging + * parts with same signatures. SingleDenotations with equal signatures + * are joined as follows: + * + * In a first step, consider only those denotations which have symbols + * that are accessible from prefix `pre`. + * + * If there are several such denotations, try to pick one by applying the following + * three precedence rules in decreasing order of priority: + * + * 1. Prefer denotations with more specific infos. + * 2. If infos are equally specific, prefer denotations with concrete symbols over denotations + * with abstract symbols. + * 3. If infos are equally specific and symbols are equally concrete, + * prefer denotations with symbols defined in subclasses + * over denotations with symbols defined in proper superclasses. + * + * If there is exactly one (preferred) accessible denotation, return it. + * + * If there is no preferred accessible denotation, return a JointRefDenotation + * with one of the operand symbols (unspecified which one), and an info which + * is the intersection (using `&` or `safe_&` if `safeIntersection` is true) + * of the infos of the operand denotations. + * + * If SingleDenotations with different signatures are joined, return NoDenotation. + */ + def & (that: Denotation, pre: Type, safeIntersection: Boolean = false)(implicit ctx: Context): Denotation = { + + /** Normally, `tp1 & tp2`. Special cases for matching methods and classes, with + * the possibility of raising a merge error. + */ + def infoMeet(tp1: Type, tp2: Type): Type = { + if (tp1 eq tp2) tp1 + else tp1 match { + case tp1: TypeBounds => + tp2 match { + case tp2: TypeBounds => if (safeIntersection) tp1 safe_& tp2 else tp1 & tp2 + case tp2: ClassInfo if tp1 contains tp2 => tp2 + case _ => mergeConflict(tp1, tp2) + } + case tp1: ClassInfo => + tp2 match { + case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix & tp2.prefix) + case tp2: TypeBounds if tp2 contains tp1 => tp1 + case _ => mergeConflict(tp1, tp2) + } + case tp1 @ MethodType(names1, formals1) if isTerm => + tp2 match { + case tp2 @ MethodType(names2, formals2) if ctx.typeComparer.matchingParams(formals1, formals2, tp1.isJava, tp2.isJava) && + tp1.isImplicit == tp2.isImplicit => + tp1.derivedMethodType( + mergeNames(names1, names2, nme.syntheticParamName), + formals1, + infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1))) + case _ => + mergeConflict(tp1, tp2) + } + case tp1: PolyType if isTerm => + tp2 match { + case tp2: PolyType if ctx.typeComparer.matchingTypeParams(tp1, tp2) => + tp1.derivedPolyType( + mergeNames(tp1.paramNames, tp2.paramNames, tpnme.syntheticTypeParamName), + tp1.paramBounds, + infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1))) + case _: MethodicType => + mergeConflict(tp1, tp2) + } + case _ => + tp1 & tp2 + } + } + + /** Try to merge denot1 and denot2 without adding a new signature. */ + def mergeDenot(denot1: Denotation, denot2: SingleDenotation): Denotation = denot1 match { + case denot1 @ MultiDenotation(denot11, denot12) => + val d1 = mergeDenot(denot11, denot2) + if (d1.exists) denot1.derivedMultiDenotation(d1, denot12) + else { + val d2 = mergeDenot(denot12, denot2) + if (d2.exists) denot1.derivedMultiDenotation(denot11, d2) + else NoDenotation + } + case denot1: SingleDenotation => + if (denot1 eq denot2) denot1 + else if (denot1.matches(denot2)) mergeSingleDenot(denot1, denot2) + else NoDenotation + } + + /** Try to merge single-denotations. */ + def mergeSingleDenot(denot1: SingleDenotation, denot2: SingleDenotation): SingleDenotation = { + val info1 = denot1.info + val info2 = denot2.info + val sym1 = denot1.symbol + val sym2 = denot2.symbol + + val sym2Accessible = sym2.isAccessibleFrom(pre) + + /** Does `sym1` come before `sym2` in the linearization of `pre`? */ + def precedes(sym1: Symbol, sym2: Symbol) = { + def precedesIn(bcs: List[ClassSymbol]): Boolean = bcs match { + case bc :: bcs1 => (sym1 eq bc) || !(sym2 eq bc) && precedesIn(bcs1) + case Nil => true + } + (sym1 ne sym2) && + (sym1.derivesFrom(sym2) || + !sym2.derivesFrom(sym1) && precedesIn(pre.baseClasses)) + } + + /** Similar to SymDenotation#accessBoundary, but without the special cases. */ + def accessBoundary(sym: Symbol) = + if (sym.is(Private)) sym.owner + else sym.privateWithin.orElse( + if (sym.is(Protected)) sym.owner.enclosingPackageClass + else defn.RootClass) + + /** Establish a partial order "preference" order between symbols. + * Give preference to `sym1` over `sym2` if one of the following + * conditions holds, in decreasing order of weight: + * 1. sym1 is concrete and sym2 is abstract + * 2. The owner of sym1 comes before the owner of sym2 in the linearization + * of the type of the prefix `pre`. + * 3. The access boundary of sym2 is properly contained in the access + * boundary of sym1. For protected access, we count the enclosing + * package as access boundary. + * 4. sym1 a method but sym2 is not. + * The aim of these criteria is to give some disambiguation on access which + * - does not depend on textual order or other arbitrary choices + * - minimizes raising of doubleDef errors + */ + def preferSym(sym1: Symbol, sym2: Symbol) = + sym1.eq(sym2) || + sym1.isAsConcrete(sym2) && + (!sym2.isAsConcrete(sym1) || + precedes(sym1.owner, sym2.owner) || + accessBoundary(sym2).isProperlyContainedIn(accessBoundary(sym1)) || + sym1.is(Method) && !sym2.is(Method)) || + sym1.info.isErroneous + + /** Sym preference provided types also override */ + def prefer(sym1: Symbol, sym2: Symbol, info1: Type, info2: Type) = + preferSym(sym1, sym2) && info1.overrides(info2) + + def handleDoubleDef = + if (preferSym(sym1, sym2)) denot1 + else if (preferSym(sym2, sym1)) denot2 + else doubleDefError(denot1, denot2, pre) + + if (sym2Accessible && prefer(sym2, sym1, info2, info1)) denot2 + else { + val sym1Accessible = sym1.isAccessibleFrom(pre) + if (sym1Accessible && prefer(sym1, sym2, info1, info2)) denot1 + else if (sym1Accessible && sym2.exists && !sym2Accessible) denot1 + else if (sym2Accessible && sym1.exists && !sym1Accessible) denot2 + else if (isDoubleDef(sym1, sym2)) handleDoubleDef + else { + val sym = + if (!sym1.exists) sym2 + else if (!sym2.exists) sym1 + else if (preferSym(sym2, sym1)) sym2 + else sym1 + val jointInfo = + try infoMeet(info1, info2) + catch { + case ex: MergeError => + if (pre.widen.classSymbol.is(Scala2x) || ctx.scala2Mode) + info1 // follow Scala2 linearization - + // compare with way merge is performed in SymDenotation#computeMembersNamed + else + throw new MergeError(s"${ex.getMessage} as members of type ${pre.show}", ex.tp1, ex.tp2) + } + new JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor) + } + } + } + + if (this eq that) this + else if (!this.exists) that + else if (!that.exists) this + else that match { + case that: SingleDenotation => + val r = mergeDenot(this, that) + if (r.exists) r else MultiDenotation(this, that) + case that @ MultiDenotation(denot1, denot2) => + this & (denot1, pre) & (denot2, pre) + } + } + + /** Form a choice between this denotation and that one. + * @param pre The prefix type of the members of the denotation, used + * to determine an accessible symbol if it exists. + */ + def | (that: Denotation, pre: Type)(implicit ctx: Context): Denotation = { + + /** Normally, `tp1 | tp2`. Special cases for matching methods and classes, with + * the possibility of raising a merge error. + */ + def infoJoin(tp1: Type, tp2: Type): Type = tp1 match { + case tp1: TypeBounds => + tp2 match { + case tp2: TypeBounds => tp1 | tp2 + case tp2: ClassInfo if tp1 contains tp2 => tp1 + case _ => mergeConflict(tp1, tp2) + } + case tp1: ClassInfo => + tp2 match { + case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix | tp2.prefix) + case tp2: TypeBounds if tp2 contains tp1 => tp2 + case _ => mergeConflict(tp1, tp2) + } + case tp1 @ MethodType(names1, formals1) => + tp2 match { + case tp2 @ MethodType(names2, formals2) + if ctx.typeComparer.matchingParams(formals1, formals2, tp1.isJava, tp2.isJava) && + tp1.isImplicit == tp2.isImplicit => + tp1.derivedMethodType( + mergeNames(names1, names2, nme.syntheticParamName), + formals1, tp1.resultType | tp2.resultType.subst(tp2, tp1)) + case _ => + mergeConflict(tp1, tp2) + } + case tp1: PolyType => + tp2 match { + case tp2: PolyType if ctx.typeComparer.matchingTypeParams(tp1, tp2) => + tp1.derivedPolyType( + mergeNames(tp1.paramNames, tp2.paramNames, tpnme.syntheticTypeParamName), + tp1.paramBounds, tp1.resultType | tp2.resultType.subst(tp2, tp1)) + case _ => + mergeConflict(tp1, tp2) + } + case _ => + tp1 | tp2 + } + + def unionDenot(denot1: SingleDenotation, denot2: SingleDenotation): Denotation = + if (denot1.matches(denot2)) { + val sym1 = denot1.symbol + val sym2 = denot2.symbol + val info1 = denot1.info + val info2 = denot2.info + val sameSym = sym1 eq sym2 + if (sameSym && (info1 frozen_<:< info2)) denot2 + else if (sameSym && (info2 frozen_<:< info1)) denot1 + else { + val jointSym = + if (sameSym) sym1 + else { + val owner2 = if (sym2 ne NoSymbol) sym2.owner else NoSymbol + /** Determine a symbol which is overridden by both sym1 and sym2. + * Preference is given to accessible symbols. + */ + def lubSym(overrides: Iterator[Symbol], previous: Symbol): Symbol = + if (!overrides.hasNext) previous + else { + val candidate = overrides.next + if (owner2 derivesFrom candidate.owner) + if (candidate isAccessibleFrom pre) candidate + else lubSym(overrides, previous orElse candidate) + else + lubSym(overrides, previous) + } + lubSym(sym1.allOverriddenSymbols, NoSymbol) + } + new JointRefDenotation( + jointSym, infoJoin(info1, info2), denot1.validFor & denot2.validFor) + } + } + else NoDenotation + + if (this eq that) this + else if (!this.exists) this + else if (!that.exists) that + else this match { + case denot1 @ MultiDenotation(denot11, denot12) => + denot1.derivedMultiDenotation(denot11 | (that, pre), denot12 | (that, pre)) + case denot1: SingleDenotation => + that match { + case denot2 @ MultiDenotation(denot21, denot22) => + denot2.derivedMultiDenotation(this | (denot21, pre), this | (denot22, pre)) + case denot2: SingleDenotation => + unionDenot(denot1, denot2) + } + } + } + + final def asSingleDenotation = asInstanceOf[SingleDenotation] + final def asSymDenotation = asInstanceOf[SymDenotation] + + def toText(printer: Printer): Text = printer.toText(this) + } + + /** An overloaded denotation consisting of the alternatives of both given denotations. + */ + case class MultiDenotation(denot1: Denotation, denot2: Denotation) extends Denotation(NoSymbol) { + final def infoOrCompleter = multiHasNot("info") + final def info(implicit ctx: Context) = infoOrCompleter + final def validFor = denot1.validFor & denot2.validFor + final def isType = false + final def signature(implicit ctx: Context) = Signature.OverloadedSignature + def atSignature(sig: Signature, site: Type, relaxed: Boolean)(implicit ctx: Context): Denotation = + derivedMultiDenotation(denot1.atSignature(sig, site, relaxed), denot2.atSignature(sig, site, relaxed)) + def current(implicit ctx: Context): Denotation = + derivedMultiDenotation(denot1.current, denot2.current) + def altsWith(p: Symbol => Boolean): List[SingleDenotation] = + denot1.altsWith(p) ++ denot2.altsWith(p) + def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation = { + val sd1 = denot1.suchThat(p) + val sd2 = denot2.suchThat(p) + if (sd1.exists) + if (sd2.exists) + if (isDoubleDef(denot1.symbol, denot2.symbol)) doubleDefError(denot1, denot2) + else throw new TypeError(s"failure to disambiguate overloaded reference $this") + else sd1 + else sd2 + } + def hasAltWith(p: SingleDenotation => Boolean): Boolean = + denot1.hasAltWith(p) || denot2.hasAltWith(p) + def accessibleFrom(pre: Type, superAccess: Boolean)(implicit ctx: Context): Denotation = { + val d1 = denot1 accessibleFrom (pre, superAccess) + val d2 = denot2 accessibleFrom (pre, superAccess) + if (!d1.exists) d2 + else if (!d2.exists) d1 + else derivedMultiDenotation(d1, d2) + } + def mapInfo(f: Type => Type)(implicit ctx: Context): Denotation = + derivedMultiDenotation(denot1.mapInfo(f), denot2.mapInfo(f)) + def derivedMultiDenotation(d1: Denotation, d2: Denotation) = + if ((d1 eq denot1) && (d2 eq denot2)) this else MultiDenotation(d1, d2) + override def toString = alternatives.mkString(" <and> ") + + private def multiHasNot(op: String): Nothing = + throw new UnsupportedOperationException( + s"multi-denotation with alternatives $alternatives does not implement operation $op") + } + + /** A non-overloaded denotation */ + abstract class SingleDenotation(symbol: Symbol) extends Denotation(symbol) with PreDenotation { + def hasUniqueSym: Boolean + protected def newLikeThis(symbol: Symbol, info: Type): SingleDenotation + + final def signature(implicit ctx: Context): Signature = { + if (isType) Signature.NotAMethod // don't force info if this is a type SymDenotation + else info match { + case info: MethodicType => + try info.signature + catch { // !!! DEBUG + case scala.util.control.NonFatal(ex) => + ctx.echo(s"cannot take signature of ${info.show}") + throw ex + } + case _ => Signature.NotAMethod + } + } + + def derivedSingleDenotation(symbol: Symbol, info: Type)(implicit ctx: Context): SingleDenotation = + if ((symbol eq this.symbol) && (info eq this.info)) this + else newLikeThis(symbol, info) + + def mapInfo(f: Type => Type)(implicit ctx: Context): SingleDenotation = + derivedSingleDenotation(symbol, f(info)) + + def orElse(that: => SingleDenotation) = if (this.exists) this else that + + def altsWith(p: Symbol => Boolean): List[SingleDenotation] = + if (exists && p(symbol)) this :: Nil else Nil + + def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation = + if (exists && p(symbol)) this else NoDenotation + + def hasAltWith(p: SingleDenotation => Boolean): Boolean = + exists && p(this) + + def accessibleFrom(pre: Type, superAccess: Boolean)(implicit ctx: Context): Denotation = + if (!symbol.exists || symbol.isAccessibleFrom(pre, superAccess)) this else NoDenotation + + def atSignature(sig: Signature, site: Type, relaxed: Boolean)(implicit ctx: Context): SingleDenotation = { + val situated = if (site == NoPrefix) this else asSeenFrom(site) + val matches = sig.matchDegree(situated.signature) >= + (if (relaxed) Signature.ParamMatch else Signature.FullMatch) + if (matches) this else NoDenotation + } + + // ------ Forming types ------------------------------------------- + + /** The TypeRef representing this type denotation at its original location. */ + def typeRef(implicit ctx: Context): TypeRef = + TypeRef(symbol.owner.thisType, symbol.name.asTypeName, this) + + /** The TermRef representing this term denotation at its original location. */ + def termRef(implicit ctx: Context): TermRef = + TermRef(symbol.owner.thisType, symbol.name.asTermName, this) + + /** The TermRef representing this term denotation at its original location + * and at signature `NotAMethod`. + */ + def valRef(implicit ctx: Context): TermRef = + TermRef.withSigAndDenot(symbol.owner.thisType, symbol.name.asTermName, Signature.NotAMethod, this) + + /** The TermRef representing this term denotation at its original location + * at the denotation's signature. + * @note Unlike `valRef` and `termRef`, this will force the completion of the + * denotation via a call to `info`. + */ + def termRefWithSig(implicit ctx: Context): TermRef = + TermRef.withSigAndDenot(symbol.owner.thisType, symbol.name.asTermName, signature, this) + + /** The NamedType representing this denotation at its original location. + * Same as either `typeRef` or `termRefWithSig` depending whether this denotes a type or not. + */ + def namedType(implicit ctx: Context): NamedType = + if (isType) typeRef else termRefWithSig + + // ------ Transformations ----------------------------------------- + + private[this] var myValidFor: Period = Nowhere + + def validFor = myValidFor + def validFor_=(p: Period) = + myValidFor = p + + /** The next SingleDenotation in this run, with wrap-around from last to first. + * + * There may be several `SingleDenotation`s with different validity + * representing the same underlying definition at different phases. + * These are called a "flock". Flock members are generated by + * @See current. Flock members are connected in a ring + * with their `nextInRun` fields. + * + * There are the following invariants concerning flock members + * + * 1) validity periods are non-overlapping + * 2) the union of all validity periods is a contiguous + * interval. + */ + protected var nextInRun: SingleDenotation = this + + /** The version of this SingleDenotation that was valid in the first phase + * of this run. + */ + def initial: SingleDenotation = + if (validFor == Nowhere) this + else { + var current = nextInRun + while (current.validFor.code > this.myValidFor.code) current = current.nextInRun + current + } + + def history: List[SingleDenotation] = { + val b = new ListBuffer[SingleDenotation] + var current = initial + do { + b += (current) + current = current.nextInRun + } + while (current ne initial) + b.toList + } + + /** Invalidate all caches and fields that depend on base classes and their contents */ + def invalidateInheritedInfo(): Unit = () + + /** Move validity period of this denotation to a new run. Throw a StaleSymbol error + * if denotation is no longer valid. + */ + private def bringForward()(implicit ctx: Context): SingleDenotation = this match { + case denot: SymDenotation if ctx.stillValid(denot) => + assert(ctx.runId > validFor.runId || ctx.settings.YtestPickler.value, // mixing test pickler with debug printing can travel back in time + s"denotation $denot invalid in run ${ctx.runId}. ValidFor: $validFor") + var d: SingleDenotation = denot + do { + d.validFor = Period(ctx.period.runId, d.validFor.firstPhaseId, d.validFor.lastPhaseId) + d.invalidateInheritedInfo() + d = d.nextInRun + } while (d ne denot) + this + case _ => + if (coveredInterval.containsPhaseId(ctx.phaseId)) { + if (ctx.debug) ctx.traceInvalid(this) + staleSymbolError + } + else NoDenotation + } + + /** Produce a denotation that is valid for the given context. + * Usually called when !(validFor contains ctx.period) + * (even though this is not a precondition). + * If the runId of the context is the same as runId of this denotation, + * the right flock member is located, or, if it does not exist yet, + * created by invoking a transformer (@See Transformers). + * If the runId's differ, but this denotation is a SymDenotation + * and its toplevel owner class or module + * is still a member of its enclosing package, then the whole flock + * is brought forward to be valid in the new runId. Otherwise + * the symbol is stale, which constitutes an internal error. + */ + def current(implicit ctx: Context): SingleDenotation = { + val currentPeriod = ctx.period + val valid = myValidFor + if (valid.code <= 0) { + // can happen if we sit on a stale denotation which has been replaced + // wholesale by an installAfter; in this case, proceed to the next + // denotation and try again. + if (validFor == Nowhere && nextInRun.validFor != Nowhere) return nextInRun.current + assert(false) + } + + if (valid.runId != currentPeriod.runId) + if (exists) initial.bringForward.current + else this + else { + var cur = this + if (currentPeriod.code > valid.code) { + // search for containing period as long as nextInRun increases. + var next = nextInRun + while (next.validFor.code > valid.code && !(next.validFor contains currentPeriod)) { + cur = next + next = next.nextInRun + } + if (next.validFor.code > valid.code) { + // in this case, next.validFor contains currentPeriod + cur = next + cur + } else { + //println(s"might need new denot for $cur, valid for ${cur.validFor} at $currentPeriod") + // not found, cur points to highest existing variant + val nextTransformerId = ctx.nextDenotTransformerId(cur.validFor.lastPhaseId) + if (currentPeriod.lastPhaseId <= nextTransformerId) + cur.validFor = Period(currentPeriod.runId, cur.validFor.firstPhaseId, nextTransformerId) + else { + var startPid = nextTransformerId + 1 + val transformer = ctx.denotTransformers(nextTransformerId) + //println(s"transforming $this with $transformer") + try { + next = transformer.transform(cur)(ctx.withPhase(transformer)).syncWithParents + } catch { + case ex: CyclicReference => + println(s"error while transforming $this") // DEBUG + throw ex + } + if (next eq cur) + startPid = cur.validFor.firstPhaseId + else { + next match { + case next: ClassDenotation => + assert(!next.is(Package), s"illegal transformation of package denotation by transformer ${ctx.withPhase(transformer).phase}") + next.resetFlag(Frozen) + case _ => + } + next.insertAfter(cur) + cur = next + } + cur.validFor = Period(currentPeriod.runId, startPid, transformer.lastPhaseId) + //printPeriods(cur) + //println(s"new denot: $cur, valid for ${cur.validFor}") + } + cur.current // multiple transformations could be required + } + } else { + // currentPeriod < end of valid; in this case a version must exist + // but to be defensive we check for infinite loop anyway + var cnt = 0 + while (!(cur.validFor contains currentPeriod)) { + //println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}") + cur = cur.nextInRun + // Note: One might be tempted to add a `prev` field to get to the new denotation + // more directly here. I tried that, but it degrades rather than improves + // performance: Test setup: Compile everything in dotc and immediate subdirectories + // 10 times. Best out of 10: 18154ms with `prev` field, 17777ms without. + cnt += 1 + if (cnt > MaxPossiblePhaseId) + return current(ctx.withPhase(coveredInterval.firstPhaseId)) + } + cur + } + } + } + + private def demandOutsideDefinedMsg(implicit ctx: Context): String = + s"demanding denotation of $this at phase ${ctx.phase}(${ctx.phaseId}) outside defined interval: defined periods are${definedPeriodsString}" + + /** Install this denotation to be the result of the given denotation transformer. + * This is the implementation of the same-named method in SymDenotations. + * It's placed here because it needs access to private fields of SingleDenotation. + * @pre Can only be called in `phase.next`. + */ + protected def installAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = { + val targetId = phase.next.id + if (ctx.phaseId != targetId) installAfter(phase)(ctx.withPhase(phase.next)) + else { + val current = symbol.current + // println(s"installing $this after $phase/${phase.id}, valid = ${current.validFor}") + // printPeriods(current) + this.validFor = Period(ctx.runId, targetId, current.validFor.lastPhaseId) + if (current.validFor.firstPhaseId >= targetId) + insertInsteadOf(current) + else { + current.validFor = Period(ctx.runId, current.validFor.firstPhaseId, targetId - 1) + insertAfter(current) + } + // printPeriods(this) + } + } + + /** Apply a transformation `f` to all denotations in this group that start at or after + * given phase. Denotations are replaced while keeping the same validity periods. + */ + protected def transformAfter(phase: DenotTransformer, f: SymDenotation => SymDenotation)(implicit ctx: Context): Unit = { + var current = symbol.current + while (current.validFor.firstPhaseId < phase.id && (current.nextInRun.validFor.code > current.validFor.code)) + current = current.nextInRun + var hasNext = true + while ((current.validFor.firstPhaseId >= phase.id) && hasNext) { + val current1: SingleDenotation = f(current.asSymDenotation) + if (current1 ne current) { + current1.validFor = current.validFor + current1.insertInsteadOf(current) + } + hasNext = current1.nextInRun.validFor.code > current1.validFor.code + current = current1.nextInRun + } + } + + /** Insert this denotation so that it follows `prev`. */ + private def insertAfter(prev: SingleDenotation) = { + this.nextInRun = prev.nextInRun + prev.nextInRun = this + } + + /** Insert this denotation instead of `old`. + * Also ensure that `old` refers with `nextInRun` to this denotation + * and set its `validFor` field to `NoWhere`. This is necessary so that + * references to the old denotation can be brought forward via `current` + * to a valid denotation. + * + * The code to achieve this is subtle in that it works correctly + * whether the replaced denotation is the only one in its cycle or not. + */ + private def insertInsteadOf(old: SingleDenotation): Unit = { + var prev = old + while (prev.nextInRun ne old) prev = prev.nextInRun + // order of next two assignments is important! + prev.nextInRun = this + this.nextInRun = old.nextInRun + old.validFor = Nowhere + } + + def staleSymbolError(implicit ctx: Context) = { + def ownerMsg = this match { + case denot: SymDenotation => s"in ${denot.owner}" + case _ => "" + } + def msg = s"stale symbol; $this#${symbol.id} $ownerMsg, defined in ${myValidFor}, is referred to in run ${ctx.period}" + throw new StaleSymbol(msg) + } + + /** The period (interval of phases) for which there exists + * a valid denotation in this flock. + */ + def coveredInterval(implicit ctx: Context): Period = { + var cur = this + var cnt = 0 + var interval = validFor + do { + cur = cur.nextInRun + cnt += 1 + assert(cnt <= MaxPossiblePhaseId, demandOutsideDefinedMsg) + interval |= cur.validFor + } while (cur ne this) + interval + } + + /** For ClassDenotations only: + * If caches influenced by parent classes are still valid, the denotation + * itself, otherwise a freshly initialized copy. + */ + def syncWithParents(implicit ctx: Context): SingleDenotation = this + + /** Show declaration string; useful for showing declarations + * as seen from subclasses. + */ + def showDcl(implicit ctx: Context): String = ctx.dclText(this).show + + override def toString = + if (symbol == NoSymbol) symbol.toString + else s"<SingleDenotation of type $infoOrCompleter>" + + def definedPeriodsString: String = { + var sb = new StringBuilder() + var cur = this + var cnt = 0 + do { + sb.append(" " + cur.validFor) + cur = cur.nextInRun + cnt += 1 + if (cnt > MaxPossiblePhaseId) { sb.append(" ..."); cur = this } + } while (cur ne this) + sb.toString + } + + // ------ PreDenotation ops ---------------------------------------------- + + final def first = this + final def last = this + final def toDenot(pre: Type)(implicit ctx: Context): Denotation = this + final def containsSym(sym: Symbol): Boolean = hasUniqueSym && (symbol eq sym) + final def matches(other: SingleDenotation)(implicit ctx: Context): Boolean = { + val d = signature.matchDegree(other.signature) + d == Signature.FullMatch || + d >= Signature.ParamMatch && info.matches(other.info) + } + final def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = + if (p(this)) this else NoDenotation + final def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): SingleDenotation = + if (denots.exists && denots.matches(this)) NoDenotation else this + def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): SingleDenotation = + if (hasUniqueSym && prevDenots.containsSym(symbol)) NoDenotation + else if (isType) filterDisjoint(ownDenots).asSeenFrom(pre) + else asSeenFrom(pre).filterDisjoint(ownDenots) + final def filterExcluded(excluded: FlagSet)(implicit ctx: Context): SingleDenotation = + if (excluded.isEmpty || !(this overlaps excluded)) this else NoDenotation + + type AsSeenFromResult = SingleDenotation + protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): SingleDenotation = { + val symbol = this.symbol + val owner = this match { + case thisd: SymDenotation => thisd.owner + case _ => if (symbol.exists) symbol.owner else NoSymbol + } + if (!owner.membersNeedAsSeenFrom(pre)) this + else derivedSingleDenotation(symbol, info.asSeenFrom(pre, owner)) + } + + private def overlaps(fs: FlagSet)(implicit ctx: Context): Boolean = this match { + case sd: SymDenotation => sd is fs + case _ => symbol is fs + } + } + + abstract class NonSymSingleDenotation(symbol: Symbol) extends SingleDenotation(symbol) { + def infoOrCompleter: Type + def info(implicit ctx: Context) = infoOrCompleter + def isType = infoOrCompleter.isInstanceOf[TypeType] + } + + class UniqueRefDenotation( + symbol: Symbol, + val infoOrCompleter: Type, + initValidFor: Period) extends NonSymSingleDenotation(symbol) { + validFor = initValidFor + override def hasUniqueSym: Boolean = true + protected def newLikeThis(s: Symbol, i: Type): SingleDenotation = new UniqueRefDenotation(s, i, validFor) + } + + class JointRefDenotation( + symbol: Symbol, + val infoOrCompleter: Type, + initValidFor: Period) extends NonSymSingleDenotation(symbol) { + validFor = initValidFor + override def hasUniqueSym = false + protected def newLikeThis(s: Symbol, i: Type): SingleDenotation = new JointRefDenotation(s, i, validFor) + } + + class ErrorDenotation(implicit ctx: Context) extends NonSymSingleDenotation(NoSymbol) { + override def exists = false + override def hasUniqueSym = false + def infoOrCompleter = NoType + validFor = Period.allInRun(ctx.runId) + protected def newLikeThis(s: Symbol, i: Type): SingleDenotation = this + } + + /** An error denotation that provides more info about the missing reference. + * Produced by staticRef, consumed by requiredSymbol. + */ + case class MissingRef(val owner: SingleDenotation, name: Name)(implicit ctx: Context) extends ErrorDenotation { + val ex: Exception = new Exception + } + + /** An error denotation that provides more info about alternatives + * that were found but that do not qualify. + * Produced by staticRef, consumed by requiredSymbol. + */ + case class NoQualifyingRef(alts: List[SingleDenotation])(implicit ctx: Context) extends ErrorDenotation + + /** A double definition + */ + def isDoubleDef(sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Boolean = + (sym1.exists && sym2.exists && + (sym1 ne sym2) && (sym1.owner eq sym2.owner) && + !sym1.is(Bridge) && !sym2.is(Bridge)) + + def doubleDefError(denot1: Denotation, denot2: Denotation, pre: Type = NoPrefix)(implicit ctx: Context): Nothing = { + val sym1 = denot1.symbol + val sym2 = denot2.symbol + def fromWhere = if (pre == NoPrefix) "" else i"\nwhen seen as members of $pre" + throw new MergeError( + i"""cannot merge + | $sym1: ${sym1.info} and + | $sym2: ${sym2.info}; + |they are both defined in ${sym1.owner} but have matching signatures + | ${denot1.info} and + | ${denot2.info}$fromWhere""", + denot2.info, denot2.info) + } + + // --------------- PreDenotations ------------------------------------------------- + + /** A PreDenotation represents a group of single denotations + * It is used as an optimization to avoid forming MultiDenotations too eagerly. + */ + trait PreDenotation { + + /** A denotation in the group exists */ + def exists: Boolean + + /** First/last denotation in the group */ + def first: Denotation + def last: Denotation + + /** Convert to full denotation by &-ing all elements */ + def toDenot(pre: Type)(implicit ctx: Context): Denotation + + /** Group contains a denotation that refers to given symbol */ + def containsSym(sym: Symbol): Boolean + + /** Group contains a denotation with given signature */ + def matches(other: SingleDenotation)(implicit ctx: Context): Boolean + + /** Keep only those denotations in this group which satisfy predicate `p`. */ + def filterWithPredicate(p: SingleDenotation => Boolean): PreDenotation + + /** Keep only those denotations in this group which have a signature + * that's not already defined by `denots`. + */ + def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): PreDenotation + + /** Keep only those inherited members M of this predenotation for which the following is true + * - M is not marked Private + * - If M has a unique symbol, it does not appear in `prevDenots`. + * - M's signature as seen from prefix `pre` does not appear in `ownDenots` + * Return the denotation as seen from `pre`. + * Called from SymDenotations.computeMember. There, `ownDenots` are the denotations found in + * the base class, which shadow any inherited denotations with the same signature. + * `prevDenots` are the denotations that are defined in the class or inherited from + * a base type which comes earlier in the linearization. + */ + def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation + + /** Keep only those denotations in this group whose flags do not intersect + * with `excluded`. + */ + def filterExcluded(excluded: FlagSet)(implicit ctx: Context): PreDenotation + + private var cachedPrefix: Type = _ + private var cachedAsSeenFrom: AsSeenFromResult = _ + private var validAsSeenFrom: Period = Nowhere + type AsSeenFromResult <: PreDenotation + + /** The denotation with info(s) as seen from prefix type */ + final def asSeenFrom(pre: Type)(implicit ctx: Context): AsSeenFromResult = + if (Config.cacheAsSeenFrom) { + if ((cachedPrefix ne pre) || ctx.period != validAsSeenFrom) { + cachedAsSeenFrom = computeAsSeenFrom(pre) + cachedPrefix = pre + validAsSeenFrom = ctx.period + } + cachedAsSeenFrom + } else computeAsSeenFrom(pre) + + protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): AsSeenFromResult + + /** The union of two groups. */ + def union(that: PreDenotation) = + if (!this.exists) that + else if (!that.exists) this + else DenotUnion(this, that) + } + + final case class DenotUnion(denots1: PreDenotation, denots2: PreDenotation) extends PreDenotation { + assert(denots1.exists && denots2.exists, s"Union of non-existing denotations ($denots1) and ($denots2)") + def exists = true + def first = denots1.first + def last = denots2.last + def toDenot(pre: Type)(implicit ctx: Context) = + (denots1 toDenot pre) & (denots2 toDenot pre, pre) + def containsSym(sym: Symbol) = + (denots1 containsSym sym) || (denots2 containsSym sym) + def matches(other: SingleDenotation)(implicit ctx: Context): Boolean = + denots1.matches(other) || denots2.matches(other) + def filterWithPredicate(p: SingleDenotation => Boolean): PreDenotation = + derivedUnion(denots1 filterWithPredicate p, denots2 filterWithPredicate p) + def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): PreDenotation = + derivedUnion(denots1 filterDisjoint denots, denots2 filterDisjoint denots) + def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation = + derivedUnion(denots1.mapInherited(ownDenots, prevDenots, pre), denots2.mapInherited(ownDenots, prevDenots, pre)) + def filterExcluded(excluded: FlagSet)(implicit ctx: Context): PreDenotation = + derivedUnion(denots1.filterExcluded(excluded), denots2.filterExcluded(excluded)) + + type AsSeenFromResult = PreDenotation + protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): PreDenotation = + derivedUnion(denots1.asSeenFrom(pre), denots2.asSeenFrom(pre)) + private def derivedUnion(denots1: PreDenotation, denots2: PreDenotation) = + if ((denots1 eq this.denots1) && (denots2 eq this.denots2)) this + else denots1 union denots2 + } + + // --------------- Context Base Trait ------------------------------- + + trait DenotationsBase { this: ContextBase => + + /** The current denotation of the static reference given by path, + * or a MissingRef or NoQualifyingRef instance, if it does not exist. + * if generateStubs is set, generates stubs for missing top-level symbols + */ + def staticRef(path: Name, generateStubs: Boolean = true)(implicit ctx: Context): Denotation = { + def recur(path: Name, len: Int): Denotation = { + val point = path.lastIndexOf('.', len - 1) + val owner = + if (point > 0) recur(path.toTermName, point).disambiguate(_.info.isParameterless) + else if (path.isTermName) defn.RootClass.denot + else defn.EmptyPackageClass.denot + if (owner.exists) { + val name = path slice (point + 1, len) + val result = owner.info.member(name) + if (result ne NoDenotation) result + else { + val alt = + if (generateStubs) missingHook(owner.symbol.moduleClass, name) + else NoSymbol + if (alt.exists) alt.denot + else MissingRef(owner, name) + } + } + else owner + } + recur(path, path.length) + } + + /** If we are looking for a non-existing term name in a package, + * assume it is a package for which we do not have a directory and + * enter it. + */ + def missingHook(owner: Symbol, name: Name)(implicit ctx: Context): Symbol = + if ((owner is Package) && name.isTermName) + ctx.newCompletePackageSymbol(owner, name.asTermName).entered + else + NoSymbol + } + + /** An exception for accessing symbols that are no longer valid in current run */ + class StaleSymbol(msg: => String) extends Exception { + util.Stats.record("stale symbol") + override def getMessage() = msg + } +}
\ No newline at end of file |