From c0c0a54309a722aa3175fd0ca7028ace52162a53 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 May 2016 17:20:38 +0200 Subject: [PATCH 01/17] Use source module ref as assumed self type when reading Tasty When reading Tasty we need to pre-set the info of a class to some ClassInfoType with (as yet) unknown parents and self type. But for module classes, we need to know the source module at all time, and this gets determined by the self type. So we now produce a TermRef for the assumed self type of a module class. --- src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 535ddd216693..7cc390ed08bf 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -646,7 +646,11 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { val cls = ctx.owner.asClass def setClsInfo(parents: List[TypeRef], selfType: Type) = cls.info = ClassInfo(cls.owner.thisType, cls, parents, cls.unforcedDecls, selfType) - setClsInfo(Nil, NoType) + val assumedSelfType = + if (cls.is(Module) && cls.owner.isClass) + TermRef.withSig(cls.owner.thisType, cls.name.sourceModuleName, Signature.NotAMethod) + else NoType + setClsInfo(Nil, assumedSelfType) val localDummy = ctx.newLocalDummy(cls) assert(readByte() == TEMPLATE) val end = readEnd() @@ -659,7 +663,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } } val parentRefs = ctx.normalizeToClassRefs(parents.map(_.tpe), cls, cls.unforcedDecls) - val self = + val self = if (nextByte == SELFDEF) { readByte() untpd.ValDef(readName(), readTpt(), EmptyTree).withType(NoType) From 7f52d17f204536611b545748da815d96bc5d71cf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 May 2016 17:24:38 +0200 Subject: [PATCH 02/17] Disable stub checking It caused an assertion error when separately compiling parts of dotty against TASTY information. Not sure the test achieves anything or whether it produces a false negative. --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 7cc390ed08bf..b8622680b893 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -44,7 +44,16 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { private val unpickledSyms = new mutable.HashSet[Symbol] private val treeAtAddr = new mutable.HashMap[Addr, Tree] private val typeAtAddr = new mutable.HashMap[Addr, Type] // currently populated only for types that are known to be SHAREd. - private var stubs: Set[Symbol] = Set() + + // Currently disabled set used for checking that all + // already encountered symbols are forward refereneces. This + // check fails in more complicated scenarios of separate + // compilation in dotty (for instance: compile all of `core` + // given the TASTY files of everything else in the compiler). + // I did not have the time to track down what caused the failure. + // The testing scheme could well have produced a false negative. + // + // private var stubs: Set[Symbol] = Set() private var roots: Set[SymDenotation] = null @@ -155,7 +164,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { forkAt(addr).createSymbol() val sym = symAtAddr(addr) ctx.log(i"forward reference to $sym") - stubs += sym + // stubs += sym sym } } @@ -405,8 +414,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { else { val sym = symAtAddr.get(start) match { case Some(preExisting) => - assert(stubs contains preExisting) - stubs -= preExisting + //assert(stubs contains preExisting, preExisting) + //stubs -= preExisting preExisting case none => ctx.newNakedSymbol(start.index) From 624561d164c72adfb1a65a2bf55103fe84765915 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 08:32:02 +0200 Subject: [PATCH 03/17] Further improve doc comment --- src/dotty/annotation/internal/Child.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dotty/annotation/internal/Child.scala b/src/dotty/annotation/internal/Child.scala index 4f3ceb6c09c6..9295de73e2c6 100644 --- a/src/dotty/annotation/internal/Child.scala +++ b/src/dotty/annotation/internal/Child.scala @@ -9,7 +9,8 @@ import scala.annotation.Annotation * case class B() extends A * case class C() extends A * - * Then `A` would carry the annotations `@Child[B] @Child[C]` where - * `B`, `C` are TypeRefs. + * Then the class symbol `A` would carry the annotations + * `@Child[Bref] @Child[Cref]` where `Bref`, `Cref` are TypeRefs + * referring to the class symbols of `B` and `C` */ class Child[T] extends Annotation From df58b455df32dbc6cf98eff2ccea6fc268cf6193 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 08:32:39 +0200 Subject: [PATCH 04/17] Decouple annotation transformers from info transformers --- .../tools/dotc/transform/CheckReentrant.scala | 2 +- .../tools/dotc/transform/CheckStatic.scala | 2 +- .../tools/dotc/transform/FirstTransform.scala | 7 +++-- .../tools/dotc/transform/TreeTransform.scala | 30 +++++++------------ 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/dotty/tools/dotc/transform/CheckReentrant.scala b/src/dotty/tools/dotc/transform/CheckReentrant.scala index 2569b3aaef47..7e0e368b577c 100644 --- a/src/dotty/tools/dotc/transform/CheckReentrant.scala +++ b/src/dotty/tools/dotc/transform/CheckReentrant.scala @@ -3,7 +3,7 @@ package transform import core._ import Names._ -import dotty.tools.dotc.transform.TreeTransforms.{AnnotationTransformer, TransformerInfo, MiniPhaseTransform, TreeTransformer} +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, MiniPhaseTransform, TreeTransformer} import ast.Trees._ import Flags._ import Types._ diff --git a/src/dotty/tools/dotc/transform/CheckStatic.scala b/src/dotty/tools/dotc/transform/CheckStatic.scala index 445e9f83958d..77c6dfc51952 100644 --- a/src/dotty/tools/dotc/transform/CheckStatic.scala +++ b/src/dotty/tools/dotc/transform/CheckStatic.scala @@ -5,7 +5,7 @@ import core._ import Names._ import StdNames.nme import Types._ -import dotty.tools.dotc.transform.TreeTransforms.{AnnotationTransformer, TransformerInfo, MiniPhaseTransform, TreeTransformer} +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, MiniPhaseTransform, TreeTransformer} import ast.Trees._ import Flags._ import Contexts.Context diff --git a/src/dotty/tools/dotc/transform/FirstTransform.scala b/src/dotty/tools/dotc/transform/FirstTransform.scala index ae72d5f6c7be..6e1fed6078cc 100644 --- a/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -30,7 +30,7 @@ import StdNames._ * - stubs out native methods * - eliminate self tree in Template and self symbol in ClassInfo */ -class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer with AnnotationTransformer { thisTransformer => +class FirstTransform extends MiniPhaseTransform with InfoTransformer with AnnotationTransformer { thisTransformer => import ast.tpd._ override def phaseName = "firstTransform" @@ -46,12 +46,13 @@ class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer wi } /** eliminate self symbol in ClassInfo */ - def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { - case tp@ClassInfo(_, _, _, _, self: Symbol) => + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + case tp @ ClassInfo(_, _, _, _, self: Symbol) => tp.derivedClassInfo(selfInfo = self.info) case _ => tp } + /* tp match { //create companions for value classes that are not from currently compiled source file diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index abada9ab47a8..89ae927b78d6 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -174,32 +174,22 @@ object TreeTransforms { } /** A helper trait to transform annotations on MemberDefs */ - trait AnnotationTransformer extends MiniPhaseTransform with InfoTransformer { + trait AnnotationTransformer extends MiniPhaseTransform with DenotTransformer { val annotationTransformer = mkTreeTransformer override final def treeTransformPhase = this // need to run at own phase because otherwise we get ahead of ourselves in transforming denotations - override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = { - val info1 = transformInfo(ref.info, ref.symbol) - - ref match { - case ref: SymDenotation => - val annots1 = - if (!ref.symbol.isDefinedInCurrentRun) ref.annotations // leave annotations read from class files alone - else { - val annotTrees = ref.annotations.map(_.tree) - val annotTrees1 = annotTrees.mapConserve(annotationTransformer.macroTransform) - if (annotTrees eq annotTrees1) ref.annotations - else annotTrees1.map(new ConcreteAnnotation(_)) - } - if ((info1 eq ref.info) && (annots1 eq ref.annotations)) ref - else ref.copySymDenotation(info = info1, annotations = annots1) - case _ => - if (info1 eq ref.info) ref - else ref.derivedSingleDenotation(ref.symbol, info1) + abstract override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = + super.transform(ref) match { + case ref1: SymDenotation if ref1.symbol.isDefinedInCurrentRun => + val annotTrees = ref1.annotations.map(_.tree) + val annotTrees1 = annotTrees.mapConserve(annotationTransformer.macroTransform) + if (annotTrees eq annotTrees1) ref1 + else ref1.copySymDenotation(annotations = annotTrees1.map(new ConcreteAnnotation(_))) + case ref1 => + ref1 } - } } @sharable val NoTransform = new TreeTransform { From 1ab71ca9b2db9d2219b23837df4c27723773738b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 10:24:23 +0200 Subject: [PATCH 05/17] Instrument Denotations#current to find CyclicReference Instrument Denotations#current to find CyclicReference errors arising during transforms. --- src/dotty/tools/dotc/core/Denotations.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 080e8a39ba58..5dfc5c17d58c 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -656,7 +656,13 @@ object Denotations { var startPid = nextTransformerId + 1 val transformer = ctx.denotTransformers(nextTransformerId) //println(s"transforming $this with $transformer") - next = transformer.transform(cur)(ctx.withPhase(transformer)).syncWithParents + try { + next = transformer.transform(cur)(ctx.withPhase(transformer)).syncWithParents + } catch { + case ex: CyclicReference => + println(s"error while transforming $this") // DEBUG + throw ex + } if (next eq cur) startPid = cur.validFor.firstPhaseId else { From 67d5660e0be6f0ae411b8fea030acd92a7b27cbf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 10:27:57 +0200 Subject: [PATCH 06/17] Don't force a symbol's denotation for isTerm/isType Forcing it led to CyclicReferences involving RefChecks.OptLevelInfo when compiling dotc/*.scala against Tasty files. The problem was that when transforming OptLevelInfo the backend forced a transformInfo of RefChecks in TypeErasure which filtered RefCheck's scope to eliminate non-class type definitions. Without the tweak in this commit this tried to make all symbols current, and so came back to OptLevelInfo. --- src/dotty/tools/dotc/core/Symbols.scala | 4 ++-- src/dotty/tools/dotc/core/TypeErasure.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index d40acdfa797b..b9458b1339f5 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -398,10 +398,10 @@ object Symbols { /** Subclass tests and casts */ final def isTerm(implicit ctx: Context): Boolean = - (if(isDefinedInCurrentRun) lastDenot else denot).isTerm + (if (defRunId == ctx.runId) lastDenot else denot).isTerm final def isType(implicit ctx: Context): Boolean = - (if(isDefinedInCurrentRun) lastDenot else denot).isType + (if (defRunId == ctx.runId) lastDenot else denot).isType final def isClass: Boolean = isInstanceOf[ClassSymbol] diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index 89077897a3ab..39d02e069ca7 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -374,7 +374,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean tr1 :: trs1.filterNot(_ isRef defn.ObjectClass) case nil => nil } - val erasedDecls = decls.filteredScope(d => !d.isType || d.isClass) + val erasedDecls = decls.filteredScope(sym => !sym.isType || sym.isClass) tp.derivedClassInfo(NoPrefix, parents, erasedDecls, erasedRef(tp.selfType)) // can't replace selftype by NoType because this would lose the sourceModule link } From e4f87d90414e5cfd590f2549e090e7e0ab8141b8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 13:08:56 +0200 Subject: [PATCH 07/17] Make sure local data is unpickled at right phase We had a problem where unpickling an annotation containing a class constant had the wrong type. Unpickling was done after erasure. The type given to the constant was an alias but aliases got eliminated during erasure, so the constant was malformed. Unpickling annotation contents at the same phase as unpickling the annotation carrier solves the problem. It seems similar problems can arise when data is unpickled using a LocalUnpickler. So we now make sure local unpickling runs at the latest at phase Pickler. --- .../tools/dotc/core/unpickleScala2/Scala2Unpickler.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index cceaed53d767..71a919ca357e 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -573,7 +573,8 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas // println(s"unpickled ${denot.debugString}, info = ${denot.info}") !!! DEBUG } atReadPos(startCoord(denot).toIndex, - () => parseToCompletion(denot)(ctx.addMode(Mode.Scala2Unpickling))) + () => parseToCompletion(denot)( + ctx.addMode(Mode.Scala2Unpickling).withPhaseNoLater(ctx.picklerPhase))) } catch { case ex: RuntimeException => handleRuntimeException(ex) } @@ -922,7 +923,8 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas val start = readIndex val atp = readTypeRef() Annotation.deferred( - atp.typeSymbol, implicit ctx => atReadPos(start, () => readAnnotationContents(end))) + atp.typeSymbol, implicit ctx1 => + atReadPos(start, () => readAnnotationContents(end)(ctx1.withPhase(ctx.phase)))) } /* Read an abstract syntax tree */ From f99d3fb1ed3de8390d444d5ee71ce1330a4f92f1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 14:05:19 +0200 Subject: [PATCH 08/17] Replace aliases to Unit by Unit Do this in the inferred (result-)type of ValDefs and DefDefs. Without this fix, TreeTraverser#traverseChildren in Trees.scala gets a result type of BoxedUnit (but only when co-compiled from source, not when unpickled). --- src/dotty/tools/dotc/typer/Namer.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 82b3b56e9714..38e62e9c600b 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -824,13 +824,21 @@ class Namer { typer: Typer => // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") def isInline = sym.is(Final, butNot = Method) + + // Widen rhs type and approximate `|' but keep ConstantTypes if + // definition is inline (i.e. final in Scala2). def widenRhs(tp: Type): Type = tp.widenTermRefExpr match { case tp: ConstantType if isInline => tp case _ => tp.widen.approximateUnion } + + // Replace aliases to Unit by Unit itself. If we leave the alias in + // it would be erased to BoxedUnit. + def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp + val rhsCtx = ctx.addMode(Mode.InferringReturnType) def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe - def cookedRhsType = ctx.deskolemize(widenRhs(rhsType)) + def cookedRhsType = ctx.deskolemize(dealiasIfUnit(widenRhs(rhsType))) lazy val lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.pos) //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") if (inherited.exists) From 1bce89efb9ccb3d58c72a89440d6096de2c6e646 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 May 2016 16:35:51 +0200 Subject: [PATCH 09/17] Fix withPhaseNoLater It's possible that the given phase argument does not exist, in which case we do not want to set the current phase to NoPhase. --- src/dotty/tools/dotc/core/Contexts.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index bbe8e920c60b..e1aeac8c3491 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -248,7 +248,7 @@ object Contexts { withPhase(phase.id) final def withPhaseNoLater(phase: Phase) = - if (ctx.phase.id > phase.id) withPhase(phase) else ctx + if (phase.exists && ctx.phase.id > phase.id) withPhase(phase) else ctx /** If -Ydebug is on, the top of the stack trace where this context * was created, otherwise `null`. From c793085ff63b8d7a3e9e371ca5bf5da5368ac6be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 21 May 2016 13:30:36 +0200 Subject: [PATCH 10/17] Make sure delayed Tasty unpicklings are done at the latest at Pickler phase --- src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b8622680b893..2abcce0fa6bf 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -53,7 +53,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { // I did not have the time to track down what caused the failure. // The testing scheme could well have produced a false negative. // - // private var stubs: Set[Symbol] = Set() + // private var stubs: Set[Symbol] = Set() private var roots: Set[SymDenotation] = null @@ -96,7 +96,9 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { class Completer(reader: TastyReader) extends LazyType { import reader._ def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { - treeAtAddr(currentAddr) = new TreeReader(reader).readIndexedDef() + treeAtAddr(currentAddr) = + new TreeReader(reader).readIndexedDef()( + ctx.withPhaseNoLater(ctx.picklerPhase))//(ctx.withOwner(owner)) } } @@ -951,7 +953,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { class LazyReader[T <: AnyRef](reader: TreeReader, op: TreeReader => Context => T) extends Trees.Lazy[T] with DeferredPosition { def complete(implicit ctx: Context): T = { pickling.println(i"starting to read at ${reader.reader.currentAddr}") - val res = op(reader)(ctx.addMode(Mode.AllowDependentFunctions)) + val res = op(reader)(ctx.addMode(Mode.AllowDependentFunctions).withPhaseNoLater(ctx.picklerPhase)) normalizePos(res, parentPos) res } @@ -960,7 +962,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { class LazyAnnotationReader(sym: Symbol, reader: TreeReader) extends LazyAnnotation(sym) with DeferredPosition { def complete(implicit ctx: Context) = { - val res = reader.readTerm() + val res = reader.readTerm()(ctx.withPhaseNoLater(ctx.picklerPhase)) normalizePos(res, parentPos) res } From 1f8cf78441d84385734a210b0a5971eae76f46fc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 21 May 2016 13:44:22 +0200 Subject: [PATCH 11/17] Let createSymbol return a symbol Compute initialization flags of possibly enclosing traits elsewhere (in indexStats). Cleans up the logic and makes the module more understandable. --- src/dotty/tools/dotc/core/Flags.scala | 3 ++ .../tools/dotc/core/tasty/TreeUnpickler.scala | 47 ++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index f866621f258d..cd660aa46216 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -525,6 +525,9 @@ object Flags { /** Either method or lazy */ final val MethodOrLazy = Method | Lazy + /** Either method or lazy or deferred */ + final val MethodOrLazyOrDeferred = Method | Lazy | Deferred + /** Labeled `private` or `final` */ final val PrivateOrFinal = Private | Final diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 2abcce0fa6bf..cd5eb9e5015a 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -371,10 +371,9 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } /** Create symbol of definition node and enter in symAtAddr map - * @return the largest subset of {NoInits, PureInterface} that a - * trait owning this symbol can have as flags. + * @return the created symbol */ - def createSymbol()(implicit ctx: Context): FlagSet = { + def createSymbol()(implicit ctx: Context): Symbol = { val start = currentAddr val tag = readByte() val end = readEnd() @@ -434,10 +433,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } - if (isClass) NoInits - else if (sym.isType || sym.isConstructor || flags.is(Deferred)) NoInitsInterface - else if (tag == VALDEF) EmptyFlags - else NoInits + sym } /** Read modifier list into triplet of flags, annotations and a privateWithin @@ -504,28 +500,33 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { (flags, annots.toList, privateWithin) } - /** Create symbols for a definitions in statement sequence between + /** Create symbols for the definitions in the statement sequence between * current address and `end`. * @return the largest subset of {NoInits, PureInterface} that a * trait owning the indexed statements can have as flags. */ def indexStats(end: Addr)(implicit ctx: Context): FlagSet = { - val flagss = - until(end) { - nextByte match { - case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM => - createSymbol() - case IMPORT => - skipTree() - NoInitsInterface - case PACKAGE => - processPackage { (pid, end) => implicit ctx => indexStats(end) } - case _ => - skipTree() - EmptyFlags - } + var initsFlags = NoInitsInterface + while (currentAddr.index < end.index) { + nextByte match { + case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM => + val sym = createSymbol() + if (sym.isTerm && !sym.is(MethodOrLazyOrDeferred)) + initsFlags = EmptyFlags + else if (sym.isClass || + sym.is(Method, butNot = Deferred) && !sym.isConstructor) + initsFlags &= NoInits + case IMPORT => + skipTree() + case PACKAGE => + processPackage { (pid, end) => implicit ctx => indexStats(end) } + case _ => + skipTree() + initsFlags = EmptyFlags } - (NoInitsInterface /: flagss)(_ & _) + } + assert(currentAddr.index == end.index) + initsFlags } /** Process package with given operation `op`. The operation takes as arguments From 7f08c1fbd674d6ebf3f74e75fe5b3e83daf9da7e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 21 May 2016 19:11:15 +0200 Subject: [PATCH 12/17] Maintain ownerTree data structure when unpickling Tasty First step for a more robust scheme to access symbols in Tasty. This entailed a swap of two fields in RefiendType, to make tree format more uniform in what concerns where references are found. --- .../tools/dotc/core/tasty/TastyFormat.scala | 11 +++ .../tools/dotc/core/tasty/TreePickler.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 80 +++++++++++++++++-- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index ea7e985c9a1a..2211706225e6 100644 --- a/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -485,4 +485,15 @@ object TastyFormat { case PRIVATEqualified => "PRIVATEqualified" case PROTECTEDqualified => "PROTECTEDqualified" } + + /** @return If non-negative, the number of leading references of a length/trees entry. + * If negative, minus the number of leading non-reference trees. + */ + def numRefs(tag: Int) = tag match { + case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | + SELFDEF | REFINEDtype => 1 + case RENAMED | PARAMtype => 2 + case POLYtype | METHODtype => -1 + case _ => 0 + } } diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index f8f9c993f43d..37b9341eb62f 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -217,8 +217,8 @@ class TreePickler(pickler: TastyPickler) { case tpe: RefinedType => writeByte(REFINEDtype) withLength { - pickleType(tpe.parent) pickleName(tpe.refinedName) + pickleType(tpe.parent) pickleType(tpe.refinedInfo, richTypes = true) } case tpe: TypeAlias => diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index cd5eb9e5015a..a82c4cad40a7 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -57,6 +57,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { private var roots: Set[SymDenotation] = null + private var ownerTree: OwnerTree = _ + private def registerSym(addr: Addr, sym: Symbol) = { symAtAddr(addr) = sym unpickledSyms += sym @@ -118,6 +120,45 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { def skipParams(): Unit = while (nextByte == PARAMS || nextByte == TYPEPARAM) skipTree() + /** Record all directly nested definitions and templates in current tree + * as `OwnerTree`s in `buf` + */ + def scanTree(buf: ListBuffer[OwnerTree]): Unit = { + val start = currentAddr + val tag = readByte() + //println(s"scan tree at $currentAddr, tag = $tag") + tag match { + case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | TEMPLATE => + val end = readEnd() + for (i <- 0 until numRefs(tag)) readNat() + buf += new OwnerTree(start, fork, end) + case tag => + if (tag >= firstLengthTreeTag) { + val end = readEnd() + var nrefs = numRefs(tag) + if (nrefs < 0) { + for (i <- nrefs until 0) scanTree(buf) + goto(end) + } + else { + for (i <- 0 until nrefs) readNat() + scanTrees(buf, end) + } + } + else if (tag >= firstNatASTTreeTag) { readNat(); scanTree(buf) } + else if (tag >= firstASTTreeTag) scanTree(buf) + 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: ListBuffer[OwnerTree], end: Addr): Unit = { + while (currentAddr.index < end.index) scanTree(buf) + assert(currentAddr.index == end.index) + } + /** The next tag, following through SHARED tags */ def nextUnsharedTag: Int = { val tag = nextByte @@ -197,8 +238,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { case SUPERtype => SuperType(readType(), readType()) case REFINEDtype => - val parent = readType() var name: Name = readName() + val parent = readType() val ttag = nextUnsharedTag if (ttag == TYPEBOUNDS || ttag == TYPEALIAS) name = name.toTypeName RefinedType(parent, name, rt => registeringType(rt, readType())) @@ -437,7 +478,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } /** Read modifier list into triplet of flags, annotations and a privateWithin - * boindary symbol. + * boundary symbol. */ def readModifiers(end: Addr)(implicit ctx: Context): (FlagSet, List[Annotation], Symbol) = { var flags: FlagSet = EmptyFlags @@ -664,6 +705,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { else NoType setClsInfo(Nil, assumedSelfType) val localDummy = ctx.newLocalDummy(cls) + registerSym(start, localDummy) assert(readByte() == TEMPLATE) val end = readEnd() val tparams = readIndexedParams[TypeDef](TYPEPARAM) @@ -716,12 +758,11 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } def readTopLevel()(implicit ctx: Context): List[Tree] = { + ownerTree = new OwnerTree(NoAddr, fork, reader.endAddr) @tailrec def read(acc: ListBuffer[Tree]): List[Tree] = nextByte match { case IMPORT | PACKAGE => acc += readIndexedStat(NoSymbol) - if (!isAtEnd) - read(acc) - else acc.toList + if (!isAtEnd) read(acc) else acc.toList case _ => // top-level trees which are not imports or packages are not part of tree acc.toList } @@ -968,4 +1009,33 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { res } } + + class OwnerTree(val addr: Addr, reader: TreeReader, val end: Addr) { + lazy val children: List[OwnerTree] = { + val buf = new ListBuffer[OwnerTree] + reader.scanTrees(buf, end) + buf.toList + } + def findOwner(addr: Addr)(implicit ctx: Context): Symbol = { + //println(s"find owner $addr") + def search(cs: List[OwnerTree], current: Symbol): Symbol = cs match { + case ot :: cs1 => + if (ot.addr.index == addr.index) { + //println(i"search ok $addr, owner = $current") + current + } + else if (ot.addr.index < addr.index && addr.index < ot.end.index) { + val encl = reader.symbolAt(ot.addr) + //println(s"search $addr in ${ot.children} with $encl") + search(ot.children, encl) + } + else + search(cs1, current) + case Nil => + throw new Error("unattached tree") + } + search(children, NoSymbol) + } + override def toString = s"OwnerTree(${addr.index}, ${end.index}" + } } From c204a962d32bc6ee64256501d7dfe6efabeccc9f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 22 May 2016 21:55:53 +0200 Subject: [PATCH 13/17] Adopt new scheme for handling forward references in Tasty Instead of stubbing with potentially wrong owners and hping for the best, we now compute owners on demand, using the lazy data structure of an OwnerTree. --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 172 +++++++++++------- 1 file changed, 106 insertions(+), 66 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index a82c4cad40a7..9598fc0c5441 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -22,6 +22,7 @@ import config.Printers.pickling class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { import TastyFormat._ import TastyName._ + import TreeUnpickler._ import tpd._ private var readPositions = false @@ -45,18 +46,12 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { private val treeAtAddr = new mutable.HashMap[Addr, Tree] private val typeAtAddr = new mutable.HashMap[Addr, Type] // currently populated only for types that are known to be SHAREd. - // Currently disabled set used for checking that all - // already encountered symbols are forward refereneces. This - // check fails in more complicated scenarios of separate - // compilation in dotty (for instance: compile all of `core` - // given the TASTY files of everything else in the compiler). - // I did not have the time to track down what caused the failure. - // The testing scheme could well have produced a false negative. - // - // private var stubs: Set[Symbol] = Set() - + /** The root symbol denotation which are defined by the Tasty file associated with this + * TreeUnpickler. Set by `enterTopLevel`. + */ private var roots: Set[SymDenotation] = null + /** The root owner tree. See `OwnerTree` class definition. Set by `enterTopLevel`. */ private var ownerTree: OwnerTree = _ private def registerSym(addr: Addr, sym: Symbol) = { @@ -69,7 +64,9 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { */ def enterTopLevel(roots: Set[SymDenotation])(implicit ctx: Context): Unit = { this.roots = roots - new TreeReader(reader).fork.indexStats(reader.endAddr) + var rdr = new TreeReader(reader).fork + ownerTree = new OwnerTree(NoAddr, 0, rdr.fork, reader.endAddr) + rdr.indexStats(reader.endAddr) } /** The unpickled trees */ @@ -123,17 +120,19 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { /** Record all directly nested definitions and templates in current tree * as `OwnerTree`s in `buf` */ - def scanTree(buf: ListBuffer[OwnerTree]): Unit = { + def scanTree(buf: ListBuffer[OwnerTree], mode: MemberDefMode = AllDefs): Unit = { val start = currentAddr val tag = readByte() - //println(s"scan tree at $currentAddr, tag = $tag") tag match { case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | TEMPLATE => val end = readEnd() for (i <- 0 until numRefs(tag)) readNat() - buf += new OwnerTree(start, fork, end) + if (tag == TEMPLATE) scanTrees(buf, end, MemberDefsOnly) + if (mode != NoMemberDefs) buf += new OwnerTree(start, tag, fork, end) + goto(end) case tag => - if (tag >= firstLengthTreeTag) { + if (mode == MemberDefsOnly) skipTree(tag) + else if (tag >= firstLengthTreeTag) { val end = readEnd() var nrefs = numRefs(tag) if (nrefs < 0) { @@ -154,8 +153,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { /** Record all directly nested definitions and templates between current address and `end` * as `OwnerTree`s in `buf` */ - def scanTrees(buf: ListBuffer[OwnerTree], end: Addr): Unit = { - while (currentAddr.index < end.index) scanTree(buf) + def scanTrees(buf: ListBuffer[OwnerTree], end: Addr, mode: MemberDefMode): Unit = { + while (currentAddr.index < end.index) scanTree(buf, mode) assert(currentAddr.index == end.index) } @@ -197,19 +196,25 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { until(end) { readNat(); readType().asInstanceOf[T] } /** Read referece to definition and return symbol created at that definition */ - def readSymRef()(implicit ctx: Context): Symbol = { - val start = currentAddr - val addr = readAddr() - symAtAddr get addr match { - case Some(sym) => sym - case None => - // Create a stub; owner might be wrong but will be overwritten later. - forkAt(addr).createSymbol() - val sym = symAtAddr(addr) - ctx.log(i"forward reference to $sym") - // stubs += sym - sym - } + def readSymRef()(implicit ctx: Context): Symbol = symbolAt(readAddr()) + + /** The symbol at given address; createa new one if none exists yet */ + def symbolAt(addr: Addr)(implicit ctx: Context): Symbol = symAtAddr.get(addr) match { + case Some(sym) => + sym + case None => + val sym = forkAt(addr).createSymbol()(ctx.withOwner(ownerTree.findOwner(addr))) + ctx.log(i"forward reference to $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, i"owner discrepancy for $sym, expected: ${ctx.owner}, found: ${sym.owner}") + sym + case None => + createSymbol() } /** Read a type */ @@ -414,7 +419,21 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { /** Create symbol of definition node and enter in symAtAddr map * @return the created symbol */ - def createSymbol()(implicit ctx: Context): Symbol = { + def createSymbol()(implicit ctx: Context): Symbol = nextByte match { + case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM => + createMemberSymbol() + case TEMPLATE => + val localDummy = ctx.newLocalDummy(ctx.owner) + registerSym(currentAddr, localDummy) + localDummy + case tag => + 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() val end = readEnd() @@ -451,22 +470,10 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { case _ => val completer = adjustIfModule(new Completer(subReader(start, end))) if (isClass) - ctx.newClassSymbol(ctx.owner, name.asTypeName, flags, completer, - privateWithin, coord = start.index) - else { - val sym = symAtAddr.get(start) match { - case Some(preExisting) => - //assert(stubs contains preExisting, preExisting) - //stubs -= preExisting - preExisting - case none => - ctx.newNakedSymbol(start.index) - } - val denot = ctx.SymDenotation(symbol = sym, owner = ctx.owner, name, flags, completer, privateWithin) - sym.denot = denot - sym - } - } // TODO set position + ctx.newClassSymbol(ctx.owner, name.asTypeName, flags, completer, privateWithin, coord = start.index) + else + ctx.newSymbol(ctx.owner, name, flags, completer, privateWithin, coord = start.index) + } // TODO set position somehow (but take care not to upset Symbol#isDefinedInCurrentRun) sym.annotations = annots ctx.enter(sym) registerSym(start, sym) @@ -474,6 +481,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } + goto(start) sym } @@ -551,7 +559,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { while (currentAddr.index < end.index) { nextByte match { case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM => - val sym = createSymbol() + val sym = symbolAtCurrent() + skipTree() if (sym.isTerm && !sym.is(MethodOrLazyOrDeferred)) initsFlags = EmptyFlags else if (sym.isClass || @@ -586,7 +595,10 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { * `tag` starting at current address. */ def indexParams(tag: Int)(implicit ctx: Context) = - while (nextByte == tag) createSymbol() + while (nextByte == tag) { + symbolAtCurrent() + skipTree() + } /** Create symbols for all type and value parameters of template starting * at current address. @@ -613,7 +625,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { val end = readEnd() def readParams[T <: MemberDef](tag: Int)(implicit ctx: Context): List[T] = { - fork.indexParams(tag) + fork.indexParams(tag)(localContext(sym)) readIndexedParams(tag) } @@ -704,8 +716,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { TermRef.withSig(cls.owner.thisType, cls.name.sourceModuleName, Signature.NotAMethod) else NoType setClsInfo(Nil, assumedSelfType) - val localDummy = ctx.newLocalDummy(cls) - registerSym(start, localDummy) + val localDummy = symbolAtCurrent() assert(readByte() == TEMPLATE) val end = readEnd() val tparams = readIndexedParams[TypeDef](TYPEPARAM) @@ -758,7 +769,6 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } def readTopLevel()(implicit ctx: Context): List[Tree] = { - ownerTree = new OwnerTree(NoAddr, fork, reader.endAddr) @tailrec def read(acc: ListBuffer[Tree]): List[Tree] = nextByte match { case IMPORT | PACKAGE => acc += readIndexedStat(NoSymbol) @@ -1010,32 +1020,62 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } } - class OwnerTree(val addr: Addr, reader: TreeReader, val end: Addr) { + /** 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) { + + /** All definitions that have the definition at `addr` as closest enclosing definition */ lazy val children: List[OwnerTree] = { val buf = new ListBuffer[OwnerTree] - reader.scanTrees(buf, end) + reader.scanTrees(buf, end, if (tag == TEMPLATE) NoMemberDefs else AllDefs) buf.toList } + + /** Find the owner of definition at `addr` */ def findOwner(addr: Addr)(implicit ctx: Context): Symbol = { - //println(s"find owner $addr") - def search(cs: List[OwnerTree], current: Symbol): Symbol = cs match { + def search(cs: List[OwnerTree], current: Symbol): Symbol = + try cs match { case ot :: cs1 => - if (ot.addr.index == addr.index) { - //println(i"search ok $addr, owner = $current") + if (ot.addr.index == addr.index) current - } - else if (ot.addr.index < addr.index && addr.index < ot.end.index) { - val encl = reader.symbolAt(ot.addr) - //println(s"search $addr in ${ot.children} with $encl") - search(ot.children, encl) - } + 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 Error("unattached tree") + throw new TreeWithoutOwner + } + catch { + case ex: TreeWithoutOwner => + println(i"no owner for $addr among $cs") // DEBUG + throw ex } search(children, NoSymbol) } + override def toString = s"OwnerTree(${addr.index}, ${end.index}" } } + +object TreeUnpickler { + + /** 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 +} + + From bef7363e3c04ec444176752e187c62b8d20c8961 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 May 2016 10:52:48 +0200 Subject: [PATCH 14/17] Refine owner trees for templates Include member defs inside templates in the enclosing class, otherwise they would get localDummy as onwer. --- src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 9598fc0c5441..787b89ef8263 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -153,7 +153,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { /** Record all directly nested definitions and templates between current address and `end` * as `OwnerTree`s in `buf` */ - def scanTrees(buf: ListBuffer[OwnerTree], end: Addr, mode: MemberDefMode): Unit = { + def scanTrees(buf: ListBuffer[OwnerTree], end: Addr, mode: MemberDefMode = AllDefs): Unit = { while (currentAddr.index < end.index) scanTree(buf, mode) assert(currentAddr.index == end.index) } From 1b4ca37e624a64a80181efa71ec079b04494986e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 May 2016 14:50:49 +0200 Subject: [PATCH 15/17] Remember owner in completer Otherwise we might get a false owner if completing from somewhere else. We do not have a failing test to demonstrate the problem, but it looks like the right thing to do. --- src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 787b89ef8263..ddc43e640399 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -92,12 +92,12 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { def toTermName(ref: NameRef): TermName = toTermName(tastyName(ref)) def toTypeName(ref: NameRef): TypeName = toTermName(ref).toTypeName - class Completer(reader: TastyReader) extends LazyType { + class Completer(owner: Symbol, reader: TastyReader) extends LazyType { import reader._ def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { treeAtAddr(currentAddr) = new TreeReader(reader).readIndexedDef()( - ctx.withPhaseNoLater(ctx.picklerPhase))//(ctx.withOwner(owner)) + ctx.withPhaseNoLater(ctx.picklerPhase).withOwner(owner)) } } @@ -463,12 +463,12 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { case Some(rootd) => pickling.println(i"overwriting ${rootd.symbol} # ${rootd.hashCode}") rootd.info = adjustIfModule( - new Completer(subReader(start, end)) with SymbolLoaders.SecondCompleter) + new Completer(ctx.owner, subReader(start, end)) with SymbolLoaders.SecondCompleter) rootd.flags = flags &~ Touched // allow one more completion rootd.privateWithin = privateWithin rootd.symbol case _ => - val completer = adjustIfModule(new Completer(subReader(start, end))) + val completer = adjustIfModule(new Completer(ctx.owner, subReader(start, end))) if (isClass) ctx.newClassSymbol(ctx.owner, name.asTypeName, flags, completer, privateWithin, coord = start.index) else From d894be65fa7997e4d854b8812ad23de2173c1f9e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 May 2016 15:50:18 +0200 Subject: [PATCH 16/17] Make isCOmpanion test cheaper As explained in the comment, a scalacLinkedClass must also be a true companion unless the original symbol is a root. This avoids us to drop the (potentially large) unpickled map and save some memory. --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index ddc43e640399..e3e9129a2751 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -41,23 +41,36 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { this.positions = positions } + /** A map from addresses of definition entries to the symbols they define */ private val symAtAddr = new mutable.HashMap[Addr, Symbol] - private val unpickledSyms = new mutable.HashSet[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 treeAtAddr = new mutable.HashMap[Addr, Tree] - private val typeAtAddr = new mutable.HashMap[Addr, Type] // currently populated only for types that are known to be SHAREd. + + /** 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. RefinedTypes, PolyTypes, MethodTypes). + */ + 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 var roots: Set[SymDenotation] = null + /** The root symbols that are defined in this Tasty file. This + * is a subset of `roots.map(_.symbol)`. + */ + private var seenRoots: Set[Symbol] = Set() + /** The root owner tree. See `OwnerTree` class definition. Set by `enterTopLevel`. */ private var ownerTree: OwnerTree = _ - private def registerSym(addr: Addr, sym: Symbol) = { + private def registerSym(addr: Addr, sym: Symbol) = symAtAddr(addr) = sym - unpickledSyms += sym - } /** Enter all toplevel classes and objects into their scopes * @param roots a set of SymDenotations that should be overwritten by unpickling @@ -466,6 +479,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { new Completer(ctx.owner, subReader(start, end)) with SymbolLoaders.SecondCompleter) rootd.flags = flags &~ Touched // allow one more completion rootd.privateWithin = privateWithin + seenRoots += rootd.symbol rootd.symbol case _ => val completer = adjustIfModule(new Completer(ctx.owner, subReader(start, end))) @@ -675,7 +689,14 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { case TYPEDEF | TYPEPARAM => if (sym.isClass) { val companion = sym.scalacLinkedClass - if (companion != NoSymbol && unpickledSyms.contains(companion)) { + + // Is the companion defined in the same Tasty file as `sym`? + // The only case top check here is if `sym` is a root. In this case + // `companion` might have been entered by the environment but it might + // been missing from the Tasty file. So we check explicitly for that. + def isCodefined = + roots.contains(companion.denot) == seenRoots.contains(companion) + if (companion.exists && isCodefined) { import transform.SymUtils._ if (sym is Flags.ModuleClass) sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion) else sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) From c996e42224eedaf3b097fea0c6175eba7d3cbe63 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 May 2016 15:53:57 +0200 Subject: [PATCH 17/17] Fix typos in comments --- src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index e3e9129a2751..91ac4ea3e9fa 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -691,9 +691,9 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { val companion = sym.scalacLinkedClass // Is the companion defined in the same Tasty file as `sym`? - // The only case top check here is if `sym` is a root. In this case + // The only case to check here is if `sym` is a root. In this case // `companion` might have been entered by the environment but it might - // been missing from the Tasty file. So we check explicitly for that. + // be missing from the Tasty file. So we check explicitly for that. def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) if (companion.exists && isCodefined) {