scala.tools.nsc.tasty.TreeUnpickler.scala Maven / Gradle / Ivy
The newest version!
/*
* 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.tools.nsc.tasty
import scala.tools.tasty.{TastyRefs, TastyReader, TastyName, TastyFormat, TastyFlags}
import TastyRefs._, TastyFlags._, TastyFormat._
import ForceKinds._
import scala.annotation.{switch, unused}
import scala.collection.mutable
import scala.reflect.io.AbstractFile
import scala.reflect.internal.Variance
import scala.util.chaining._
import scala.collection.immutable.ArraySeq
/**`TreeUnpickler` is responsible for traversing all trees in the "ASTs" section of a TASTy file, which represent the
* definitions inside the classfile associated with the root class/module. `TreeUnpickler` will enter the public api
* of the TASTy file into the symbolTable of `TastyUniverse`. "Public API" includes annotations when they are
* simple trees.
*
* Where possible, `TreeUnpickler` should not directly manipulate values created by the symbolTable, but use
* operations provided by `TastyUniverse`
* @param reader the reader from which to unpickle
* @param nameAtRef an index of names from the tasty file of this unpickler
* @param tasty the handle on the `TastyUniverse`
*/
class TreeUnpickler[Tasty <: TastyUniverse](
reader: TastyReader,
nameAtRef: NameRef => TastyName)(implicit
val tasty: Tasty) { self =>
import tasty._
import TreeUnpickler._
import MaybeCycle._
import TastyModes._
@inline
final protected def unsupportedWhen(cond: Boolean, msg: => String)(implicit ctx: Context): Unit =
if (cond) unsupportedError(msg)
/** A map from addresses of definition entries to the symbols they define */
private val symAtAddr = new mutable.HashMap[Addr, Symbol]
/** A temporary map from addresses of definition entries to the trees they define.
* Used to remember trees of symbols that are created by a completion. Emptied
* once the tree is inlined into a larger tree.
*/
private val cycleAtAddr = new mutable.HashMap[Addr, MaybeCycle]
/** A map from addresses of type entries to the types they define.
* Currently only populated for types that might be recursively referenced
* from within themselves (i.e. RecTypes, LambdaTypes).
*/
private val typeAtAddr = new mutable.HashMap[Addr, Type]
/** The root symbol denotation which are defined by the Tasty file associated with this
* TreeUnpickler. Set by `enterTopLevel`.
*/
private[this] var roots: Set[Symbol] = _
/** The root owner tree. See `OwnerTree` class definition. Set by `enterTopLevel`. */
private[this] var ownerTree: OwnerTree = _
//---------------- unpickling trees ----------------------------------------------------------------------------------
private def registerSym(addr: Addr, sym: Symbol, rejected: Boolean)(implicit ctx: Context) = {
assert(!(rejected && isSymbol(sym)), "expected no symbol when rejected")
ctx.log(
if (isSymbol(sym)) s"$addr registered ${showSym(sym)}"
else s"$addr registering symbol was rejected"
)
symAtAddr(addr) = sym
}
/** Enter all toplevel classes and objects into their scopes
*/
def enterTopLevel(classRoot: Symbol, objectRoot: Symbol)(implicit ctx: Context): Unit = {
this.roots = Set(objectRoot, classRoot)
val rdr = new TreeReader(reader).fork
ownerTree = new OwnerTree(NoAddr, 0, rdr.fork, reader.endAddr)
def indexTopLevel()(implicit ctx: Context): Unit = rdr.indexStats(reader.endAddr)
if (rdr.isTopLevel) {
inIndexScopedStatsContext { ctx0 =>
ctx0.trace(traceTopLevel(classRoot, objectRoot)) {
indexTopLevel()(ctx0)
}
}
}
}
private def traceTopLevel(classRoot: Symbol, objectRoot: Symbol) = TraceInfo[Unit](
query = s"reading top level roots",
qual = s"${showSym(classRoot)}, ${showSym(objectRoot)}",
res = _ => "entered top level roots"
)
/** A completer that captures the current position and context, which then uses the position to discover the symbol
* to compute the info for.
*/
class Completer(
isClass: Boolean,
reader: TastyReader,
tflags: TastyFlagSet
)(implicit ctx: Context)
extends TastyCompleter(isClass, tflags) {
private val symAddr = reader.currentAddr
private def fork(reader: TastyReader): TastyReader = reader.subReader(reader.startAddr, reader.endAddr)
def computeInfo(sym: Symbol)(implicit ctx: Context): Unit = {
// implicit assertion that the completion is done by the same mirror that loaded owner
require(symAtAddr(symAddr) eq sym)
cycleAtAddr(symAddr) = ctx.withPhaseNoLater("pickler") { ctx0 =>
new TreeReader(fork(reader)).readIndexedMember()(ctx0) // fork here so that cycles start at the same address
}
}
}
class TreeReader(val reader: TastyReader) {
import reader._
def forkAt(start: Addr): TreeReader = new TreeReader(subReader(start, endAddr))
def fork: TreeReader = forkAt(currentAddr)
def skipParentTree(tag: Int): Unit = if (tag != SPLITCLAUSE) skipTree(tag)
def skipParentTree(): Unit = skipParentTree(readByte())
def skipTree(tag: Int): Unit =
if (tag >= firstLengthTreeTag) goto(readEnd())
else if (tag >= firstNatASTTreeTag) { readNat(); skipTree() }
else if (tag >= firstASTTreeTag) skipTree()
else if (tag >= firstNatTreeTag) readNat()
def skipTree(): Unit = skipTree(readByte())
def skipParams(): Unit =
while ({
val tag = nextByte
tag == PARAM || tag == TYPEPARAM || tag == EMPTYCLAUSE || tag == SPLITCLAUSE
}) skipTree()
/** Record all directly nested definitions and templates in current tree
* as `OwnerTree`s in `buf`.
* A complication concerns member definitions. These are lexically nested in a
* Template node, but need to be listed separately in the OwnerTree of the enclosing class
* in order not to confuse owner chains.
*/
def scanTree(buf: mutable.ListBuffer[OwnerTree], mode: MemberDefMode): Unit = {
val start = currentAddr
val tag = readByte()
tag match {
case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | TEMPLATE =>
val end = readEnd()
for (_ <- 0 until numRefs(tag)) readNat()
if (tag === TEMPLATE) {
// Read all member definitions now, whereas non-members are children of
// template's owner tree.
val nonMemberReader = fork
scanTrees(buf, end, MemberDefsOnly)
buf += new OwnerTree(start, tag, nonMemberReader, end)
}
else if (mode != NoMemberDefs)
buf += new OwnerTree(start, tag, fork, end)
goto(end)
case tag =>
if (mode === MemberDefsOnly) skipTree(tag)
else if (tag >= firstLengthTreeTag) {
val end = readEnd()
val nrefs = numRefs(tag)
if (nrefs < 0) {
for (_ <- nrefs until 0) scanTree(buf, AllDefs)
goto(end)
}
else {
for (_ <- 0 until nrefs) readNat()
if (tag === BIND) {
// a Bind is never the owner of anything, so we set `end = start`
buf += new OwnerTree(start, tag, fork, end = start)
}
scanTrees(buf, end, AllDefs)
}
}
else if (tag >= firstNatASTTreeTag) { readNat(); scanTree(buf, AllDefs) }
else if (tag >= firstASTTreeTag) scanTree(buf, AllDefs)
else if (tag >= firstNatTreeTag) readNat()
}
}
/** Record all directly nested definitions and templates between current address and `end`
* as `OwnerTree`s in `buf`
*/
def scanTrees(buf: mutable.ListBuffer[OwnerTree], end: Addr, mode: MemberDefMode): Unit = {
while (currentAddr.index < end.index) scanTree(buf, mode)
assert(currentAddr.index === end.index)
}
/** The next tag, following through SHARED tags */
def nextUnsharedTag: Int = {
val tag = nextByte
if (tag === SHAREDtype || tag === SHAREDterm) {
val lookAhead = fork
lookAhead.reader.readByte()
forkAt(lookAhead.reader.readAddr()).nextUnsharedTag
}
else tag
}
def readTastyName(): TastyName = nameAtRef(readNameRef())
// ------ Reading types -----------------------------------------------------
/** Read names in an interleaved sequence of types/bounds and (parameter) names,
* possibly followed by a sequence of modifiers.
*/
def readParamNamesAndMods(end: Addr): (ArraySeq[TastyName], TastyFlagSet) = {
val names =
collectWhile(currentAddr != end && !isModifierTag(nextByte)) {
skipTree()
readTastyName()
}
var mods = EmptyTastyFlags
while (currentAddr != end) { // avoid boxing the mods
readByte() match {
case IMPLICIT => mods |= Implicit
case ERASED => mods |= Erased
case GIVEN => mods |= Given
}
}
(names.to(ArraySeq), mods)
}
/** Read `n` parameter types or bounds which are interleaved with names */
def readParamTypes(ps: ArraySeq[Symbol])(implicit ctx: Context): ArraySeq[Type] = {
def inner(ps1: Iterator[Symbol], buf: mutable.ArrayBuffer[Type]): ArraySeq[Type] = {
if (ps1.isEmpty) buf.to(ArraySeq)
else {
val p = ps1.next()
val rest = ps1
val localCtx = ctx.withOwner(p)
val t = readType()(localCtx)
readNat() // skip name
inner(rest, buf += t)
}
}
inner(ps.iterator, new mutable.ArrayBuffer)
}
/** Read reference to definition and return symbol created at that definition */
def readSymRef()(implicit ctx: Context): Symbol = symbolAt(readAddr())
/** The symbol at given address; create a new one if none exists yet */
def symbolAt(addr: Addr)(implicit ctx: Context): Symbol = symAtAddr.get(addr) match {
case Some(sym) =>
sym
case None =>
ctx.trace(traceForwardReference(addr)) {
val ctxAtOwner = ctx.withOwner(ownerTree.findOwner(addr))
forkAt(addr).createSymbol()(ctxAtOwner)
}
}
private def traceForwardReference(addr: Addr) = TraceInfo[Symbol](
query = s"creating forward reference",
qual = s"at $addr",
res = sym => s"$addr forward reference to ${showSym(sym)}"
)
/** The symbol defined by current definition */
def symbolAtCurrent()(implicit ctx: Context): Symbol = symAtAddr.get(currentAddr) match {
case Some(sym) =>
assert(ctx.owner === sym.owner, s"owner discrepancy for ${showSym(sym)}, expected: ${showSym(ctx.owner)}, found: ${showSym(sym.owner)}")
sym
case None =>
ctx.trace(traceCurrentSymbol(currentAddr)) {
createSymbol()
}
}
private def traceCurrentSymbol(addr: Addr) = TraceInfo[Symbol](
query = "create symbol at current address",
qual = s"$addr",
res = sym => if (!isSymbol(sym)) s"evicted symbol at $addr" else s"created ${showSym(sym)} at $addr"
)
def readConstant(tag: Int)(implicit ctx: Context): Constant = (tag: @switch) match {
case UNITconst =>
tpd.Constant(())
case TRUEconst =>
tpd.Constant(true)
case FALSEconst =>
tpd.Constant(false)
case BYTEconst =>
tpd.Constant(readInt().toByte)
case SHORTconst =>
tpd.Constant(readInt().toShort)
case CHARconst =>
tpd.Constant(readNat().toChar)
case INTconst =>
tpd.Constant(readInt())
case LONGconst =>
tpd.Constant(readLongInt())
case FLOATconst =>
tpd.Constant(java.lang.Float.intBitsToFloat(readInt()))
case DOUBLEconst =>
tpd.Constant(java.lang.Double.longBitsToDouble(readLongInt()))
case STRINGconst =>
tpd.Constant(readTastyName().asSimpleName.raw)
case NULLconst =>
tpd.Constant(null)
case CLASSconst =>
tpd.Constant(readType())
}
/** Read a type */
def readType()(implicit ctx: Context): Type = {
val start = currentAddr
val tag = readByte()
def traceReadType = TraceInfo[Type](
query = "reading type",
qual = s"${astTagToString(tag)} $start",
res = tpe => s"exit ${showType(tpe)} ${astTagToString(tag)} $start"
)
def registeringTypeWith[T](tp: Type, op: => T): T = {
typeAtAddr(start) = tp
op
}
def readLengthType(): Type = {
val end = readEnd()
def readMethodic[N <: TastyName](
factory: LambdaFactory[N],
parseFlags: FlagSets.FlagParser,
nameMap: TastyName => N
)(implicit ctx: Context): Type = {
val result = typeAtAddr.getOrElse(start, {
// TODO [tasty]: can we share LambdaTypes/RecType/RefinedType safely
// under a new context owner? (aka when referenced by a `SHAREDtype`).
// So far this has been safe to do, but perhaps with macros comparing the
// owners of the symbols of PolyTypes maybe not?
// one concrete example where TypeLambdaType is shared between two unrelated classes:
// - test/tasty/run/src-3/tastytest/issue12420/ShareLambda.scala
val nameReader = fork
nameReader.skipTree() // skip result
val paramReader = nameReader.fork
val (paramNames, mods) = nameReader.readParamNamesAndMods(end)
LambdaFactory.parse(factory, paramNames.map(nameMap), parseFlags(mods)(ctx))(
ps => paramReader.readParamTypes(ps),
() => readType(),
pt => typeAtAddr(start) = pt, // register the lambda so that we can access its parameters
)
})
goto(end)
result
}
def readVariances(tp: Type): Type = tp match {
case tp: LambdaPolyType if currentAddr != end =>
val vs = until(end) {
readByte() match {
case STABLE => Variance.Invariant
case COVARIANT => Variance.Covariant
case CONTRAVARIANT => Variance.Contravariant
}
}
tp.withVariances(vs)
case _ => tp
}
val result =
(tag: @switch) match {
case TERMREFin =>
defn.TermRefIn(name = readTastyName(), prefix = readType(), space = readType())
case TYPEREFin =>
defn.TypeRefIn(
name = readTastyName().toTypeName, prefix = readType(), space = readType())
case REFINEDtype =>
var name = readTastyName()
val parent = readType()
if (nextUnsharedTag === TYPEBOUNDS) name = name.toTypeName
ctx.enterRefinement(parent)(refinedCtx =>
defn.RefinedType(parent, name, refinedCtx.owner, readType())
)
case APPLIEDtype => defn.AppliedType(readType(), until(end)(readType()))
case TYPEBOUNDS =>
val lo = readType()
if (nothingButMods(end)) readVariances(lo)
else defn.TypeBounds(lo, readVariances(readType()))
case ANNOTATEDtype => defn.AnnotatedType(readType(), readTerm()(ctx.addMode(ReadAnnotTopLevel)))
case ANDtype => defn.IntersectionType(readType(), readType())
case ORtype => unionIsUnsupported
case SUPERtype => defn.SuperType(readType(), readType())
case MATCHtype | MATCHCASEtype => matchTypeIsUnsupported
case POLYtype => readMethodic(PolyTypeLambda, FlagSets.addDeferred, _.toTypeName)
case METHODtype => readMethodic(MethodTermLambda, FlagSets.parseMethod, id)
case TYPELAMBDAtype => readMethodic(HKTypeLambda, FlagSets.addDeferred, _.toTypeName)
case PARAMtype => defn.ParamRef(readTypeRef(), readNat()) // reference to a parameter within a LambdaType
case FLEXIBLEtype =>
// dotty would wrap the inner type in FlexibleType (with lower bound >: tpe | Null),
// but we can leave as-is - as Scala 2 does not have explicit nulls.
readType()
}
assert(currentAddr === end, s"$start $currentAddr $end ${astTagToString(tag)}")
result
}
def readSimpleType(): Type = {
(tag: @switch) match {
case TYPEREFdirect => defn.NamedType(defn.NoPrefix, readSymRef())
case TERMREFdirect => defn.NamedType(defn.NoPrefix, readSymRef())
case TYPEREFsymbol | TERMREFsymbol => defn.NamedType(sym = readSymRef(), prefix = readType())
case TYPEREFpkg => defn.NamedType(defn.NoPrefix, sym = readPackageRef().objectImplementation)
case TERMREFpkg => defn.NamedType(defn.NoPrefix, sym = readPackageRef())
case TYPEREF => defn.TypeRef(name = readTastyName().toTypeName, prefix = readType())
case TERMREF => defn.TermRef(name = readTastyName(), prefix = readType())
case THIS => defn.ThisType(readType())
case RECtype =>
typeAtAddr.get(start) match {
case Some(tp) =>
skipTree(tag)
tp
case None =>
defn.RecType(rt =>
registeringTypeWith(rt, readType()(ctx.withOwner(rt.refinementClass)))
).tap(typeAtAddr(start) = _)
}
case RECthis => defn.RecThis(readTypeRef())
case SHAREDtype =>
val ref = readAddr()
// TODO [tasty]: there are enough cases now to suggest that ultimately
// nsc is not designed around cached types, e.g.
// - unique symbols for wildcard type arguments
// - context-sensitive substitutions of types we need to make,
// which may propagate incorrect semantics to other sites if the type is shared
// so we should probably remove this,
// however as of writing removing caching breaks `TastyTestJUnit.run`,
// so further investigation is needed.
typeAtAddr.getOrElseUpdate(ref, forkAt(ref).readType())
case BYNAMEtype => defn.ByNameType(readType())
case _ => defn.ConstantType(readConstant(tag))
}
}
ctx.traceV(traceReadType) {
if (tag < firstLengthTreeTag) readSimpleType() else readLengthType()
}
}
private def readPackageRef()(implicit ctx: Context): Symbol = {
ctx.requiredPackage(readTastyName())
}
def readTypeRef(): Type = typeAtAddr(readAddr())
// ------ Reading definitions -----------------------------------------------------
private def nothingButMods(end: Addr): Boolean =
currentAddr === end || isModifierTag(nextByte)
private def normalizeName(isType: Boolean, name: TastyName)(implicit ctx: Context): TastyName = {
val prior = if (ctx.owner.isTrait && name === TastyName.Constructor) TastyName.MixinConstructor else name
if (isType) prior.toTypeName else prior
}
private def addInferredFlags(tag: Int, tastyFlags: TastyFlagSet, name: TastyName, isAbsType: Boolean, isClass: Boolean, rhsIsEmpty: Boolean)(implicit ctx: Context): TastyFlagSet = {
var flags = tastyFlags
if (flags.is(Given))
flags |= Implicit
val lacksDefinition =
rhsIsEmpty &&
name.isTermName && !name.isConstructorName && !flags.isOneOf(FlagSets.TermParamOrAccessor) ||
isAbsType ||
flags.is(Opaque) && !isClass
if (lacksDefinition && tag != PARAM) flags |= Deferred
if (isClass && flags.is(Trait)) flags |= Abstract
if (tag === DEFDEF) {
flags |= Method
if (name.isDefaultName)
flags |= HasDefault // this corresponds to DEFAULTPARAM
if (ctx.isJava && !lacksDefinition && ctx.owner.is(Trait) && !name.isConstructorName)
flags |= HasDefault // will be replaced by JAVA_DEFAULTMETHOD
}
if (tag === VALDEF) {
if (flags.is(Inline) || ctx.owner.is(Trait))
flags |= FieldAccessor
if (flags.not(Mutable))
flags |= Stable
if (flags.is(Case | Enum)) // singleton enum case
flags |= Object | Stable // encode as a module (this needs to be corrected in bytecode)
}
if (ctx.owner.isClass) {
if (tag === TYPEPARAM) flags |= Param
else if (tag === PARAM) {
flags |= ParamSetter | FieldAccessor | Stable
if (!rhsIsEmpty) // param alias
flags |= Method
}
}
else if (isParamTag(tag)) flags |= Param
if (flags.is(Object)) flags |= (if (tag === VALDEF) FlagSets.Creation.ObjectDef else FlagSets.Creation.ObjectClassDef)
flags
}
def isAbstractType(@unused ttag: Int)(implicit ctx: Context): Boolean = nextUnsharedTag match {
case LAMBDAtpt =>
val rdr = fork
rdr.reader.readByte() // tag
rdr.reader.readNat() // length
rdr.skipParams() // tparams
rdr.isAbstractType(rdr.nextUnsharedTag)
case TYPEBOUNDS | TYPEBOUNDStpt => true
case _ => false
}
/** Create symbol of definition node and enter in symAtAddr map
* @return the created symbol
*/
def createSymbol()(implicit ctx: Context): Symbol = nextByte match {
case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM =>
createMemberSymbol()
case TEMPLATE =>
val localDummy = ctx.newLocalDummy
registerSym(currentAddr, localDummy, rejected = false)
localDummy
case tag =>
assert(tag != BIND, "bind pattern symbol creation from TASTy")
throw new Error(s"illegal createSymbol at $currentAddr, tag = $tag")
}
/** Create symbol of member definition or parameter node and enter in symAtAddr map
* @return the created symbol
*/
def createMemberSymbol()(implicit ctx: Context): Symbol = {
def rejectSymbol(owner: Symbol, name: TastyName, flags: TastyFlagSet): Boolean = {
def isPureMixinCtor =
name == TastyName.MixinConstructor && owner.isTrait && flags.is(Stable)
def isInvisible =
flags.is(Invisible)
isPureMixinCtor || isInvisible
}
val start = currentAddr
val tag = readByte()
def isTypeTag = tag === TYPEDEF || tag === TYPEPARAM
val end = readEnd()
val parsedName: TastyName = readTastyName()
ctx.log(s"${astTagToString(tag)} ${parsedName.debug} in ${location(ctx.owner)}")
skipParams()
val ttag = nextUnsharedTag
val isAbsType = isAbstractType(ttag)
val isClass = ttag === TEMPLATE
val templateStart = currentAddr
skipTree() // tpt
val rhsIsEmpty = nothingButMods(end)
if (!rhsIsEmpty) skipTree()
val (parsedFlags0, annotations, privateWithin) =
readModifiers(end, readTypedAnnot, readTypedWithin, noSymbol)
val name = normalizeName(isTypeTag, parsedName)
val flags = addInferredFlags(tag, parsedFlags0, name, isAbsType, isClass, rhsIsEmpty)
def mkCompleter = new Completer(isClass, subReader(start, end), flags)(ctx.retractMode(IndexScopedStats))
def isTypeParameter = flags.is(Param) && isTypeTag
def canEnterInClass = !isTypeParameter
ctx.log {
val privateFlag = {
if (isSymbol(privateWithin)) {
if (flags.is(Protected)) s"Protected[$privateWithin]"
else s"Private[$privateWithin]"
}
else {
""
}
}
val debugFlags = {
if (privateFlag.nonEmpty) {
val flags0 = flags &~ Protected
val rest = if (!flags0) "" else s" ${flags0.debug}"
privateFlag + rest
}
else flags.debug
}
s"""$start parsed flags $debugFlags"""
}
val rejected = rejectSymbol(ctx.owner, name, flags)
val sym = {
if (tag === TYPEPARAM && ctx.owner.isConstructor) {
// TASTy encodes type parameters for constructors
// nsc only has class type parameters
val tparam = ctx.findOuterClassTypeParameter(name.toTypeName)
ctx.log(s"$start reusing class type param ${showSym(tparam)}")
tparam
}
else {
ctx.findRootSymbol(roots, name) match {
case Some(rootd) =>
roots -= rootd
if (rejected) {
ctx.evict(rootd)
noSymbol
}
else {
ctx.redefineSymbol(rootd, mkCompleter, privateWithin)
ctx.log(s"$start replaced info of root ${showSym(rootd)}")
rootd
}
case _ =>
if (rejected) noSymbol
else if (isClass) ctx.delayClassCompletion(ctx.owner, name.toTypeName, mkCompleter, privateWithin)
else ctx.delayCompletion(ctx.owner, name, mkCompleter, privateWithin)
}
}
}
registerSym(start, sym, rejected)
if (isSymbol(sym)) {
if (tag == VALDEF && flags.is(FlagSets.SingletonEnum))
ctx.markAsEnumSingleton(sym)
if (canEnterInClass && ctx.owner.isClass)
ctx.enterIfUnseen(sym)
if (isClass) {
ctx.log(s"$templateStart indexing params (may be empty):")
val localCtx = ctx.withOwner(sym)
forkAt(templateStart).indexTemplateParams()(localCtx)
}
ctx.adjustAnnotations(sym, annotations)
}
goto(start)
sym
}
/** Read modifier list into triplet of flags, annotations and a privateWithin
* boundary symbol.
*/
def readModifiers[WithinType]
(end: Addr, readAnnot: Context => DeferredAnnotation, readWithin: Context => WithinType, defaultWithin: WithinType)
(implicit ctx: Context): (TastyFlagSet, List[DeferredAnnotation], WithinType) = {
var flags = EmptyTastyFlags
var annotFns: List[DeferredAnnotation] = Nil
var privateWithin = defaultWithin
while (currentAddr.index != end.index) {
def addFlag(flag: TastyFlagSet) = {
flags |= flag
readByte()
}
nextByte match {
case PRIVATE => addFlag(Private)
case PROTECTED => addFlag(Protected)
case ABSTRACT =>
readByte()
nextByte match {
case OVERRIDE => addFlag(AbsOverride)
case _ => flags |= Abstract
}
case FINAL => addFlag(Final)
case SEALED => addFlag(Sealed)
case CASE => addFlag(Case)
case IMPLICIT => addFlag(Implicit)
case ERASED => addFlag(Erased)
case LAZY => addFlag(Lazy)
case OVERRIDE => addFlag(Override)
case INLINE => addFlag(Inline)
case INLINEPROXY => addFlag(InlineProxy)
case MACRO => addFlag(Macro)
case OPAQUE => addFlag(Opaque)
case STATIC => addFlag(Static)
case OBJECT => addFlag(Object)
case TRAIT => addFlag(Trait)
case TRANSPARENT => addFlag(Transparent)
case INFIX => addFlag(Infix)
case ENUM => addFlag(Enum)
case LOCAL => addFlag(Local)
case SYNTHETIC => addFlag(Synthetic)
case ARTIFACT => addFlag(Artifact)
case MUTABLE => addFlag(Mutable)
case FIELDaccessor => addFlag(FieldAccessor)
case CASEaccessor => addFlag(CaseAccessor)
case COVARIANT => addFlag(Covariant)
case CONTRAVARIANT => addFlag(Contravariant)
case HASDEFAULT => addFlag(HasDefault)
case STABLE => addFlag(Stable)
case EXTENSION => addFlag(Extension)
case GIVEN => addFlag(Given)
case PARAMsetter => addFlag(ParamSetter)
case PARAMalias => addFlag(ParamAlias)
case EXPORTED => addFlag(Exported)
case OPEN => addFlag(Open)
case INVISIBLE => addFlag(Invisible)
case TRACKED => addFlag(Tracked)
case PRIVATEqualified =>
readByte()
privateWithin = readWithin(ctx)
case PROTECTEDqualified =>
addFlag(Protected)
privateWithin = readWithin(ctx)
case ANNOTATION =>
annotFns = readAnnot(ctx) :: annotFns
case tag =>
assert(false, s"illegal modifier tag ${astTagToString(tag)} at $currentAddr, end = $end")
}
}
(flags, if (ctx.ignoreAnnotations) Nil else annotFns.reverse, privateWithin)
}
private val readTypedWithin: Context => Symbol = implicit ctx => readType().typeSymbolDirect
private val readTypedAnnot: Context => DeferredAnnotation = { implicit ctx =>
val annotCtx = ctx.addMode(ReadAnnotTopLevel)
val start = currentAddr
readByte() // tag
val end = readEnd()
val annotSym = readType()(annotCtx).typeSymbolDirect
val annotStart = currentAddr
ctx.log(s"$annotStart collected annotation ${showSym(annotSym)}, starting at $start, ending at $end")
val mkTree = readLaterWithOwner(end, rdr => ctx =>
ctx.trace(traceAnnotation(annotStart, annotSym, ctx.owner)(ctx)) {
rdr.readTerm()(ctx)
}
)(annotCtx.retractMode(IndexScopedStats))
DeferredAnnotation.fromTree(annotSym)(mkTree)
}
private def traceAnnotation(annotStart: Addr, annotSym: Symbol, annotee: Symbol)(implicit ctx: Context) = TraceInfo[Tree](
query = s"reading annotation tree",
qual = s"${showSym(annotSym)} at $annotStart",
res = atree => s"annotation of ${showSym(annotee)} = ${showTree(atree)}"
)
/** Create symbols for the definitions in the statement sequence between
* current address and `end`.
*/
def indexStats(end: Addr)(implicit ctx: Context): Unit = {
while (currentAddr.index < end.index) {
nextByte match {
case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM =>
symbolAtCurrent()
skipTree()
case IMPORT | EXPORT =>
skipTree()
case PACKAGE =>
processPackage(end => implicit ctx => indexStats(end))
case _ =>
skipTree()
}
}
assert(currentAddr.index === end.index)
}
/** Process package with given operation `op`. The operation takes as arguments
* - an end address,
* - a context which has the processed package as owner
*/
def processPackage[T](op: Addr => Context => T)(implicit ctx: Context): T = {
readByte() // tag
val end = readEnd()
val tpe = readType()
op(end)(ctx.withOwner(tpe.typeSymbolDirect.objectImplementation))
}
/** Create symbols the longest consecutive sequence of parameters with given
* `tag` starting at current address.
*/
def indexParams(tag: Int)(implicit ctx: Context): Unit = {
while (nextByte === tag) {
symbolAtCurrent()
skipTree()
}
}
/** Create symbols for all type and value parameters of template starting
* at current address.
*/
def indexTemplateParams()(implicit ctx: Context): Unit = {
assert(readByte() === TEMPLATE)
readEnd()
indexParams(TYPEPARAM)
indexParams(PARAM)
}
def readIndexedMember()(implicit ctx: Context): NoCycle = cycleAtAddr.remove(currentAddr) match {
case Some(maybeCycle) =>
assert(maybeCycle ne Tombstone, s"Cyclic reference while unpickling definition at address ${currentAddr.index} in file ${ctx.source}")
skipTree()
maybeCycle.asInstanceOf[NoCycle]
case _ =>
val start = currentAddr
cycleAtAddr(start) = Tombstone
val noCycle = initializeMember()
cycleAtAddr.remove(start)
noCycle
}
private def initializeMember()(implicit ctx: Context): NoCycle = {
val symAddr = currentAddr
val tag = readByte()
val end = readEnd()
val tname = readTastyName()
val sym = symAtAddr(symAddr)
def readParamss()(implicit ctx: Context): List[List[NoCycle]] = {
def readRest() = {
if (nextByte == SPLITCLAUSE) readByte()
readParamss()
}
nextByte match {
case PARAM => readParams[NoCycle](PARAM) :: readRest()
case TYPEPARAM => readParams[NoCycle](TYPEPARAM) :: readRest()
case EMPTYCLAUSE => readByte(); Nil :: readRest()
case _ => Nil
}
}
def checkUnsupportedFlags(unsupported: TastyFlagSet)(implicit ctx: Context): Unit = {
unsupportedWhen(unsupported.hasFlags, s"${showTasty(unsupported)} ${sym.kindString} $tname")
}
def DefDef(repr: TastyRepr, localCtx: Context)(implicit ctx: Context): Unit = {
val isMacro = repr.tflags.is(Erased | Macro)
val supportedFlags = Extension | Exported | Infix | Given | optFlag(isMacro)(Erased)
checkUnsupportedFlags(repr.unsupportedFlags &~ supportedFlags)
val isCtor = sym.isConstructor
val paramss = readParamss()(localCtx)
val typeClause = {
// A type parameter list must be non-empty and with type symbols
val first = paramss.take(1)
if (first.exists(_.headOption.exists(nc => symFromNoCycle(nc).isType))) first.head else Nil
}
val valueClauses = paramss.drop(if (typeClause.isEmpty) 0 else 1)
val typeParams = typeClause.map(symFromNoCycle)
val vparamss = {
val vparamSymss = valueClauses.map(_.map(symFromNoCycle))
// A value parameter list may be empty, or filled with term symbols
val hasTypeParams = vparamSymss.exists(_.headOption.exists(_.isType))
unsupportedWhen(hasTypeParams, {
val noun = (
if (isCtor) "constructor"
else if (repr.unsupportedFlags.is(Extension)) "extension method"
else "method"
)
s"$noun with unmergeable type parameters: $tname"
})
vparamSymss
}
val tpt = readTpt()(localCtx)
if (isMacro) {
val impl = tpd.Macro(readTerm()(ctx.addMode(ReadMacro)))
val annot = symbolTable.AnnotationInfo(
atp = symbolTable.definitions.MacroImplLocationAnnotation.tpe,
args = List(impl),
assocs = Nil
)
sym.addAnnotation(annot)
}
val valueParamss = normalizeIfConstructor(sym.enclClass, vparamss, valueClauses, isCtor)
val resType = effectiveResultType(sym, tpt.tpe)
ctx.setInfo(sym, defn.DefDefType(if (isCtor) Nil else typeParams, valueParamss, resType))
}
def ValDef(repr: TastyRepr, localCtx: Context)(implicit ctx: Context): Unit = {
// valdef in TASTy is either a singleton object or a method forwarder to a local value.
checkUnsupportedFlags(repr.unsupportedFlags &~ (Enum | Extension | Exported | Given))
val tpe = readTpt()(localCtx).tpe
ctx.setInfo(sym,
if (repr.tflags.is(FlagSets.SingletonEnum)) {
ctx.completeEnumSingleton(sym, tpe)
defn.NamedType(sym.owner.thisPrefix, sym.objectImplementation)
}
else if (ctx.isJava && repr.tflags.is(FlagSets.JavaEnumCase)) defn.ConstantType(tpd.Constant(sym))
else if (!ctx.isJava && sym.isFinal && isConstantType(tpe)) defn.InlineExprType(tpe)
else if (sym.isMethod) defn.ExprType(tpe)
else tpe
)
}
def TypeDef(repr: TastyRepr, localCtx: Context)(implicit ctx: Context): Unit = {
val allowedShared = Enum | Opaque | Infix | Given
val allowedTypeFlags = allowedShared | Exported
val allowedClassFlags = allowedShared | Open | Transparent | Tracked
if (sym.isClass) {
checkUnsupportedFlags(repr.unsupportedFlags &~ allowedClassFlags)
sym.owner.ensureCompleted(CompleteOwner)
readTemplate()(localCtx)
}
else {
checkUnsupportedFlags(repr.unsupportedFlags &~ allowedTypeFlags)
sym.info = defn.InitialTypeInfo // needed to avoid cyclic references when unpickling rhs, see dotty_i3816.scala
val rhs = readTpt()(if (repr.tflags.is(Opaque)) localCtx.addMode(OpaqueTypeDef) else localCtx)
val info =
if (repr.tflags.is(Opaque)) {
val (info, alias) = defn.OpaqueTypeToBounds(rhs.tpe)
ctx.markAsOpaqueType(sym, alias)
info
}
else rhs.tpe
ctx.setInfo(sym, defn.NormalisedBounds(info, sym))
if (sym.is(Param)) sym.reset(Private | Protected)
}
}
def TermParam(repr: TastyRepr, localCtx: Context)(implicit ctx: Context): Unit = {
checkUnsupportedFlags(repr.unsupportedFlags &~ (ParamAlias | Exported | Given | Tracked))
val tpt = readTpt()(localCtx)
ctx.setInfo(sym,
if (nothingButMods(end) && sym.not(ParamSetter)) tpt.tpe
else defn.ExprType(tpt.tpe))
}
def initialize(localCtx: Context)(implicit ctx: Context) = ctx.trace(traceCompletion(symAddr, sym)) {
sym.rawInfo match {
case repr: TastyRepr =>
tag match {
case DEFDEF => DefDef(repr, localCtx)
case VALDEF => ValDef(repr, localCtx)
case TYPEDEF | TYPEPARAM => TypeDef(repr, localCtx)
case PARAM => TermParam(repr, localCtx)
}
repr.tflags
case _ => // nothing to do here (assume correctly initalised)
ctx.log(s"${showSym(sym)} is already initialised, in owner ${showSym(sym.owner)}")
EmptyTastyFlags
}
}
try {
val localCtx = ctx.withOwner(sym)
val tflags = {
if (sym.isClass) {
inIndexScopedStatsContext(localCtx0 => initialize(localCtx0)(ctx))(localCtx)
}
else {
initialize(localCtx)
}
}
NoCycle(at = symAddr, tflags)
}
catch ctx.onCompletionError(sym)
finally goto(end)
}
private def traceCompletion(addr: Addr, sym: Symbol)(implicit ctx: Context) = TraceInfo[TastyFlagSet](
query = "begin completion",
qual = s"${showSym(sym)} in context ${showSym(ctx.owner)} $addr",
res = _ => s"completed ${showSym(sym)}: ${showType(sym.info)}"
)
private def readTemplate()(implicit ctx: Context): Unit = {
val start = currentAddr
val cls = ctx.enterClassCompletion()
val localDummy = symbolAtCurrent()
assert(readByte() === TEMPLATE)
val end = readEnd()
def traceCompleteParams = TraceInfo[List[Symbol]](
query = "force template parameters",
qual = s"${showSym(cls)} $currentAddr",
res = _ => "forced template parameters"
)
def traceIndexMembers = TraceInfo[Unit](
query = "index template body",
qual = s"${showSym(cls)} $currentAddr",
res = _ => "indexed template body"
)
def traceCollectParents = TraceInfo[List[Type]](
query = "collect template parents",
qual = s"${showSym(cls)} $currentAddr",
res = { parentTypes =>
val addendum = parentTypes.map(lzyShow).mkString(s"`${cls.fullName} extends ", " with ", "`")
s"collected template parents $addendum"
}
)
def traceReadSelf = TraceInfo[Type](
query = "reading template self-type",
qual = s"${showSym(cls)} $currentAddr",
res = tpe => s"template self-type is $tpe"
)
def completeParameters()(implicit ctx: Context): List[Symbol] = ctx.trace(traceCompleteParams) {
val tparams = readIndexedParams[NoCycle](TYPEPARAM).map(symFromNoCycle)
if (tparams.nonEmpty) {
cls.info = defn.PolyType(tparams, cls.info)
}
readIndexedParams[NoCycle](PARAM) // skip value parameters
tparams
}
def indexMembers()(implicit ctx: Context): Unit = ctx.trace(traceIndexMembers) {
val bodyIndexer = fork
while ({val tag = bodyIndexer.reader.nextByte; tag != DEFDEF && tag != SPLITCLAUSE})
bodyIndexer.skipParentTree() // skip until primary ctor
bodyIndexer.indexStats(end)
}
def collectParents()(implicit ctx: Context): List[Type] = ctx.trace(traceCollectParents) {
val parentCtx = ctx.withOwner(localDummy).addMode(ReadParents)
val parentWithOuter = parentCtx.addMode(OuterTerm)
collectWhile({val tag = nextByte; tag != SELFDEF && tag != DEFDEF && tag != SPLITCLAUSE}) {
defn.adjustParent(
nextUnsharedTag match {
case APPLY | TYPEAPPLY | BLOCK => readTerm()(parentWithOuter).tpe
case _ => readTpt()(parentCtx).tpe
}
)
}
}
def addSelfDef()(implicit ctx: Context): Unit = {
val selfTpe = ctx.trace(traceReadSelf) {
readByte() // read SELFDEF tag
readLongNat() // skip Name
readTpt().tpe
}
cls.typeOfThis = selfTpe
}
def setInfoWithParents(tparams: List[Symbol], parentTypes: List[Type])(implicit ctx: Context): Unit = {
val info = {
val classInfo = defn.ClassInfoType(parentTypes, cls)
// TODO [tasty]: if support opaque types, refine the self type with any opaque members here
if (tparams.isEmpty) classInfo
else defn.PolyType(tparams, classInfo)
}
ctx.setInfo(cls, info)
}
def traverseTemplate()(implicit ctx: Context): Unit = {
val tparams = completeParameters()
indexMembers()
val parents = collectParents()
if (nextByte === SELFDEF) {
addSelfDef()
}
if (nextByte === SPLITCLAUSE) {
assert(ctx.isJava, s"unexpected SPLITCLAUSE at $start")
}
setInfoWithParents(tparams, ctx.processParents(cls, parents))
}
traverseTemplate()
}
def isTopLevel: Boolean = nextByte === IMPORT || nextByte === PACKAGE
def readIndexedStatAsSym(@unused exprOwner: Symbol)(implicit ctx: Context): NoCycle = nextByte match {
case TYPEDEF | VALDEF | DEFDEF =>
readIndexedMember()
case IMPORT =>
unsupportedTermTreeError("import statement")
case EXPORT =>
unsupportedTermTreeError("export statement")
case PACKAGE =>
unsupportedTermTreeError("package statement")
case _ =>
skipTree() // readTerm()(ctx.withOwner(exprOwner))
NoCycle(at = NoAddr, tflags = EmptyTastyFlags)
}
def readIndexedStatsAsSyms(exprOwner: Symbol, end: Addr)(implicit ctx: Context): List[NoCycle] =
until(end)(readIndexedStatAsSym(exprOwner))
def readStatsAsSyms(exprOwner: Symbol, end: Addr)(implicit ctx: Context): List[NoCycle] = {
def forkAndIndexStats(implicit ctx: Context): Unit = fork.indexStats(end)
inIndexStatsContext(forkAndIndexStats(_))
readIndexedStatsAsSyms(exprOwner, end)
}
def readIndexedParams[T <: MaybeCycle /*MemberDef*/](tag: Int)(implicit ctx: Context): List[T] =
collectWhile(nextByte === tag) { readIndexedMember().asInstanceOf[T] }
def readParams[T <: MaybeCycle /*MemberDef*/](tag: Int)(implicit ctx: Context): List[T] = {
if (nextByte == tag) {
fork.indexParams(tag)
readIndexedParams(tag)
}
else {
Nil
}
}
// ------ Reading trees -----------------------------------------------------
def readTerm()(implicit ctx: Context): Tree = {
val start = currentAddr
val tag = readByte()
def traceReadTerm = TraceInfo[Tree](
query = "reading term",
qual = s"${astTagToString(tag)} $start",
res = tree => s"exit term (`${showTree(tree)}`: ${showType(tree.tpe)}) ${astTagToString(tag)} $start"
)
def inParentCtor = ctx.mode.is(ReadParents | OuterTerm)
def readPathTerm(): Tree = {
goto(start)
tpd.PathTree(readType())
}
def readQualId(): (TastyName.TypeName, Type) = {
val qual = readTerm()
(qual.typeIdent, defn.ThisType(qual.tpe))
}
def completeSelectType(name: TastyName.TypeName)(implicit ctx: Context): Tree =
completeSelect(name)
def completeSelect(name: TastyName)(implicit ctx: Context): Tree =
tpd.Select(readTerm(), name)
def completeSelectionParent(name: TastyName)(implicit ctx: Context): Tree = {
assert(name.isSignedConstructor, s"Parent of ${ctx.owner} is not a constructor.")
readTerm() // just need the type of the parent
}
def readSimpleTerm(): Tree = tag match {
case SHAREDterm => forkAt(readAddr()).readTerm()
case IDENT => tpd.Ident(readTastyName())(readType())
case IDENTtpt => tpd.Ident(readTastyName().toTypeName)(readType())
case SELECT =>
if (inParentCtor) completeSelectionParent(readTastyName())
else completeSelect(readTastyName())
case SELECTtpt => completeSelectType(readTastyName().toTypeName)
case QUALTHIS =>
val (qual, tref) = readQualId()
tpd.This(qual)(tref)
case NEW => tpd.New(readTpt())
case SINGLETONtpt => tpd.SingletonTypeTree(readTerm())
case BYNAMEtpt => tpd.ByNameTypeTree(readTpt())
case NAMEDARG => tpd.NamedArg(readTastyName(), readTerm())
case THROW => unsupportedTermTreeError("throw clause")
case _ => readPathTerm()
}
def readLengthTerm(): Tree = {
val end = readEnd()
val result =
(tag: @switch) match {
case SELECTin =>
val name = readTastyName()
val qual = readTerm()
if (inParentCtor) {
assert(name.isSignedConstructor, s"Parent of ${ctx.owner} is not a constructor.")
skipTree()
qual
}
else {
tpd.Select(readType())(qual, name)
}
case SUPER =>
val qual = readTerm()
val (mixId, mixTpe) = ifBefore(end)(readQualId(), (TastyName.EmptyTpe, defn.NoType))
tpd.Super(qual, mixId)(mixTpe)
case APPLY =>
val fn = readTerm()
if (inParentCtor) {
until(end)(skipTree())
tpd.TypeTree(fnResult(fn.tpe))
} else {
val argsCtx = ctx.argumentCtx(fn)
tpd.Apply(fn, until(end)(readTerm()(argsCtx)))
}
case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt()))
case APPLYsigpoly =>
// this is skipped if it appears in parents, so only affects forced annotation trees
signaturePolymorphicIsUnsupported
case TYPED => tpd.Typed(readTerm(), readTpt())
case IF =>
if (nextByte === INLINE) unsupportedTermTreeError("inline conditional expression")
else tpd.If(readTerm(), readTerm(), readTerm()) // if is ok if its parts are made of constants/paths
case REPEATED =>
val elemtpt = readTpt()
tpd.SeqLiteral(until(end)(readTerm()), elemtpt)
case REFINEDtpt =>
val refineCls = symAtAddr.getOrElse(start, ctx.newRefinementClassSymbol)
registerSym(start, refineCls, rejected = false)
typeAtAddr(start) = refineCls.ref
val parent = readTpt()
ctx.withOwner(refineCls).enterRefinement(parent.tpe) { refinedCtx =>
readStatsAsSyms(refineCls, end)(refinedCtx)
tpd.RefinedTypeTree(parent, Nil, refineCls)
}
case APPLIEDtpt =>
// If we do directly a tpd.AppliedType tree we might get a
// wrong number of arguments in some scenarios reading F-bounded
// types. This came up in #137 of collection strawman.
tpd.AppliedTypeTree(readTpt(), until(end)(readTpt()))
case ANNOTATEDtpt => tpd.Annotated(readTpt(), readTerm()(ctx.addMode(ReadAnnotTopLevel)))
case LAMBDAtpt => tpd.LambdaTypeTree(readParams[NoCycle](TYPEPARAM).map(symFromNoCycle), readTpt())
case MATCHtpt => matchTypeIsUnsupported
case TYPEBOUNDStpt =>
val lo = readTpt()
val hi = if (currentAddr == end) lo else readTpt()
val alias = {
if (currentAddr == end) {
untpd.EmptyTree
}
else {
assert(ctx.mode.is(OpaqueTypeDef))
readTpt()(ctx.retractMode(OpaqueTypeDef))
}
}
tpd.TypeBoundsTree(lo, hi, alias)
case BLOCK =>
if (inParentCtor | ctx.mode.is(ReadMacro)) {
val exprReader = fork
skipTree()
until(end)(skipTree()) //val stats = readStats(ctx.owner, end)
exprReader.readTerm()
}
else unsupportedTermTreeError("block expression")
case ASSIGN => unsupportedTermTreeError("assignment expression")
case LAMBDA => unsupportedTermTreeError("anonymous function literal")
case MATCH => unsupportedTermTreeError("match expression")
case RETURN => unsupportedTermTreeError("return statement")
case WHILE => unsupportedTermTreeError("loop statement")
case TRY => unsupportedTermTreeError("try expression")
case BIND => unsupportedTermTreeError("bind pattern")
case ALTERNATIVE => unsupportedTermTreeError("pattern alternative")
case UNAPPLY => unsupportedTermTreeError("unapply pattern")
case INLINED => unsupportedTermTreeError("inlined expression")
case SELECTouter => metaprogrammingIsUnsupported // only within inline
case QUOTE => abortQuote
case SPLICE => abortSplice
case QUOTEPATTERN => abortQuotePattern
case SPLICEPATTERN => abortSplicePattern
case HOLE => abortMacroHole
case _ => readPathTerm()
}
assert(currentAddr === end, s"$start $currentAddr $end ${astTagToString(tag)}")
result
}
ctx.traceV(traceReadTerm) {
if (tag < firstLengthTreeTag) readSimpleTerm() else readLengthTerm() // dotty sets span of tree to start
}
}
def readTpt()(implicit ctx: Context): Tree = {
val tpt: Tree = nextByte match {
case SHAREDterm =>
readByte()
forkAt(readAddr()).readTpt()
case BLOCK => // BLOCK appears in type position when quoting a type, but only in the body of a method
metaprogrammingIsUnsupported
case HOLE => abortMacroHole
case tag =>
if (isTypeTreeTag(tag)) readTerm()(ctx.retractMode(OuterTerm))
else {
val tp = readType()
if (isTypeType(tp)) tpd.TypeTree(tp) else untpd.EmptyTree
}
}
tpt
}
/**
* A HOLE should never appear in TASTy for a top level class, only in quotes.
*/
private def abortMacroHole[T]: T = abortWith(msg = "Scala 3 macro hole in pickled TASTy")
private def abortQuote[T]: T = abortWith(msg = "Scala 3 quoted expression in pickled TASTy")
private def abortSplice[T]: T = abortWith(msg = "Scala 3 quoted splice in pickled TASTy")
private def abortQuotePattern[T]: T = abortWith(msg = "Scala 3 quoted pattern in pickled TASTy")
private def abortSplicePattern[T]: T = abortWith(msg = "Scala 3 quoted pattern splice in pickled TASTy")
private def signaturePolymorphicIsUnsupported[T](implicit ctx: Context): T =
unsupportedTermTreeError("signature polymorphic application")
private def metaprogrammingIsUnsupported[T](implicit ctx: Context): T =
unsupportedError("Scala 3 metaprogramming features")
def readLaterWithOwner[T <: AnyRef](end: Addr, op: TreeReader => Context => T)(implicit ctx: Context): Symbol => Context => T = {
val localReader = fork
goto(end)
owner => ctx0 => readWith(localReader, owner, ctx.mode, ctx.source, op)(ctx0)
}
}
def readWith[T <: AnyRef](
treader: TreeReader,
owner: Symbol,
mode: TastyMode,
source: AbstractFile,
op: TreeReader => Context => T)(
implicit ctx: Context
): T = ctx.trace[T](traceReadWith(treader, mode, owner)) {
ctx.withPhaseNoLater("pickler") { ctx0 =>
op(treader)(ctx0
.withOwner(owner)
.withMode(mode)
.withSource(source)
)
}
}
private def traceReadWith[T](treader: TreeReader, mode: TastyMode, owner: Symbol) = TraceInfo[T](
query = "read within owner",
qual = s"${showSym(owner)} with modes `${mode.debug}` at ${treader.reader.currentAddr}",
res = _ => s"exiting sub reader"
)
/** A lazy datastructure that records how definitions are nested in TASTY data.
* The structure is lazy because it needs to be computed only for forward references
* to symbols that happen before the referenced symbol is created (see `symbolAt`).
* Such forward references are rare.
*
* @param addr The address of tree representing an owning definition, NoAddr for root tree
* @param tag The tag at `addr`. Used to determine which subtrees to scan for children
* (i.e. if `tag` is template, don't scan member defs, as these belong already
* to enclosing class).
* @param reader The reader to be used for scanning for children
* @param end The end of the owning definition
*/
class OwnerTree(val addr: Addr, tag: Int, reader: TreeReader, val end: Addr) {
private var myChildren: List[OwnerTree] = _
/** All definitions that have the definition at `addr` as closest enclosing definition */
def children: List[OwnerTree] = {
if (myChildren === null) myChildren = {
val buf = new mutable.ListBuffer[OwnerTree]
reader.scanTrees(buf, end, if (tag === TEMPLATE) NoMemberDefs else AllDefs)
buf.toList
}
myChildren
}
/** Find the owner of definition at `addr` */
def findOwner(addr: Addr)(implicit ctx: Context): Symbol = {
def search(cs: List[OwnerTree], current: Symbol): Symbol =
try cs match {
case ot :: cs1 =>
if (ot.addr.index === addr.index) {
assert(isSymbol(current), s"no symbol at $addr")
current
}
else if (ot.addr.index < addr.index && addr.index < ot.end.index)
search(ot.children, reader.symbolAt(ot.addr))
else
search(cs1, current)
case Nil =>
throw new TreeWithoutOwner
}
catch {
case ex: TreeWithoutOwner =>
ctx.log(s"no owner for $addr among $cs%, %") // pickling.println
throw ex
}
try search(children, noSymbol).tap(owner => ctx.log(s"$addr within owner ${showSym(owner)} do:"))
catch {
case ex: TreeWithoutOwner =>
ctx.log(s"ownerTree = $ownerTree") // pickling.println
throw ex
}
}
override def toString: String =
s"OwnerTree(${addr.index}, ${end.index}, ${if (myChildren === null) "?" else myChildren.mkString(" ")})"
}
def symFromNoCycle(noCycle: NoCycle): Symbol = symAtAddr(noCycle.at)
}
object TreeUnpickler {
sealed trait MaybeCycle
object MaybeCycle {
case class NoCycle(at: Addr, tflags: TastyFlagSet) extends MaybeCycle
case object Tombstone extends MaybeCycle
}
/** An enumeration indicating which subtrees should be added to an OwnerTree. */
type MemberDefMode = Int
final val MemberDefsOnly = 0 // add only member defs; skip other statements
final val NoMemberDefs = 1 // add only statements that are not member defs
final val AllDefs = 2 // add everything
class TreeWithoutOwner extends Exception
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy