Skip to content

Commit

Permalink
Delay forcing annotations in template body until parents are added to…
Browse files Browse the repository at this point in the history
… class info.

Before this, inherited members may not be visible at the point of forcing the annotation
on a definition in the template.

Also moved symbol cache to the initial context so that child contexts all pass to the same
cache.
  • Loading branch information
bishabosha committed Jul 7, 2020
1 parent 85a574e commit 94f693f
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 93 deletions.
85 changes: 51 additions & 34 deletions src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ class TreeUnpickler[Tasty <: TastyUniverse](
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)
rdr.indexStats(reader.endAddr)
inIndexingContext(indexTopLevel(_))
}

class Completer(reader: TastyReader, originalFlagSet: TastyFlagSet)(implicit ctx: Context) extends TastyLazyType(originalFlagSet) { self =>
Expand Down Expand Up @@ -604,21 +605,19 @@ class TreeUnpickler[Tasty <: TastyUniverse](
* current address and `end`.
*/
def indexStats(end: Addr)(implicit ctx: Context): Unit = {
val statsCtx = ctx.addMode(IndexStats)
while (currentAddr.index < end.index) {
nextByte match {
case tag @ (VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM) =>
symbolAtCurrent()(statsCtx)
symbolAtCurrent()
skipTree()
case IMPORT =>
skipTree()
case PACKAGE =>
processPackage(end => implicit ctx => indexStats(end))(statsCtx)
processPackage(end => implicit ctx => indexStats(end))
case _ =>
skipTree()
}
}
statsCtx.forceAnnotations()
assert(currentAddr.index === end.index)
}

Expand Down Expand Up @@ -774,23 +773,25 @@ class TreeUnpickler[Tasty <: TastyUniverse](
assert(readByte() === TEMPLATE)
val end = readEnd()

// ** PARAMETERS **
ctx.log(s"$currentAddr Template: reading parameters of $cls:")
val tparams = readIndexedParams[NoCycle](TYPEPARAM)
if (tparams.nonEmpty) {
cls.info = defn.PolyType(tparams.map(symFromNoCycle), cls.info)
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
}
readIndexedParams[NoCycle](PARAM) // skip value parameters

// ** MEMBERS **
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 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)
}

// ** PARENTS **
ctx.log(s"$currentAddr Template: adding parents of $cls:")
val parents = {
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) {
Expand All @@ -801,7 +802,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
}
}

if (nextByte === SELFDEF) {
def addSelfDef()(implicit ctx: Context): Unit = {
ctx.log(s"$currentAddr Template: adding self-type of $cls:")
readByte() // read SELFDEF tag
readLongNat() // skip Name
Expand All @@ -810,21 +811,36 @@ class TreeUnpickler[Tasty <: TastyUniverse](
cls.typeOfThis = selfTpe
}

val parentTypes = ctx.adjustParents(cls, parents)

ctx.setInfo(cls, {
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.map(symFromNoCycle), classInfo)
})
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)
}

ctx.log {
val addendum =
if (parentTypes.isEmpty) ""
else parentTypes.map(lzyShow).mkString(" extends ", " with ", "") // don't force types
s"$currentAddr Template: Updated info of $cls$addendum"
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)
}

inIndexingContext(traverseTemplate()(_))

}

def isTopLevel: Boolean = nextByte === IMPORT || nextByte === PACKAGE
Expand All @@ -845,7 +861,8 @@ class TreeUnpickler[Tasty <: TastyUniverse](
until(end)(readIndexedStatAsSym(exprOwner))

def readStatsAsSyms(exprOwner: Symbol, end: Addr)(implicit ctx: Context): List[NoCycle] = {
fork.indexStats(end)
def forkAndIndexStats(implicit ctx: Context): Unit = fork.indexStats(end)
inIndexingContext(forkAndIndexStats(_))
readIndexedStatsAsSyms(exprOwner, end)
}

Expand Down
124 changes: 65 additions & 59 deletions src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,30 @@ trait ContextOps { self: TastyUniverse =>
else u.NoSymbol //throw new AssertionError(s"no module $name in ${location(owner)}")
}

/**Perform an operation within a context that has the mode [[IndexStats]] will force any collected annotations
* afterwards*/
def inIndexingContext(op: Context => Unit)(implicit ctx: Context): Unit = {
val statsCtx = ctx.addMode(IndexStats)
op(statsCtx)
statsCtx.initialContext.forceAnnotations()
}



/**Forces lazy annotations, if one is [[scala.annotation.internal.Child]] then it will add the referenced type as a
* sealed child.
*/
private def analyseAnnotations(sym: Symbol)(implicit ctx: Context): Unit = {
for (annot <- sym.annotations) {
annot.completeInfo()
if (annot.tpe.typeSymbolDirect === defn.ChildAnnot) {
val child = annot.tpe.typeArgs.head.typeSymbolDirect
sym.addChild(child)
ctx.log(s"adding sealed child ${showSym(child)} to ${showSym(sym)}")
}
}
}

/**Maintains state through traversal of a TASTy file, such as the outer scope of the defintion being traversed, the
* traversal mode, and the root owners and source path for the TASTy file.
* It also provides all operations for manipulation of the symbol table, such as creating/updating symbols and
Expand All @@ -82,56 +106,14 @@ trait ContextOps { self: TastyUniverse =>
sealed abstract class Context {
thisCtx =>

private[this] implicit final def implyThisCtx: thisCtx.type = thisCtx

private[this] var mySymbolsToForceAnnots: mutable.LinkedHashSet[Symbol] = _

private def stageSymbolToForceAnnots(sym: Symbol): Unit = {
if (sym.annotations.nonEmpty) {
if (mySymbolsToForceAnnots == null) {
mySymbolsToForceAnnots = mutable.LinkedHashSet.empty
}
mySymbolsToForceAnnots += sym
}
}

/** Force any lazy annotations collected from declaration statements directly in this scope.
*
* It is important to call this *after* indexing statements in a scope, otherwise calling
* [[ownertree.findOwner]] can fail, this is because [[ownertree.findOwner]] cannot traverse a definition tree at
* a given address before a symbol has been registered to that address.
*/
def forceAnnotations(): Unit = {
if (mySymbolsToForceAnnots != null) {
val toForce = mySymbolsToForceAnnots
mySymbolsToForceAnnots = null
for (sym <- toForce) {
log(s"!!! forcing annotations on ${showSym(sym)}")
analyseAnnotations(sym)
}
}
}

/**Forces lazy annotations, if one is [[scala.annotation.internal.Child]] then it will add the referenced type as a
* sealed child.
*/
private def analyseAnnotations(sym: Symbol): Unit = {
for (annot <- sym.annotations) {
annot.completeInfo()
if (annot.tpe.typeSymbolDirect === defn.ChildAnnot) {
val child = annot.tpe.typeArgs.head.typeSymbolDirect
sym.addChild(child)
log(s"adding sealed child ${showSym(child)} to ${showSym(sym)}")
}
}
}
protected implicit final def implyThisCtx: thisCtx.type = thisCtx

/**Associates the annotations with the symbol, and will force their evaluation if not reading statements.*/
def adjustAnnotations(sym: Symbol, annots: List[DeferredAnnotation]): Unit = {
if (annots.nonEmpty) {
if (mode.is(IndexStats)) {
log(s"lazily adding annotations to ${showSym(sym)}")
stageSymbolToForceAnnots(sym.setAnnotations(annots.map(_.lzy(sym))))
initialContext.stageSymbolToForceAnnots(sym.setAnnotations(annots.map(_.lzy(sym))))
}
else {
log(s"eagerly adding annotations to ${showSym(sym)}")
Expand Down Expand Up @@ -428,36 +410,30 @@ trait ContextOps { self: TastyUniverse =>
}

final def withOwner(owner: Symbol): Context =
if (owner `ne` this.owner) fresh(owner) else this
if (owner `ne` this.owner) freshSymbol(owner) else this

final def withNewScope: Context =
fresh(newLocalDummy)
freshSymbol(newLocalDummy)

final def selectionCtx(name: TastyName): Context = this // if (name.isConstructorName) this.addMode(Mode.InSuperCall) else this
final def fresh(owner: Symbol): FreshContext = new FreshContext(owner, this, this.mode)

private def sibling(mode: TastyMode): FreshContext = new FreshContext(this.owner, outerOrThis, mode)
private def sibling: FreshContext = sibling(mode)

private def outerOrThis: Context = this match {
case ctx: FreshContext => ctx.outer
case ctx => ctx
}
final def freshSymbol(owner: Symbol): FreshContext = new FreshContext(owner, this, this.mode)
final def freshMode(mode: TastyMode): FreshContext = new FreshContext(this.owner, this, mode)
final def fresh: FreshContext = new FreshContext(this.owner, this, this.mode)

final def addMode(mode: TastyMode): Context =
if (!this.mode.is(mode)) sibling(this.mode | mode)
if (!this.mode.is(mode)) freshMode(this.mode | mode)
else this

final def retractMode(mode: TastyMode): Context =
if (this.mode.isOneOf(mode)) sibling(this.mode &~ mode)
if (this.mode.isOneOf(mode)) freshMode(this.mode &~ mode)
else this

final def withMode(mode: TastyMode): Context =
if (mode != this.mode) sibling(mode)
if (mode != this.mode) freshMode(mode)
else this

final def withSource(source: AbstractFile): Context =
if (source `ne` this.source) sibling.atSource(source)
if (source `ne` this.source) fresh.atSource(source)
else this

final def withPhaseNoLater[T](phase: String)(op: Context => T): T =
Expand All @@ -468,6 +444,36 @@ trait ContextOps { self: TastyUniverse =>
final class InitialContext(val topLevelClass: Symbol, val source: AbstractFile) extends Context {
def mode: TastyMode = EmptyTastyMode
def owner: Symbol = topLevelClass.owner

private[this] var mySymbolsToForceAnnots: mutable.LinkedHashSet[Symbol] = _

private[ContextOps] def stageSymbolToForceAnnots(sym: Symbol): Unit = {
if (sym.annotations.nonEmpty) {
if (mySymbolsToForceAnnots == null) {
mySymbolsToForceAnnots = mutable.LinkedHashSet.empty
}
mySymbolsToForceAnnots += sym
}
}

/** Force any lazy annotations collected from declaration statements directly in this scope.
*
* It is important to call this *after* indexing statements in a scope, otherwise calling
* [[ownertree.findOwner]] can fail, this is because [[ownertree.findOwner]] cannot traverse a definition tree at
* a given address before a symbol has been registered to that address.
*/
private[ContextOps] def forceAnnotations(): Unit = {
if (mySymbolsToForceAnnots != null) {
val toForce = mySymbolsToForceAnnots
mySymbolsToForceAnnots = null
for (sym <- toForce) {
log(s"!!! forcing annotations on ${showSym(sym)}")
analyseAnnotations(sym)
}
assert(mySymbolsToForceAnnots == null, "more symbols added while forcing")
}
}

}

final class FreshContext(val owner: Symbol, val outer: Context, val mode: TastyMode) extends Context {
Expand Down
5 changes: 5 additions & 0 deletions test/tasty/pos/src-2/tastytest/TestAnnotated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ package tastytest
object TestAnnotated {
def test1 = new Annotated {}
def test2 = new RootAnnotated {}
def test3 = {
val o = new OuterClassAnnotated {}
o.foo
}
def test4 = new ParameterizedAnnotated(23).foo
}
12 changes: 12 additions & 0 deletions test/tasty/pos/src-3/tastytest/Annotated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@ trait Annotated

@rootAnnot(1)
trait RootAnnotated

trait OuterClassAnnotated extends OuterClass {
@basicAnnot(xyz)
def foo = 1
}

class ParameterizedAnnotated(@basicAnnot(ParameterizedAnnotated.value) x: Int) {
def foo = x
}
object ParameterizedAnnotated {
final val value = 23
}
7 changes: 7 additions & 0 deletions test/tasty/pos/src-3/tastytest/OuterClass.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package tastytest

class OuterClass {

final val xyz = "xyz"

}
3 changes: 3 additions & 0 deletions test/tasty/pos/src-3/tastytest/basicAnnot.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tastytest

final class basicAnnot[T](member: T) extends scala.annotation.StaticAnnotation

0 comments on commit 94f693f

Please sign in to comment.