scala.tools.nsc.tasty.TreeUnpickler.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the Scala Programming Language
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}, TastyRefs._, TastyFlags._, TastyFormat._
import scala.annotation.switch
import scala.collection.mutable
import scala.reflect.io.AbstractFile
import scala.reflect.internal.Variance
import scala.util.chaining._
/**[[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 FlagSets._
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)(implicit ctx: Context) = {
ctx.log(s"$addr registered ${showSym(sym)} in ${location(sym.owner)}")
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)
inIndexStatsContext(indexTopLevel(_))
}
/** 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, originalFlagSet: TastyFlagSet)(implicit ctx: Context)
extends TastyCompleter(isClass, originalFlagSet) { self =>
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 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 == PARAMEND
}) skipTree()
def skipTypeParams(): Unit =
while (nextByte === TYPEPARAM) 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 (i <- 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 (i <- 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): (List[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, mods)
}
/** Read `n` parameter types or bounds which are interleaved with names */
def readParamTypes[T <: Type](n: Int)(implicit ctx: Context): List[T] = {
if (n == 0) Nil
else {
val t = readType().asInstanceOf[T]
readNat() // skip name
t :: readParamTypes(n - 1)
}
}
/** 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.log(s"<<< No symbol found at forward reference $addr, ensuring one exists:")
val ctxAtOwner = ctx.withOwner(ownerTree.findOwner(addr))
val sym = forkAt(addr).createSymbol()(ctxAtOwner)
ctx.log(s">>> $addr forward reference to ${showSym(sym)}")
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.log(s"$currentAddr No symbol found at current address, ensuring one exists:")
createSymbol()
}
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())
case ENUMconst =>
tpd.Constant(readTypeRef().termSymbol)
}
/** Read a type */
def readType()(implicit ctx: Context): Type = {
val start = currentAddr
val tag = readByte()
ctx.log(s"$start reading type ${astTagToString(tag)}:")
def registeringTypeWith[T](tp: Type, op: => T): T = {
typeAtAddr(start) = tp
op
}
def readLengthType(): Type = {
val end = readEnd()
def readMethodic[N <: TastyName]
(companionOp: TastyFlagSet => LambdaTypeCompanion[N], nameMap: TastyName => N)(implicit ctx: Context): Type = {
val result = typeAtAddr.getOrElse(start, {
val nameReader = fork
nameReader.skipTree() // skip result
val paramReader = nameReader.fork
val (paramNames, mods) = nameReader.readParamNamesAndMods(end)
companionOp(mods)(paramNames.map(nameMap))(
pt => typeAtAddr(start) = pt,
() => paramReader.readParamTypes(paramNames.length),
() => readType()
).tap(typeAtAddr(start) = _)
})
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 => selectTerm(readTastyName(), readType(), readType())
case TYPEREFin => selectType(readTastyName().toTypeName, readType(), 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))
typeRef(readVariances(lo))
else defn.TypeBounds(lo, readVariances(readType()))
case ANNOTATEDtype => defn.AnnotatedType(readType(), readTerm()(ctx.addMode(ReadAnnotation)))
case ANDtype => defn.IntersectionType(readType(), readType())
case ORtype => unionIsUnsupported
case SUPERtype => defn.SuperType(readType(), readType())
case MATCHtype => matchTypeIsUnsupported
case POLYtype => readMethodic(Function.const(PolyType), _.toTypeName)
case METHODtype =>
def companion(mods0: TastyFlagSet) = {
var mods = EmptyTastyFlags
if (mods0.is(Erased)) erasedRefinementIsUnsupported[Unit]
if (mods0.isOneOf(Given | Implicit)) mods |= Implicit
methodTypeCompanion(mods)
}
readMethodic(companion, id)
case TYPELAMBDAtype => readMethodic(Function.const(HKTypeLambda), _.toTypeName)
case PARAMtype => // reference to a type parameter within a LambdaType
readTypeRef().typeParams(readNat()).ref
}
assert(currentAddr === end, s"$start $currentAddr $end ${astTagToString(tag)}")
result
}
def readSimpleType(): Type = {
(tag: @switch) match {
case TYPEREFdirect => readSymRef().termRef
case TERMREFdirect => readSymRef().singleRef
case TYPEREFsymbol | TERMREFsymbol => readSymNameRef()
case TYPEREFpkg => readPackageRef().objectImplementation.ref
case TERMREFpkg => readPackageRef().termRef
case TYPEREF => selectType(readTastyName().toTypeName, readType())
case TERMREF => selectTerm(readTastyName(), readType())
case THIS => defn.ThisType(singletonLike(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 => recThis(readTypeRef())
case SHAREDtype =>
val ref = readAddr()
typeAtAddr.getOrElseUpdate(ref, forkAt(ref).readType())
case BYNAMEtype => defn.ByNameType(readType())
case _ => defn.ConstantType(readConstant(tag))
}
}
if (tag < firstLengthTreeTag) readSimpleType() else readLengthType()
}
private def readSymNameRef()(implicit ctx: Context): Type = {
val sym = readSymRef()
val prefix = readType()
prefixedRef(prefix, sym)
}
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 normalizeFlags(tag: Int, tastyFlags: TastyFlagSet, name: TastyName, isAbsType: Boolean, isClass: Boolean, rhsIsEmpty: Boolean)(implicit ctx: Context): TastyFlagSet = {
var flags = tastyFlags
val lacksDefinition =
rhsIsEmpty &&
name.isTermName && !name.isConstructorName && !flags.isOneOf(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 (tag === VALDEF) {
if (flags.is(Inline) || ctx.owner.is(Trait)) flags |= FieldAccessor
if (flags.not(Mutable)) flags |= Stable
if (flags.is(SingletonEnumFlags)) flags |= Object // we will encode dotty enum constants as objects (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) ObjectCreationFlags else ObjectClassCreationFlags)
flags
}
def isAbstractType(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)
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 = {
val start = currentAddr
val tag = readByte()
def isTypeTag = tag === TYPEDEF || tag === TYPEPARAM
val end = readEnd()
val parsedName: TastyName = readTastyName()
ctx.log(s"$start ::: => create ${astTagToString(tag)} ${parsedName.debug}")
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 (name, flags, annotations, privateWithin) = {
val (parsedFlags, annotations, privateWithin) =
readModifiers(end, readTypedAnnot, readTypedWithin, noSymbol)
val name = normalizeName(isTypeTag, parsedName)
val flags = normalizeFlags(tag, parsedFlags, name, isAbsType, isClass, rhsIsEmpty)
(name, flags, annotations, privateWithin)
}
def isTypeParameter = flags.is(Param) && isTypeTag
def canEnterInClass = !isTypeParameter
ctx.log {
val privateFlag = if (isSymbol(privateWithin)) s"private[$privateWithin] " else ""
val debugFlags = {
if (privateFlag.nonEmpty) {
val given = if (!flags) "" else " " + (flags &~ Private).debug
privateFlag + given
}
else flags.debug
}
s"""$start parsed flags $debugFlags"""
}
val sym = {
if (tag === TYPEPARAM && ctx.owner.isConstructor) {
ctx.findOuterClassTypeParameter(name.toTypeName)
}
else {
val completer = new Completer(isClass, subReader(start, end), flags)(ctx.retractMode(IndexScopedStats))
ctx.findRootSymbol(roots, name) match {
case Some(rootd) =>
ctx.adjustSymbol(rootd, flags, completer, privateWithin) // dotty "removes one completion" here from the flags, which is not possible in nsc
ctx.log(s"$start replaced info of ${showSym(rootd)}")
rootd
case _ =>
if (isClass) ctx.delayClassCompletion(ctx.owner, name.toTypeName, completer, privateWithin)
else ctx.delayCompletion(ctx.owner, name, completer, privateWithin)
}
}
}.ensuring(isSymbol(_), s"${ctx.classRoot}: Could not create symbol at $start")
if (tag == VALDEF && flags.is(SingletonEnumFlags))
ctx.markAsEnumSingleton(sym)
registerSym(start, sym)
if (canEnterInClass && ctx.owner.isClass)
ctx.enterIfUnseen(sym)
if (isClass) {
val localCtx = ctx.withOwner(sym)
forkAt(templateStart).indexTemplateParams()(localCtx)
}
goto(start)
ctx.adjustAnnotations(sym, annotations)
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 INTERNAL => addFlag(Internal)
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 SUPERTRAIT => addFlag(SuperTrait)
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(Implicit)
case PARAMsetter => addFlag(ParamSetter)
case PARAMalias => addFlag(ParamAlias)
case EXPORTED => addFlag(Exported)
case OPEN => addFlag(Open)
case PRIVATEqualified =>
readByte()
privateWithin = readWithin(ctx)
case PROTECTEDqualified =>
addFlag(Protected)
privateWithin = readWithin(ctx)
case ANNOTATION =>
annotFns = readAnnot(ctx) :: annotFns
case tag =>
assert(assertion = 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(ReadAnnotation)
val start = currentAddr
ctx.log(s"<<< $start reading annotation:")
readByte() // tag
val end = readEnd()
val annotSym = readType()(annotCtx).typeSymbolDirect
val deferred = readLaterWithOwner(end, rdr => ctx => {
ctx.log(s"${rdr.reader.currentAddr} reading LazyAnnotationRef[${annotSym.fullName}]()")
rdr.readTerm()(ctx)
})(annotCtx.retractMode(IndexScopedStats))
ctx.log(s">>> $start LazyAnnotationRef[${annotSym.fullName}]()")
new DeferredAnnotation(deferred)
}
/** 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 tag @ (VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM) =>
symbolAtCurrent()
skipTree()
case IMPORT =>
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 = readNewMember()
cycleAtAddr.remove(start)
noCycle
}
private def readNewMember()(implicit ctx: Context): NoCycle = {
val symAddr = currentAddr
val tag = readByte()
val end = readEnd()
val tname = readTastyName()
val sym = symAtAddr(symAddr)
val repr = sym.repr
ctx.log(s"$symAddr completing ${showSym(sym)} in scope ${showSym(ctx.owner)}")
def readParamss(implicit ctx: Context): List[List[NoCycle/*ValDef*/]] = nextByte match {
case PARAM | PARAMEND =>
readParams[NoCycle](PARAM) ::
(if (nextByte == PARAMEND) { readByte(); readParamss } else Nil)
case _ => Nil
}
def checkUnsupportedFlags(unsupported: TastyFlagSet)(implicit ctx: Context): Unit = {
unsupportedWhen(unsupported.hasFlags, s"${showTasty(unsupported)} ${sym.kindString} $tname")
}
try {
val localCtx = ctx.withOwner(sym)
tag match {
case DEFDEF =>
val isMacro = repr.originalFlagSet.is(Erased | Macro)
checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Extension | Exported | optFlag(isMacro)(Erased)))
val isCtor = sym.isConstructor
val typeParams = {
if (isCtor) {
skipTypeParams()
sym.owner.typeParams
}
else {
readParams[NoCycle](TYPEPARAM)(localCtx).map(symFromNoCycle)
}
}
val vparamss = readParamss(localCtx)
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(vparamss.map(_.map(symFromNoCycle)), isCtor)
val resType = effectiveResultType(sym, typeParams, tpt.tpe)
ctx.setInfo(sym, defn.DefDefType(if (isCtor) Nil else typeParams, valueParamss, resType))
case VALDEF => // valdef in TASTy is either a singleton object or a method forwarder to a local value.
checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Enum | Extension | Exported))
val tpe = readTpt()(localCtx).tpe
ctx.setInfo(sym,
if (repr.originalFlagSet.is(SingletonEnumFlags)) {
val enumClass = sym.objectImplementation
val selfTpe = defn.SingleType(sym.owner.thisPrefix, sym)
val ctor = ctx.unsafeNewSymbol(
owner = enumClass,
name = TastyName.Constructor,
flags = Method,
info = defn.DefDefType(Nil, Nil :: Nil, selfTpe)
)
enumClass.typeOfThis = selfTpe
ctx.setInfo(enumClass, defn.ClassInfoType(intersectionParts(tpe), ctor :: Nil, enumClass))
prefixedRef(sym.owner.thisPrefix, enumClass)
}
else if (sym.isFinal && isConstantType(tpe)) defn.InlineExprType(tpe)
else if (sym.isMethod) defn.ExprType(tpe)
else tpe
)
case TYPEDEF | TYPEPARAM =>
checkUnsupportedFlags(repr.tastyOnlyFlags &~ (Enum | Open | Opaque | Exported | SuperTrait))
if (sym.isClass) {
sym.owner.ensureCompleted()
readTemplate()(localCtx)
}
else {
// sym.setFlag(Provisional) // TODO [tasty]: is there an equivalent in scala 2?
val rhs = readTpt()(localCtx)
// TODO [tasty]: if opaque type alias will be supported, unwrap `type bounds with alias` to bounds and then
// refine self type of the owner to be aware of the alias.
ctx.setInfo(sym, defn.NormalisedBounds(rhs.tpe, sym))
if (sym.is(Param)) sym.reset(Private | Protected)
// if sym.isOpaqueAlias then sym.typeRef.recomputeDenot() // make sure we see the new bounds from now on
// sym.resetFlag(Provisional)
}
case PARAM =>
checkUnsupportedFlags(repr.tastyOnlyFlags &~ (ParamAlias | Exported))
val tpt = readTpt()(localCtx)
ctx.setInfo(sym,
if (nothingButMods(end) && sym.not(ParamSetter)) tpt.tpe
else defn.ExprType(tpt.tpe))
}
ctx.log(s"$symAddr @@@ ${showSym(sym)}.tpe =:= '[${if (sym.isType) sym.tpe else sym.info}]; owned by ${location(sym.owner)}")
goto(end)
NoCycle(at = symAddr)
} catch ctx.onCompletionError(sym)
}
private def readTemplate()(implicit ctx: Context): Unit = {
val cls = ctx.enterClassCompletion()
val localDummy = symbolAtCurrent()
assert(readByte() === TEMPLATE)
val end = readEnd()
def completeTypeParameters()(implicit ctx: Context): List[Symbol] = {
ctx.log(s"$currentAddr Template: reading parameters of $cls:")
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.log(s"$currentAddr Template: indexing members of $cls:")
val bodyIndexer = fork
while (bodyIndexer.reader.nextByte != DEFDEF) bodyIndexer.skipTree() // skip until primary ctor
bodyIndexer.indexStats(end)
}
def traverseParents()(implicit ctx: Context): List[Type] = {
ctx.log(s"$currentAddr Template: adding parents of $cls:")
val parentCtx = ctx.withOwner(localDummy).addMode(ReadParents)
val parentWithOuter = parentCtx.addMode(OuterTerm)
collectWhile(nextByte != SELFDEF && nextByte != DEFDEF) {
nextUnsharedTag match {
case APPLY | TYPEAPPLY | BLOCK => readTerm()(parentWithOuter).tpe
case _ => readTpt()(parentCtx).tpe
}
}
}
def addSelfDef()(implicit ctx: Context): Unit = {
ctx.log(s"$currentAddr Template: adding self-type of $cls:")
readByte() // read SELFDEF tag
readLongNat() // skip Name
val selfTpe = readTpt().tpe
ctx.log(s"$currentAddr Template: self-type is $selfTpe")
cls.typeOfThis = selfTpe
}
def setInfoWithParents(tparams: List[Symbol], parentTypes: List[Type])(implicit ctx: Context): Unit = {
def debugMsg = {
val addendum =
if (parentTypes.isEmpty) ""
else parentTypes.map(lzyShow).mkString(" extends ", " with ", "") // don't force types
s"$currentAddr Template: Updated info of $cls$addendum"
}
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)
ctx.log(debugMsg)
}
def traverseTemplate()(implicit ctx: Context): Unit = {
val tparams = completeTypeParameters()
indexMembers()
val parents = traverseParents()
if (nextByte === SELFDEF) {
addSelfDef()
}
val parentTypes = ctx.adjustParents(cls, parents)
setInfoWithParents(tparams, parentTypes)
}
inIndexScopedStatsContext(traverseTemplate()(_))
}
def isTopLevel: Boolean = nextByte === IMPORT || nextByte === PACKAGE
def readIndexedStatAsSym(exprOwner: Symbol)(implicit ctx: Context): NoCycle = nextByte match {
case TYPEDEF | VALDEF | DEFDEF =>
readIndexedMember()
case IMPORT =>
unsupportedTermTreeError("import statement")
case PACKAGE =>
unsupportedTermTreeError("package statement")
case _ =>
skipTree() // readTerm()(ctx.withOwner(exprOwner))
NoCycle(at = NoAddr)
}
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()
ctx.log(s"$start reading term ${astTagToString(tag)}:")
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(symOfTypeRef(qual.tpe)))
}
def completeSelectType(name: TastyName.TypeName)(implicit ctx: Context): Tree = completeSelect(name)
def completeSelect(name: TastyName)(implicit ctx: Context): Tree = {
val qual = readTerm()
val qualType = qual.tpe // TODO [tasty]: qual.tpe.widenIfUnstable
tpd.Select(qual, name)(namedMemberOfPrefix(qualType, 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 sname = readTastyName()
val qual = readTerm()
if (inParentCtor) {
assert(sname.isSignedConstructor, s"Parent of ${ctx.owner} is not a constructor.")
skipTree()
qual
}
else {
val owner = readType()
val qualTpe = qual.tpe // qual.tpe.widenIfUnstable
tpd.Select(qual, sname)(namedMemberOfTypeWithPrefix(qualTpe, owner, sname))
}
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 {
tpd.Apply(fn, until(end)(readTerm()))
}
case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt()))
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)
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(ReadAnnotation)))
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 readTpt()
if (alias != untpd.EmptyTree) alias // only for opaque type alias
else tpd.TypeBoundsTree(lo, hi)
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 HOLE => assertNoMacroHole
case _ => readPathTerm()
}
assert(currentAddr === end, s"$start $currentAddr $end ${astTagToString(tag)}")
result
}
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 => assertNoMacroHole
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 assertNoMacroHole[T]: T = assertError("Scala 3 macro hole in pickled TASTy")
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](
reader: TreeReader,
owner: Symbol,
mode: TastyMode,
source: AbstractFile,
op: TreeReader => Context => T)(
implicit ctx: Context
): T =
ctx.withPhaseNoLater("pickler") { ctx0 =>
ctx0.log(s"${reader.reader.currentAddr} starting to read with owner ${location(owner)}:")
op(reader)(ctx0
.withOwner(owner)
.withMode(mode)
.withSource(source)
)
}
/** 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) 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
}