From 1b301e9b8da1fc48b1720cccedafdb7cdb7058a4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Feb 2015 18:29:30 +0100 Subject: New scheme for recording positions Single traverser, also handles lazy trees. --- .../tools/dotc/core/pickling/TreeUnpickler.scala | 695 +++++++++++++++++++++ 1 file changed, 695 insertions(+) create mode 100644 src/dotty/tools/dotc/core/pickling/TreeUnpickler.scala (limited to 'src/dotty/tools/dotc/core/pickling/TreeUnpickler.scala') diff --git a/src/dotty/tools/dotc/core/pickling/TreeUnpickler.scala b/src/dotty/tools/dotc/core/pickling/TreeUnpickler.scala new file mode 100644 index 000000000..c037b4336 --- /dev/null +++ b/src/dotty/tools/dotc/core/pickling/TreeUnpickler.scala @@ -0,0 +1,695 @@ +package dotty.tools +package dotc +package core +package pickling + +import Contexts._, Symbols._, Types._, Scopes._, SymDenotations._, Names._, NameOps._ +import StdNames._, Denotations._, Flags._, Constants._, Annotations._ +import util.Positions._ +import dotty.tools.dotc.ast.{tpd, Trees, untpd} +import Trees._ +import Decorators._ +import TastyUnpickler._, TastyBuffer._ +import annotation.switch +import scala.collection.{ mutable, immutable } +import typer.Mode + +/** Unpickler for typed trees + * @param reader the reader from which to unpickle + * @param tastyName the nametable + * @param roots a set of pre-existing symbols whose attributes should be overwritten + * instead of creating a new symbol. + * @param readPositions a flag indicating whether positions should be read + */ +class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, readPositions: Boolean) { + import dotty.tools.dotc.core.pickling.PickleFormat._ + import TastyName._ + import tpd._ + + private val symAtAddr = new mutable.HashMap[Addr, 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. + + def unpickle()(implicit ctx: Context): List[Tree] = + new TreeReader(reader).readTopLevelStats() + + def toTermName(tname: TastyName): TermName = tname match { + case Simple(name) => name + case Qualified(qual, name) => toTermName(qual) ++ "." ++ toTermName(name) + case Signed(original, params, result) => toTermName(original) + case Expanded(original) => ??? + case ModuleClass(original) => toTermName(original).moduleClassName.toTermName + case SuperAccessor(accessed) => ??? + case DefaultGetter(meth, num) => ??? + } + + def toTermName(ref: NameRef): TermName = toTermName(tastyName(ref)) + def toTypeName(ref: NameRef): TypeName = toTermName(ref).toTypeName + + class Completer(reader: TastyReader) extends LazyType { + import reader._ + def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { + // println(i"complete ${denot.name}") + treeAtAddr(currentAddr) = new TreeReader(reader).readIndexedDef() + } + } + + class TreeReader(val reader: TastyReader) { + import reader._ + + def forkAt(start: Addr) = new TreeReader(subReader(start, endAddr)) + def fork = forkAt(currentAddr) + + def skipTree(tag: Int): Unit = { + // println(s"skipping ${astTagToString(tag)} at $currentAddr") + if (tag >= firstLengthTreeTag) skipTo(readEnd()) + else if (tag >= firstNatASTTreeTag) { readNat(); skipTree() } + else if (tag >= firstNatTreeTag) readNat() + } + def skipTree(): Unit = skipTree(readByte()) + + def skipParams(): Unit = + while (nextByte == PARAMS || nextByte == TYPEPARAM) skipTree() + + def readName(): TermName = toTermName(readNameRef()) + + def readNameSplitSig()(implicit ctx: Context): Any /* TermName | (TermName, Signature) */ = + tastyName(readNameRef()) match { + case Signed(original, params, result) => + var sig = Signature(params map toTypeName, toTypeName(result)) + if (sig == Signature.NotAMethod) sig = Signature.NotAMethod + (toTermName(original), sig) + case name => + toTermName(name) + } + +// ------ Reading types ----------------------------------------------------- + + /** Read names in an interleaved sequence of (parameter) names and types/bounds */ + def readParamNames[N <: Name](end: Addr): List[N] = + until(end) { try readName().asInstanceOf[N] finally skipTree() } + + /** Read types or bounds in an interleaved sequence of (parameter) names and types/bounds */ + def readParamTypes[T <: Type](end: Addr)(implicit ctx: Context): List[T] = + until(end) { readNat(); readType().asInstanceOf[T] } + + /** Read referece to definition and return symbol created at that definition */ + def readSymRef()(implicit ctx: Context): Symbol = symAtAddr(readAddr()) + + /** Read a type */ + def readType()(implicit ctx: Context): Type = { + val start = currentAddr + // println(s"read type at $start") + val tag = readByte() + + def registeringType[T](tp: Type, op: => T): T = { + typeAtAddr(start) = tp + op + } + + def readLengthType(): Type = { + val end = readEnd() + + def readNamesSkipParams[N <: Name]: (List[N], TreeReader) = { + val nameReader = fork + nameReader.skipTree() // skip result + val paramReader = nameReader.fork + (nameReader.readParamNames[N](end), paramReader) + } + + try { + (tag: @switch) match { + case THIS => + ThisType.raw(readType().asInstanceOf[TypeRef]) + case SUPERtype => + SuperType(readType(), readType()) + case REFINEDtype => + val parent = readType() + var name: Name = readName() + if (nextByte == TYPEBOUNDS || nextByte == TYPEALIAS) name = name.toTypeName + RefinedType(parent, name, registeringType(_, readType())) + case APPLIEDtype => + readType().appliedTo(until(end)(readType())) + case TYPEBOUNDS => + TypeBounds(readType(), readType()) + case TYPEALIAS => + TypeAlias(readType()) + case ANNOTATED => + AnnotatedType(Annotation(readTerm()), readType()) + case ANDtype => + AndType(readType(), readType()) + case ORtype => + OrType(readType(), readType()) + case BIND => + symAtAddr(start) = + ctx.newSymbol(ctx.owner, readName().toTypeName, BindDefinedType, readType()) + readType() + case BYNAMEtype => + ExprType(readType()) + case POLYtype => + val (names, paramReader) = readNamesSkipParams[TypeName] + try PolyType(names)( + registeringType(_, paramReader.readParamTypes[TypeBounds](end)), + _ => readType()) + finally skipTo(end) + case METHODtype => + val (names, paramReader) = readNamesSkipParams[TermName] + try MethodType(names, paramReader.readParamTypes[Type](end))( + registeringType(_, readType())) + finally skipTo(end) + case PARAMtype => + readTypeRef() match { + case binder: PolyType => PolyParam(binder, readNat()) + case binder: MethodType => MethodParam(binder, readNat()) + } + case CLASSconst => + readEnd() + ConstantType(Constant(readType())) + case ENUMconst => + readEnd() + ConstantType(Constant(readTermRef().termSymbol)) + } + } + finally assert(currentAddr == end) + } + + def readSimpleType(): Type = (tag: @switch) match { + case TYPEREFdirect | TERMREFdirect => + NamedType.withFixedSym(NoPrefix, readSymRef()) + case TYPEREFsymbol | TERMREFsymbol => + val sym = readSymRef() + NamedType.withFixedSym(readType(), sym) + case TYPEREFpkg => + val name = readName() + val pkg = + if (name == nme.ROOT) defn.RootClass + else if (name == nme.EMPTY_PACKAGE) defn.EmptyPackageClass + else ctx.requiredPackage(name).moduleClass + pkg.typeRef + case TERMREFpkg => + val name = readName() + val pkg = + if (name == nme.ROOT) defn.RootPackage + else if (name == nme.EMPTY_PACKAGE) defn.EmptyPackageVal + else ctx.requiredPackage(name) + pkg.termRef + case TYPEREF => + val name = readName().toTypeName + TypeRef(readType(), name) + case TERMREF => + readNameSplitSig() match { + case name: TermName => TermRef.all(readType(), name) + case (name: TermName, sig: Signature) => TermRef.withSig(readType(), name, sig) + } + case SKOLEMtype => + SkolemType(readTypeRef()) + case NOTYPE => + NoType + case SHARED => + val ref = readAddr() + typeAtAddr.getOrElseUpdate(ref, forkAt(ref).readType()) + case UNITconst => + ConstantType(Constant(())) + case TRUEconst => + ConstantType(Constant(true)) + case FALSEconst => + ConstantType(Constant(false)) + case BYTEconst => + ConstantType(Constant(readInt().toByte)) + case SHORTconst => + ConstantType(Constant(readInt().toShort)) + case CHARconst => + ConstantType(Constant(readNat().toChar)) + case INTconst => + ConstantType(Constant(readInt())) + case LONGconst => + ConstantType(Constant(readLongInt())) + case FLOATconst => + ConstantType(Constant(java.lang.Float.intBitsToFloat(readInt()))) + case DOUBLEconst => + ConstantType(Constant(java.lang.Double.longBitsToDouble(readLongInt()))) + case STRINGconst => + ConstantType(Constant(readName().toString)) + case NULLconst => + ConstantType(Constant(null)) + } + + if (tag < firstLengthTreeTag) readSimpleType() else readLengthType() + } + + def readTypeRef(): Type = + typeAtAddr(readAddr()) + + def readPath()(implicit ctx: Context): Type = { + val tp = readType() + assert(tp.isInstanceOf[SingletonType]) + tp + } + + def readTermRef()(implicit ctx: Context): TermRef = + readType().asInstanceOf[TermRef] + +// ------ Reading definitions ----------------------------------------------------- + + /** Create symbol of definition node and enter in symAtAddr map */ + def createSymbol()(implicit ctx: Context): Unit = { + val start = currentAddr + val tag = readByte() + val end = readEnd() + val name = if (tag == TYPEDEF || tag == TYPEPARAM) readName().toTypeName else readName() + skipParams() + val isAbstractType = nextByte == TYPEBOUNDS + val isClass = nextByte == TEMPLATE + val templateStart = currentAddr + skipTree() + val rhsIsEmpty = + (tag == VALDEF || tag == DEFDEF) && { + val result = nextByte == EMPTYTREE + skipTree() + result + } + val (givenFlags, annots, privateWithin) = readModifiers(end) + println(i"creating symbol $name at $start with flags $givenFlags") + val lacksDefinition = + rhsIsEmpty && !name.isConstructorName && !givenFlags.is(ParamOrAccessor) || + isAbstractType + var flags = givenFlags + if (lacksDefinition) flags |= Deferred + if (ctx.mode.is(Mode.InSuperCall) && !flags.is(ParamOrAccessor)) flags |= InSuperCall + if (ctx.owner.isClass) { + if (tag == TYPEPARAM) flags |= Param + else if (tag == PARAM) flags |= ParamAccessor // TODO: try to unify param and paramaccessor + } + else if (isParamTag(tag)) flags |= Param + val nameMatches = (_: Denotation).symbol.name == name + val prevDenot: SymDenotation = + if (ctx.owner.is(Package)) ctx.effectiveScope.lookup(name) + else NoDenotation // TODO check for double reads + var completer: LazyType = + if (prevDenot.exists) new Completer(subReader(start, end)) with SymbolLoaders.SecondCompleter + else new Completer(subReader(start, end)) + if (flags is Module) completer = ctx.adjustModuleCompleter(completer, name) + val sym = + if (prevDenot.exists) { + println(i"overwriting ${prevDenot.symbol} # ${prevDenot.hashCode}") + prevDenot.info = completer + prevDenot.setFlag(flags) + prevDenot.resetFlag(Touched) // allow one more completion + prevDenot.privateWithin = privateWithin + prevDenot.symbol + } else if (isClass) + 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 + sym.annotations = annots + ctx.enter(sym) + symAtAddr(start) = sym + if (isClass) { + completer.withDecls(newScope) + forkAt(templateStart).indexTemplateParams()(ctx.fresh.setOwner(sym)) + } + } + + /** Read modifier list into triplet of flags, annotations and a privateWithin + * boindary symbol. + */ + def readModifiers(end: Addr)(implicit ctx: Context): (FlagSet, List[Annotation], Symbol) = { + var flags: FlagSet = EmptyFlags + var annots = new mutable.ListBuffer[Annotation] + var privateWithin: Symbol = NoSymbol + while (currentAddr.index != end.index) { + def addFlag(flag: FlagSet) = { + flags |= flag + readByte() + } + nextByte match { + case PRIVATE => addFlag(Private) + case INTERNAL => ??? // addFlag(Internal) + case PROTECTED => addFlag(Protected) + case ABSTRACT => addFlag(Abstract) + case FINAL => addFlag(Final) + case SEALED => addFlag(Sealed) + case CASE => addFlag(Case) + case IMPLICIT => addFlag(Implicit) + case LAZY => addFlag(Lazy) + case OVERRIDE => addFlag(Override) + case INLINE => addFlag(Inline) + case ABSOVERRIDE => addFlag(AbsOverride) + case STATIC => addFlag(JavaStatic) + case MODULE => addFlag(Module) + case LOCAL => addFlag(Local) + case SYNTHETIC => addFlag(Synthetic) + case ARTIFACT => addFlag(Artifact) + case MUTABLE => addFlag(Mutable) + case LABEL => addFlag(Label) + case FIELDaccessor => addFlag(Accessor) + case PARAMaccessor => addFlag(ParamAccessor) + case CASEaccessor => addFlag(CaseAccessor) + case COVARIANT => addFlag(Covariant) + case CONTRAVARIANT => addFlag(Contravariant) + case SCALA2X => addFlag(Scala2x) + case DEFAULTparameterized => addFlag(DefaultParameterized) + case DEFAULTinit => addFlag(DefaultInit) + case INSUPERCALL => addFlag(InSuperCall) + case PRIVATEqualified => + readByte() + readEnd() + privateWithin = readType().typeSymbol + case PROTECTEDqualified => + addFlag(Protected) + readEnd() + privateWithin = readType().typeSymbol + case ANNOTATION => + readByte() + val end = readEnd() + val sym = readType().typeSymbol + val lazyAnnotTree = readLater(end, _.readTerm()) + annots += Annotation.deferred(sym, _ => lazyAnnotTree.complete) + case _ => + assert(false, s"illegal modifier tag at $currentAddr") + } + } + (flags, annots.toList, privateWithin) + } + + /** Create symbols for a definitions in statement sequence between + * current address and `end`. + */ + def indexStats(end: Addr)(implicit ctx: Context) = + until(end) { if (isDefTag(nextByte)) createSymbol() else skipTree() } + + /** Create symbols the longest consecutive sequence of parameters with given + * `tag starting at current address. + */ + def indexParams(tag: Int)(implicit ctx: Context) = + while (nextByte == tag) createSymbol() + + /** Create symbols for all type and value parameters of template starting + * at current address. + */ + def indexTemplateParams()(implicit ctx: Context) = { + assert(readByte() == TEMPLATE) + readEnd() + indexParams(TYPEPARAM) + indexParams(PARAM) + } + + /** If definition was already read by a completer, return the previously read tree + * or else read definition. + */ + def readIndexedDef()(implicit ctx: Context): Tree = treeAtAddr.remove(currentAddr) match { + case Some(tree) => skipTree(); tree + case none => readNewDef() + } + + private def readNewDef()(implicit ctx: Context): Tree = { + val start = currentAddr + // println(s"reading def at $start") + val sym = symAtAddr(start) + val tag = readByte() + val end = readEnd() + + def readParams[T <: MemberDef](tag: Int)(implicit ctx: Context): List[T] = { + fork.indexParams(tag) + readIndexedParams(tag) + } + + def readParamss(implicit ctx: Context): List[List[ValDef]] = { + collectWhile(nextByte == PARAMS) { + readByte() + readEnd() + readParams[ValDef](PARAM) + } + } + + def readRhs(implicit ctx: Context) = readLater(end, _.readTerm()) + + def localCtx = ctx.fresh.setOwner(sym) + + def ta = ctx.typeAssigner + + val name = readName() + // println(s"reading def of $name at $start") + val tree: MemberDef = tag match { + case DEFDEF => + val tparams = readParams[TypeDef](TYPEPARAM)(localCtx) + val vparamss = readParamss(localCtx) + val tpt = readTpt() + val typeParams = tparams.map(_.symbol) + val valueParamss = vparamss.nestedMap(_.symbol) + val resType = ctx.effectiveResultType(sym, typeParams, tpt.tpe) + sym.info = ctx.methodType(typeParams, valueParamss, resType) + ta.assignType( + untpd.DefDef( + sym.name.asTermName, tparams, vparamss, tpt, readRhs(localCtx)), + sym) + case VALDEF => + sym.info = readType() + ValDef(sym.asTerm, readRhs(localCtx)) + case TYPEDEF | TYPEPARAM => + if (sym.isClass) { + val cls = sym.asClass + val impl = readTemplate(localCtx) + cls.info = ClassInfo( + cls.owner.thisType, + cls, + impl.parents.map(_.tpe.asInstanceOf[TypeRef]), + cls.unforcedDecls, + if (impl.self.isEmpty) NoType else impl.self.tpt.tpe) + ta.assignType(untpd.TypeDef(sym.name.asTypeName, impl), sym) + } + else { + sym.info = readType() + TypeDef(sym.asType) + } + case PARAM => + sym.info = readType() + ValDef(sym.asTerm) + } + val mods = + if (sym.annotations.isEmpty) EmptyModifiers + else Modifiers(annotations = sym.annotations.map(_.tree)) + tree.withMods(mods) // record annotations in tree so that tree positions can be filled in. + skipTo(end) + addAddr(start, tree) + } + + private def readTemplate(implicit ctx: Context): Template = { + val start = currentAddr + val cls = ctx.owner.asClass + val localDummy = ctx.newLocalDummy(cls) + assert(readByte() == TEMPLATE) + val end = readEnd() + readIndexedParams(TYPEPARAM) + readIndexedParams(PARAM) + val parents = collectWhile(nextByte != SELFDEF && nextByte != DEFDEF) { + nextByte match { + case APPLY | TYPEAPPLY => readTerm() + case _ => readTpt() + } + } + val self = + if (nextByte == SELFDEF) { + readByte() + readEnd() + untpd.ValDef(readName(), readTpt(), EmptyTree).withType(NoType) + } + else EmptyValDef + fork.indexStats(end) + val constr = readIndexedDef().asInstanceOf[DefDef] + val lazyStats = readLater(end, _.readIndexedStats(localDummy, end)) + addAddr(start, + untpd.Template(constr, parents, self, lazyStats) + .withType(localDummy.nonMemberTermRef)) + } + + def readIndexedStat(exprOwner: Symbol)(implicit ctx: Context): Tree = nextByte match { + case TYPEDEF | VALDEF | DEFDEF | IMPORT => readIndexedDef() + case _ => readTerm()(ctx.withOwner(exprOwner)) + } + + def readStats(exprOwner: Symbol, end: Addr)(implicit ctx: Context): List[Tree] = { + fork.indexStats(end) + readIndexedStats(exprOwner, end) + } + + def readIndexedStats(exprOwner: Symbol, end: Addr)(implicit ctx: Context): List[Tree] = + until(end)(readIndexedStat(exprOwner)) + + def readIndexedParams[T <: MemberDef](tag: Int)(implicit ctx: Context): List[T] = + collectWhile(nextByte == tag) { readIndexedDef().asInstanceOf[T] } + +// ------ Reading terms ----------------------------------------------------- + + def readTerm()(implicit ctx: Context): Tree = { + val start = currentAddr + // println(s"reading term at $start") + val tag = readByte() + + def readSimpleTerm(): Tree = tag match { + case IDENT => + untpd.Ident(readName()).withType(readType()) + case SELECT => + def readQual(name: Name) = { + val localCtx = + if (name == nme.CONSTRUCTOR) ctx.fresh.addMode(Mode.InSuperCall) else ctx + readTerm()(localCtx) + } + readNameSplitSig match { + case name: Name => readQual(name).select(name) + case (name: Name, sig: Signature) => readQual(name).selectWithSig(name, sig) + } + case EMPTYTREE => + EmptyTree + case _ => + ref(readTermRef()) + } + + def readLengthTerm(): Tree = { + val end = readEnd() + + try { + (tag: @switch) match { + case SUPER => + val qual = readTerm() + val mixClass = ifBefore(end)(readType().typeSymbol, NoSymbol) + val mixName = if (mixClass.exists) mixClass.name.asTypeName else tpnme.EMPTY + tpd.Super(qual, mixName, ctx.mode.is(Mode.InSuperCall), mixClass) + case APPLY => + val fn = readTerm() + val isJava = fn.tpe.isInstanceOf[JavaMethodType] + def readArg() = readTerm() match { + case SeqLiteral(elems) if isJava => JavaSeqLiteral(elems) + case arg => arg + } + tpd.Apply(fn, until(end)(readArg())) + case TYPEAPPLY => + tpd.TypeApply(readTerm(), until(end)(readTpt())) + case NEW => + New(readTpt()) + case PAIR => + Pair(readTerm(), readTerm()) + case TYPED => + Typed(readTerm(), readTpt()) + case NAMEDARG => + NamedArg(readName(), readTerm()) + case ASSIGN => + Assign(readTerm(), readTerm()) + case BLOCK => + val exprReader = fork + skipTree() + Block(readStats(ctx.owner, end), exprReader.readTerm())(ctx.fresh.setNewScope) + case IF => + If(readTerm(), readTerm(), readTerm()) + case CLOSURE => + val meth = readTerm() + val tpt = readTpt() + Closure(until(end)(readTerm()), meth, tpt) + case MATCH => + Match(readTerm(), readCases()) + case RETURN => + val from = readSymRef() + val expr = ifBefore(end)(readTerm(), EmptyTree) + Return(expr, Ident(from.termRef)) + case TRY => + Try(readTerm(), readCases(), ifBefore(end)(readTerm(), EmptyTree)) + case THROW => + Throw(readTerm()) + case REPEATED => + SeqLiteral(until(end)(readTerm())) + case BIND => + val name = readName() + val info = readType() + val sym = ctx.newSymbol(ctx.owner, name, EmptyFlags, info) + symAtAddr(start) = sym + Bind(sym, readTerm()) + case ALTERNATIVE => + Alternative(until(end)(readTerm())) + case UNAPPLY => + val fn = readTerm() + val implicitArgs = + collectWhile(nextByte == IMPLICITARG) { + readByte() + readEnd() + readTerm() + } + val patType = readType() + val argPats = until(end)(readTerm()) + UnApply(fn, implicitArgs, argPats, patType) + } + } + finally assert(currentAddr == end, s"$currentAddr $end ${astTagToString(tag)}") + } + + addAddr(start, + if (tag < firstLengthTreeTag) readSimpleTerm() + else readLengthTerm()) + } + + def readTpt()(implicit ctx: Context) = { + val start = currentAddr + addAddr(start, TypeTree(readType())) + } + + def readCases()(implicit ctx: Context): List[CaseDef] = + collectWhile(nextByte == CASEDEF) { + val start = currentAddr + readByte() + val end = readEnd() + val pat = readTerm() + val rhs = readTerm() + val guard = ifBefore(end)(readTerm(), EmptyTree) + addAddr(start, CaseDef(pat, guard, rhs)) + } + + def readTopLevelStats()(implicit ctx: Context): List[Tree] = { + fork.indexStats(endAddr) + until(endAddr) { + if (nextByte == PACKAGE) { + val start = currentAddr + readByte() + val end = readEnd() + val pid = ref(readTermRef()).asInstanceOf[RefTree] + addAddr(start, + PackageDef(pid, readStats(NoSymbol, end)(ctx.fresh.setOwner(pid.symbol.moduleClass)))) + } + else readIndexedStat(ctx.owner) + } + } + + def readLater[T](end: Addr, op: TreeReader => T): Trees.Lazy[T] = { + val localReader = fork + skipTo(end) + new LazyReader(localReader, op) + } + +// ------ Hooks for positions ------------------------------------------------ + + /** A temporary position encoding. + * Start and end fields are each given the address of bytes from which tree is unpickled + * These are later overridden with the actual offsets taken from the Positions section. + */ + def indexPosition(addr: Addr): Position = { + assert(addr.index < MaxOffset) + Position(addr.index, addr.index, 0) + } + + /** Record address from which tree was created as a temporary position in the tree. */ + def addAddr[T <: Tree](addr: Addr, tree: T): T = { + if (readPositions) { tree.setPosUnchecked(indexPosition(addr)); tree } + else tree + } + } + + class LazyReader[T](reader: TreeReader, op: TreeReader => T) extends Trees.Lazy[T] { + var posReader: Option[PositionReader] = None + def complete: T = { + val res = op(reader) + posReader.foreach(_.unpickle(res)) + res + } + } + +} -- cgit v1.2.3