From 750553f7cef359238b64c43116d41fc751f24e1d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 26 May 2018 17:06:59 +0200 Subject: [PATCH 01/21] Introduce UntypedSplice Introduce UntypedSplice and make TreeCopiers and TreeMaps more regular to take splices into account. --- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 4 ++-- compiler/src/dotty/tools/dotc/ast/Trees.scala | 7 +++++-- compiler/src/dotty/tools/dotc/ast/tpd.scala | 21 ++++++++++++++++++- compiler/src/dotty/tools/dotc/ast/untpd.scala | 18 ++++++++++------ .../tools/dotc/printing/RefinedPrinter.scala | 2 ++ .../tools/dotc/transform/MacroTransform.scala | 2 ++ .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 7 files changed, 44 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index e52213cf0485..2bc7ac6b2b13 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -11,7 +11,7 @@ import core.tasty.TreePickler.Hole /** A map that applies three functions and a substitution together to a tree and * makes sure they are coordinated so that the result is well-typed. The functions are - * @param typeMap A function from Type to Type that gets applied to the + * @param typeMap A function from Type to Type that gets applied to the * type of every tree node and to all locally defined symbols, * followed by the substitution [substFrom := substTo]. * @param treeMap A transformer that translates all encountered subtrees in @@ -38,7 +38,7 @@ class TreeTypeMap( val oldOwners: List[Symbol] = Nil, val newOwners: List[Symbol] = Nil, val substFrom: List[Symbol] = Nil, - val substTo: List[Symbol] = Nil)(implicit ctx: Context) extends tpd.TreeMap { + val substTo: List[Symbol] = Nil)(implicit ctx: Context) extends tpd.TypedTreeMap { import tpd._ /** If `sym` is one of `oldOwners`, replace by corresponding symbol in `newOwners` */ diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 1a0d52497085..87c04939617d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1122,6 +1122,9 @@ object Trees { abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { + protected def handleMoreCases(tree: Tree)(implicit ctx: Context): Tree = + if (ctx.reporter.errorsReported) tree else throw new MatchError(tree) + def transform(tree: Tree)(implicit ctx: Context): Tree = { Stats.record(s"TreeMap.transform $getClass") Stats.record("TreeMap.transform total") @@ -1219,8 +1222,8 @@ object Trees { case Thicket(trees) => val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) - case _ if ctx.reporter.errorsReported => - tree + case _ => + handleMoreCases(tree) } } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index e77b56e4c27f..f0051a8bef33 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -324,7 +324,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { case pre: ThisType => tp.isType || pre.cls.isStaticOwner || - tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && ctx.owner.enclosingClass == pre.cls + tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && ctx.owner.enclosingClass == pre.cls // was ctx.owner.enclosingClass.derivesFrom(pre.cls) which was not tight enough // and was spuriously triggered in case inner class would inherit from outer one // eg anonymous TypeMap inside TypeMap.andThen @@ -471,6 +471,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } else foldOver(sym, tree) } + case class UntypedSplice(splice: untpd.Tree) extends Tree + override val cpy: TypedTreeCopier = // Type ascription needed to pick up any new members in TreeCopier (currently there are none) new TypedTreeCopier @@ -609,6 +611,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } + def UntypedSplice(tree: Tree)(splice: untpd.Tree) = tree match { + case tree: tpd.UntypedSplice if tree.splice `eq` splice => tree + case _ => finalize(tree, tpd.UntypedSplice(splice)) + } + override def If(tree: If)(cond: Tree = tree.cond, thenp: Tree = tree.thenp, elsep: Tree = tree.elsep)(implicit ctx: Context): If = If(tree: Tree)(cond, thenp, elsep) override def Closure(tree: Closure)(env: List[Tree] = tree.env, meth: Tree = tree.meth, tpt: Tree = tree.tpt)(implicit ctx: Context): Closure = @@ -638,6 +645,18 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Closure(tree: Tree)(env, meth, tpt) } + class TypedTreeMap(cpy: TypedTreeCopier = tpd.cpy) extends TreeMap(cpy) { self => + override def handleMoreCases(tree: Tree)(implicit ctx: Context) = tree match { + case UntypedSplice(utree) => + val umap = new untpd.UntypedTreeMap() { + override def typedMap = self.transform(_) + } + cpy.UntypedSplice(tree)(umap.transform(utree)) + case _ => + super.handleMoreCases(tree) + } + } + override def skipTransform(tree: Tree)(implicit ctx: Context) = tree.tpe.isError implicit class TreeOps[ThisTree <: tpd.Tree](val tree: ThisTree) extends AnyVal { diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index a7e291431ede..aeb19e740667 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -24,8 +24,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { /** A typed subtree of an untyped tree needs to be wrapped in a TypedSlice * @param owner The current owner at the time the tree was defined */ - abstract case class TypedSplice(tree: tpd.Tree)(val owner: Symbol) extends ProxyTree { - def forwardTo = tree + abstract case class TypedSplice(splice: tpd.Tree)(val owner: Symbol) extends ProxyTree { + def forwardTo = splice } object TypedSplice { @@ -482,10 +482,16 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: PatDef if (mods eq tree.mods) && (pats eq tree.pats) && (tpt eq tree.tpt) && (rhs eq tree.rhs) => tree case _ => finalize(tree, untpd.PatDef(mods, pats, tpt, rhs)) } + def TypedSplice(tree: Tree)(splice: tpd.Tree)(implicit ctx: Context) = tree match { + case tree: TypedSplice if splice `eq` tree.splice => tree + case _ => finalize(tree, untpd.TypedSplice(splice)) + } } abstract class UntypedTreeMap(cpy: UntypedTreeCopier = untpd.cpy) extends TreeMap(cpy) { - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + protected def typedMap: tpd.Tree => tpd.Tree = identity + + override def handleMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match { case ModuleDef(name, impl) => cpy.ModuleDef(tree)(name, transformSub(impl)) case ParsedTry(expr, handler, finalizer) => @@ -526,10 +532,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.ContextBounds(tree)(transformSub(bounds), transform(cxBounds)) case PatDef(mods, pats, tpt, rhs) => cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs)) - case TypedSplice(_) => - tree + case TypedSplice(splice) => + cpy.TypedSplice(tree)(typedMap(splice)) case _ => - super.transform(tree) + super.handleMoreCases(tree) } } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6fd5da3fc623..791abba99f7b 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -447,6 +447,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "" case TypedSplice(t) => toText(t) + case tpd.UntypedSplice(t) => + toText(t) case tree @ ModuleDef(name, impl) => withEnclosingDef(tree) { modText(tree.mods, NoSymbol, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) diff --git a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala index 9d84a2adb66c..a2f27747d87b 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -58,6 +58,8 @@ abstract class MacroTransform extends Phase { transform(parents)(ctx.superCallContext), transformSelf(self), transformStats(impl.body, tree.symbol)) + case UntypedSplice(_) => + tree case _ => super.transform(tree) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9e34deeb2555..1eaaf3968d8e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1648,7 +1648,7 @@ class Typer extends Namer } def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = - tree.tree match { + tree.splice match { case tree1: TypeTree => tree1 // no change owner necessary here ... case tree1: Ident => tree1 // ... or here, since these trees cannot contain bindings case tree1 => From 998c64e55f2a33850696917ec1441928cef77f04 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 26 May 2018 18:05:18 +0200 Subject: [PATCH 02/21] Piggyback unrelated test case --- tests/neg/t10035.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/neg/t10035.scala diff --git a/tests/neg/t10035.scala b/tests/neg/t10035.scala new file mode 100644 index 000000000000..f3f8444bed88 --- /dev/null +++ b/tests/neg/t10035.scala @@ -0,0 +1,11 @@ +trait Inner { + def f(): Outer +} + +class Outer(o: Set[Inner]) { + def this() = this(Set(1).map{ + case k => new Inner { + def f(): Outer = Outer.this // error: Outer is not an enclosing class + } + }) +} From ff838db7c5bf85489689a306d157cf5a734ed8e8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 26 May 2018 19:48:52 +0200 Subject: [PATCH 03/21] More robust scheme for untyped splices - extend scheme to accumulators - no need anymore to define special tpd versions of maps and accumulators - no extra `typeMap` field needed in typeMap; instead the UntypedSplice case is handled directly. --- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 2 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 57 +++++++++++++++---- compiler/src/dotty/tools/dotc/ast/tpd.scala | 17 ------ compiler/src/dotty/tools/dotc/ast/untpd.scala | 24 ++++---- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 2bc7ac6b2b13..ccadcf15d4da 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -38,7 +38,7 @@ class TreeTypeMap( val oldOwners: List[Symbol] = Nil, val newOwners: List[Symbol] = Nil, val substFrom: List[Symbol] = Nil, - val substTo: List[Symbol] = Nil)(implicit ctx: Context) extends tpd.TypedTreeMap { + val substTo: List[Symbol] = Nil)(implicit ctx: Context) extends tpd.TreeMap { import tpd._ /** If `sym` is one of `oldOwners`, replace by corresponding symbol in `newOwners` */ diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 87c04939617d..ff15c23559eb 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1083,6 +1083,10 @@ object Trees { case tree: Annotated if (arg eq tree.arg) && (annot eq tree.annot) => tree case _ => finalize(tree, untpd.Annotated(arg, annot)) } + def UntypedSplice(tree: Tree)(splice: untpd.Tree) = tree match { + case tree: tpd.UntypedSplice if tree.splice `eq` splice => tree + case _ => finalize(tree, tpd.UntypedSplice(splice)) + } def Thicket(tree: Tree)(trees: List[Tree]): Thicket = tree match { case tree: Thicket if trees eq tree.trees => tree case _ => finalize(tree, untpd.Thicket(trees)) @@ -1120,10 +1124,7 @@ object Trees { */ protected def inlineContext(call: Tree)(implicit ctx: Context): Context = ctx - abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { - - protected def handleMoreCases(tree: Tree)(implicit ctx: Context): Tree = - if (ctx.reporter.errorsReported) tree else throw new MatchError(tree) + abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { self => def transform(tree: Tree)(implicit ctx: Context): Tree = { Stats.record(s"TreeMap.transform $getClass") @@ -1223,7 +1224,7 @@ object Trees { val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) case _ => - handleMoreCases(tree) + transformMoreCases(tree) } } @@ -1235,9 +1236,26 @@ object Trees { transform(tree).asInstanceOf[Tr] def transformSub[Tr <: Tree](trees: List[Tr])(implicit ctx: Context): List[Tr] = transform(trees).asInstanceOf[List[Tr]] + + protected def transformMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match { + case tpd.UntypedSplice(usplice) => + // For a typed tree map: homomorphism on the untyped part with + // recursive mapping of typed splices. + // The case is overridden in UntypedTreeMap.## + val untpdMap = new untpd.UntypedTreeMap { + override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = tree match { + case untpd.TypedSplice(tsplice) => + untpd.cpy.TypedSplice(tree)(self.transform(tsplice).asInstanceOf[tpd.Tree]) + // the cast is safe, since the UntypedSplice case is overridden in UntypedTreeMap. + case _ => super.transform(tree) + } + } + cpy.UntypedSplice(tree)(untpdMap.transform(usplice)) + case _ if ctx.reporter.errorsReported => tree + } } - abstract class TreeAccumulator[X] { + abstract class TreeAccumulator[X] { self => // Ties the knot of the traversal: call `foldOver(x, tree))` to dive in the `tree` node. def apply(x: X, tree: Tree)(implicit ctx: Context): X @@ -1332,14 +1350,29 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) - case _ if ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive) => - // In interactive mode, errors might come from previous runs. - // In case of errors it may be that typed trees point to untyped ones. - // The IDE can still traverse inside such trees, either in the run where errors - // are reported, or in subsequent ones. - x + case _ => + foldMoreCases(x, tree) } } + + def foldMoreCases(x: X, tree: Tree)(implicit ctx: Context): X = tree match { + case tpd.UntypedSplice(usplice) => + // For a typed tree accumulator: skip the untyped part and fold all typed splices. + // The case is overridden in UntypedTreeAccumulator. + val untpdAcc = new untpd.UntypedTreeAccumulator[X] { + override def apply(x: X, tree: untpd.Tree)(implicit ctx: Context): X = tree match { + case untpd.TypedSplice(tsplice) => self(x, tsplice) + case _ => foldOver(x, tree) + } + } + untpdAcc(x, usplice) + case _ if ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive) => + // In interactive mode, errors might come from previous runs. + // In case of errors it may be that typed trees point to untyped ones. + // The IDE can still traverse inside such trees, either in the run where errors + // are reported, or in subsequent ones. + x + } } abstract class TreeTraverser extends TreeAccumulator[Unit] { diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index f0051a8bef33..adaca39dbe29 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -611,11 +611,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } - def UntypedSplice(tree: Tree)(splice: untpd.Tree) = tree match { - case tree: tpd.UntypedSplice if tree.splice `eq` splice => tree - case _ => finalize(tree, tpd.UntypedSplice(splice)) - } - override def If(tree: If)(cond: Tree = tree.cond, thenp: Tree = tree.thenp, elsep: Tree = tree.elsep)(implicit ctx: Context): If = If(tree: Tree)(cond, thenp, elsep) override def Closure(tree: Closure)(env: List[Tree] = tree.env, meth: Tree = tree.meth, tpt: Tree = tree.tpt)(implicit ctx: Context): Closure = @@ -645,18 +640,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Closure(tree: Tree)(env, meth, tpt) } - class TypedTreeMap(cpy: TypedTreeCopier = tpd.cpy) extends TreeMap(cpy) { self => - override def handleMoreCases(tree: Tree)(implicit ctx: Context) = tree match { - case UntypedSplice(utree) => - val umap = new untpd.UntypedTreeMap() { - override def typedMap = self.transform(_) - } - cpy.UntypedSplice(tree)(umap.transform(utree)) - case _ => - super.handleMoreCases(tree) - } - } - override def skipTransform(tree: Tree)(implicit ctx: Context) = tree.tpe.isError implicit class TreeOps[ThisTree <: tpd.Tree](val tree: ThisTree) extends AnyVal { diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index aeb19e740667..31fb46a3f976 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -489,9 +489,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { } abstract class UntypedTreeMap(cpy: UntypedTreeCopier = untpd.cpy) extends TreeMap(cpy) { - protected def typedMap: tpd.Tree => tpd.Tree = identity - - override def handleMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match { + override def transformMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match { case ModuleDef(name, impl) => cpy.ModuleDef(tree)(name, transformSub(impl)) case ParsedTry(expr, handler, finalizer) => @@ -532,15 +530,17 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.ContextBounds(tree)(transformSub(bounds), transform(cxBounds)) case PatDef(mods, pats, tpt, rhs) => cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs)) - case TypedSplice(splice) => - cpy.TypedSplice(tree)(typedMap(splice)) + case tpd.UntypedSplice(splice) => + cpy.UntypedSplice(tree)(transform(splice)) + case TypedSplice(_) => + tree case _ => - super.handleMoreCases(tree) + super.transformMoreCases(tree) } } - abstract class UntypedTreeAccumulator[X] extends TreeAccumulator[X] { - override def foldOver(x: X, tree: Tree)(implicit ctx: Context): X = tree match { + abstract class UntypedTreeAccumulator[X] extends TreeAccumulator[X] { self => + override def foldMoreCases(x: X, tree: Tree)(implicit ctx: Context): X = tree match { case ModuleDef(name, impl) => this(x, impl) case ParsedTry(expr, handler, finalizer) => @@ -581,10 +581,12 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { this(this(x, bounds), cxBounds) case PatDef(mods, pats, tpt, rhs) => this(this(this(x, pats), tpt), rhs) - case TypedSplice(tree) => - this(x, tree) + case TypedSplice(splice) => + this(x, splice) + case tpd.UntypedSplice(splice) => + this(x, splice) case _ => - super.foldOver(x, tree) + super.foldMoreCases(x, tree) } } From 02918cda77fd847deaf50ea7e1e3b6eca230742d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 28 May 2018 11:28:33 +0200 Subject: [PATCH 04/21] Make TreePickler untpd friendly Some refactorings so that it will become easier to generate untyped trees --- .../tools/dotc/core/tasty/TreePickler.scala | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 4c4ab3c72147..0c18ef5a61bc 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -504,16 +504,7 @@ class TreePickler(pickler: TastyPickler) { } case Import(expr, selectors) => writeByte(IMPORT) - withLength { - pickleTree(expr) - selectors foreach { - case Thicket((from @ Ident(_)) :: (to @ Ident(_)) :: Nil) => - pickleSelector(IMPORTED, from) - pickleSelector(RENAMED, to) - case id @ Ident(_) => - pickleSelector(IMPORTED, id) - } - } + withLength { pickleTree(expr); pickleSelectors(selectors) } case PackageDef(pid, stats) => writeByte(PACKAGE) withLength { pickleType(pid.tpe); pickleStats(stats) } @@ -545,7 +536,7 @@ class TreePickler(pickler: TastyPickler) { pickleTree(tp) case Annotated(tree, annot) => writeByte(ANNOTATEDtpt) - withLength { pickleTree(tree); pickleTree(annot.tree) } + withLength { pickleTree(tree); pickleTree(annot) } case LambdaTypeTree(tparams, body) => writeByte(LAMBDAtpt) withLength { pickleParams(tparams); pickleTree(body) } @@ -569,6 +560,15 @@ class TreePickler(pickler: TastyPickler) { } } + def pickleSelectors(selectors: List[untpd.Tree])(implicit ctx: Context): Unit = + selectors foreach { + case Thicket((from @ Ident(_)) :: (to @ Ident(_)) :: Nil) => + pickleSelector(IMPORTED, from) + pickleSelector(RENAMED, to) + case id @ Ident(_) => + pickleSelector(IMPORTED, id) + } + def pickleSelector(tag: Int, id: untpd.Ident)(implicit ctx: Context): Unit = { registerTreeAddr(id) writeByte(tag) @@ -577,15 +577,24 @@ class TreePickler(pickler: TastyPickler) { def pickleModifiers(sym: Symbol)(implicit ctx: Context): Unit = { import Flags._ - val flags = sym.flags + var flags = sym.flags val privateWithin = sym.privateWithin if (privateWithin.exists) { writeByte(if (flags is Protected) PROTECTEDqualified else PRIVATEqualified) pickleType(privateWithin.typeRef) + flags = flags &~ Protected } + if ((flags is ParamAccessor) && sym.isTerm && !sym.isSetter) + flags = flags &~ ParamAccessor // we only generate a tag for parameter setters + pickleFlags(flags, sym.isTerm) + sym.annotations.foreach(pickleAnnotation(sym, _)) + } + + def pickleFlags(flags: Flags.FlagSet, isTerm: Boolean)(implicit ctx: Context): Unit = { + import Flags._ if (flags is Private) writeByte(PRIVATE) - if (flags is Protected) if (!privateWithin.exists) writeByte(PROTECTED) - if ((flags is Final) && !(sym is Module)) writeByte(FINAL) + if (flags is Protected) writeByte(PROTECTED) + if (flags.is(Final, butNot = Module)) writeByte(FINAL) if (flags is Case) writeByte(CASE) if (flags is Override) writeByte(OVERRIDE) if (flags is Inline) writeByte(INLINE) @@ -596,18 +605,18 @@ class TreePickler(pickler: TastyPickler) { if (flags is Synthetic) writeByte(SYNTHETIC) if (flags is Artifact) writeByte(ARTIFACT) if (flags is Scala2x) writeByte(SCALA2X) - if (sym.isTerm) { + if (isTerm) { if (flags is Implicit) writeByte(IMPLICIT) if (flags is Erased) writeByte(ERASED) - if ((flags is Lazy) && !(sym is Module)) writeByte(LAZY) + if (flags.is(Lazy, butNot = Module)) writeByte(LAZY) if (flags is AbsOverride) { writeByte(ABSTRACT); writeByte(OVERRIDE) } if (flags is Mutable) writeByte(MUTABLE) if (flags is Accessor) writeByte(FIELDaccessor) if (flags is CaseAccessor) writeByte(CASEaccessor) if (flags is DefaultParameterized) writeByte(DEFAULTparameterized) if (flags is Stable) writeByte(STABLE) - if ((flags is ParamAccessor) && sym.isSetter) writeByte(PARAMsetter) - if ((flags is Label)) writeByte(LABEL) + if (flags is ParamAccessor) writeByte(PARAMsetter) + if (flags is Label) writeByte(LABEL) } else { if (flags is Sealed) writeByte(SEALED) if (flags is Abstract) writeByte(ABSTRACT) @@ -615,7 +624,6 @@ class TreePickler(pickler: TastyPickler) { if (flags is Covariant) writeByte(COVARIANT) if (flags is Contravariant) writeByte(CONTRAVARIANT) } - sym.annotations.foreach(pickleAnnotation(sym, _)) } private def isUnpicklable(owner: Symbol, ann: Annotation)(implicit ctx: Context) = ann match { From f7f0fab16a39c2269c7f03025382471c85c08299 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 28 May 2018 11:41:44 +0200 Subject: [PATCH 05/21] Drop EnumCase modifier Use combination of Enum and Case instead. --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 8 ++++---- compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala | 6 +++--- compiler/src/dotty/tools/dotc/ast/untpd.scala | 5 +++-- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index d4582ac695c0..bad09c51b74b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -297,8 +297,8 @@ object desugar { val isCaseClass = mods.is(Case) && !mods.is(Module) val isCaseObject = mods.is(Case) && mods.is(Module) val isImplicit = mods.is(Implicit) - val isEnum = mods.hasMod[Mod.Enum] && !mods.is(Module) - val isEnumCase = mods.hasMod[Mod.EnumCase] + val isEnum = mods.isEnumClass && !mods.is(Module) + def isEnumCase = mods.isEnumCase val isValueClass = parents.nonEmpty && isAnyVal(parents.head) // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. @@ -641,7 +641,7 @@ object desugar { val moduleName = checkNotReservedName(mdef).asTermName val impl = mdef.impl val mods = mdef.mods - lazy val isEnumCase = mods.hasMod[Mod.EnumCase] + def isEnumCase = mods.isEnumCase if (mods is Package) PackageDef(Ident(moduleName), cpy.ModuleDef(mdef)(nme.PACKAGE, impl).withMods(mods &~ Package) :: Nil) else if (isEnumCase) @@ -688,7 +688,7 @@ object desugar { */ def patDef(pdef: PatDef)(implicit ctx: Context): Tree = flatTree { val PatDef(mods, pats, tpt, rhs) = pdef - if (mods.hasMod[Mod.EnumCase]) + if (mods.isEnumCase) pats map { case id: Ident => expandSimpleEnumCase(id.name.asTermName, mods, diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 925d3f74c415..ebb3157a5733 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -33,8 +33,8 @@ object DesugarEnums { /** Is `tree` an (untyped) enum case? */ def isEnumCase(tree: Tree)(implicit ctx: Context): Boolean = tree match { - case tree: MemberDef => tree.mods.hasMod[Mod.EnumCase] - case PatDef(mods, _, _, _) => mods.hasMod[Mod.EnumCase] + case tree: MemberDef => tree.mods.isEnumCase + case PatDef(mods, _, _, _) => mods.isEnumCase case _ => false } @@ -69,7 +69,7 @@ object DesugarEnums { /** Add implied flags to an enum class or an enum case */ def addEnumFlags(cdef: TypeDef)(implicit ctx: Context) = - if (cdef.mods.hasMod[Mod.Enum]) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Abstract | Sealed)) + if (cdef.mods.isEnumClass) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Abstract | Sealed)) else if (isEnumCase(cdef)) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Final)) else cdef diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 31fb46a3f976..4b2173aaf3bb 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -131,8 +131,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Inline() extends Mod(Flags.Inline) case class Enum() extends Mod(Flags.EmptyFlags) - - case class EnumCase() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions @@ -190,6 +188,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { val cls = implicitly[ClassTag[T]].runtimeClass mods.exists(mod => cls.isAssignableFrom(mod.getClass)) } + + def isEnumCase = hasMod[Mod.Enum] && is(Case) + def isEnumClass = hasMod[Mod.Enum] && !is(Case) } @sharable val EmptyModifiers: Modifiers = new Modifiers() diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 560699232edf..e4844cf2561a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2270,7 +2270,7 @@ object Parsers { /** EnumCase = `case' (id ClassConstr [`extends' ConstrApps] | ids) */ def enumCase(start: Offset, mods: Modifiers): DefTree = { - val mods1 = mods.withAddedMod(atPos(in.offset)(Mod.EnumCase())) | Case + val mods1 = mods.withAddedMod(atPos(in.offset)(Mod.Enum())) | Case accept(CASE) in.adjustSepRegions(ARROW) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 7c5d0894a18a..5854a2cdeab9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -819,7 +819,7 @@ trait Checking { cls.isAnonymousClass && cls.owner.isTerm && (cls.owner.flagsUNSAFE.is(Case) || cls.owner.name == nme.DOLLAR_NEW) - if (!cdef.mods.hasMod[untpd.Mod.EnumCase] && !isEnumAnonCls) + if (!cdef.mods.isEnumCase && !isEnumAnonCls) ctx.error(em"normal case $cls in ${cls.owner} cannot extend an enum", cdef.pos) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1eaaf3968d8e..9e1222521e89 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1886,7 +1886,7 @@ class Typer extends Namer case mdef1 => import untpd.modsDeco mdef match { - case mdef: untpd.TypeDef if mdef.mods.hasMod[untpd.Mod.Enum] => + case mdef: untpd.TypeDef if mdef.mods.isEnumClass => enumContexts(mdef1.symbol) = ctx case _ => } From 90bd0de5953605fbcd8e91242023e062a55834b0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 28 May 2018 11:56:50 +0200 Subject: [PATCH 06/21] Liberate Enum flag so that it can be used for Scala enums as well. --- .../dotty/tools/backend/jvm/DottyBackendInterface.scala | 4 ++-- compiler/src/dotty/tools/dotc/core/Flags.scala | 9 +++++++++ .../tools/dotc/core/classfile/ClassfileParser.scala | 4 ++-- compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala | 4 ++-- .../src/dotty/tools/dotc/transform/patmat/Space.scala | 6 +++--- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 32061e0d7561..c3ad297c860b 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -258,7 +258,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma case t: TypeApply if (t.fun.symbol == Predef_classOf) => av.visit(name, t.args.head.tpe.classSymbol.denot.info.toTypeKind(bcodeStore)(innerClasesStore).toASMType) case t: tpd.Select => - if (t.symbol.denot.owner.is(Flags.Enum)) { + if (t.symbol.denot.owner.is(Flags.JavaEnum)) { val edesc = innerClasesStore.typeDescriptor(t.tpe.asInstanceOf[bcodeStore.int.Type]) // the class descriptor of the enumeration class. val evalue = t.symbol.name.mangledString // value the actual enumeration value. av.visitEnum(name, edesc, evalue) @@ -710,7 +710,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def isBottomClass: Boolean = (sym ne defn.NullClass) && (sym ne defn.NothingClass) def isBridge: Boolean = sym is Flags.Bridge def isArtifact: Boolean = sym is Flags.Artifact - def hasEnumFlag: Boolean = sym is Flags.Enum + def hasEnumFlag: Boolean = sym is Flags.JavaEnum def hasAccessBoundary: Boolean = sym.accessBoundary(defn.RootClass) ne defn.RootClass def isVarargsMethod: Boolean = sym is Flags.JavaVarargs def isDeprecated: Boolean = false diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 80630f3afa18..e51c1c916c54 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -620,6 +620,15 @@ object Flags { /** A Java companion object */ final val JavaProtected = allOf(JavaDefined, Protected) + /** A Java enum */ + final val JavaEnum = allOf(JavaDefined, Enum) + + /** A Java enum trait */ + final val JavaEnumTrait = allOf(JavaDefined, Enum) + + /** A Java enum value */ + final val JavaEnumValue = allOf(Stable, JavaStatic, JavaDefined, Enum) + /** Labeled private[this] */ final val PrivateLocal = allOf(Private, Local) diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 00e73fcd4d1e..86d7866041ad 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -186,8 +186,8 @@ class ClassfileParser( if (isEnum) { instanceScope.toList.map(_.ensureCompleted()) staticScope.toList.map(_.ensureCompleted()) - classRoot.setFlag(Flags.Enum) - moduleRoot.setFlag(Flags.Enum) + classRoot.setFlag(Flags.JavaEnum) + moduleRoot.setFlag(Flags.JavaEnum) } result diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index a39c281d200f..5c203d123f4a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -842,7 +842,7 @@ object JavaParsers { List(Literal(Constant(null)),Literal(Constant(0)))) val enumclazz = atPos(start, nameOffset) { TypeDef(name, - makeTemplate(superclazz :: interfaces, body, List(), true)).withMods(mods | Flags.Enum) + makeTemplate(superclazz :: interfaces, body, List(), true)).withMods(mods | Flags.JavaEnum) } addCompanionObject(consts ::: statics ::: predefs, enumclazz) } @@ -861,7 +861,7 @@ object JavaParsers { skipAhead() accept(RBRACE) } - ValDef(name.toTermName, enumType, unimplementedExpr).withMods(Modifiers(Flags.Enum | Flags.Stable | Flags.JavaDefined | Flags.JavaStatic)) + ValDef(name.toTermName, enumType, unimplementedExpr).withMods(Modifiers(Flags.JavaEnum | Flags.Stable | Flags.JavaDefined | Flags.JavaStatic)) } } diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 41989b04e66f..a3cfefb12d07 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -460,7 +460,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { ) case tp if tp.isRef(defn.UnitClass) => Typ(ConstantType(Constant(())), true) :: Nil - case tp if tp.classSymbol.is(Enum) => + case tp if tp.classSymbol.is(JavaEnum) => children.map(sym => Typ(sym.termRef, true)) case tp => val parts = children.map { sym => @@ -730,7 +730,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { }) || tp.isRef(defn.BooleanClass) || tp.isRef(defn.UnitClass) || - tp.classSymbol.is(allOf(Enum, Sealed)) // Enum value doesn't have Sealed flag + tp.classSymbol.is(JavaEnumTrait) debug.println(s"decomposable: ${tp.show} = $res") @@ -874,7 +874,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { isCheckable(and.tp1) || isCheckable(and.tp2) }) || tpw.isRef(defn.BooleanClass) || - tpw.typeSymbol.is(Enum) || + tpw.typeSymbol.is(JavaEnum) || canDecompose(tpw) || (defn.isTupleType(tpw) && tpw.argInfos.exists(isCheckable(_))) } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 2d6ecae78fe4..592006e2b57f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -494,7 +494,7 @@ class Namer { typer: Typer => // We don't check for clazz.superClass == JavaEnumClass, because this causes a illegal // cyclic reference error. See the commit message for details. // if (ctx.compilationUnit.isJava) ctx.owner.companionClass.is(Enum) else ctx.owner.is(Enum) - vd.mods.is(allOf(Enum, Stable, JavaStatic, JavaDefined)) // && ownerHasEnumFlag + vd.mods.is(JavaEnumValue) // && ownerHasEnumFlag } /** Add java enum constants */ From 1ded402b9d78e96f9b43398dcb2f0ed209ca1612 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 28 May 2018 14:54:51 +0200 Subject: [PATCH 07/21] Re-use Enum flag for Scala enums --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 8 +++++--- compiler/src/dotty/tools/dotc/core/Flags.scala | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 4b2173aaf3bb..219b468e9391 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -130,7 +130,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Inline() extends Mod(Flags.Inline) - case class Enum() extends Mod(Flags.EmptyFlags) + case class Enum() extends Mod(Flags.Enum) } /** Modifiers and annotations for definitions @@ -189,8 +189,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { mods.exists(mod => cls.isAssignableFrom(mod.getClass)) } - def isEnumCase = hasMod[Mod.Enum] && is(Case) - def isEnumClass = hasMod[Mod.Enum] && !is(Case) + private def isEnum = hasMod[Mod.Enum] || is(Enum, butNot = JavaDefined) + + def isEnumCase = isEnum && is(Case) + def isEnumClass = isEnum && !is(Case) } @sharable val EmptyModifiers: Modifiers = new Modifiers() diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index e51c1c916c54..ad052f84c7fb 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -563,6 +563,9 @@ object Flags { /** An inline parameter */ final val InlineParam = allOf(Inline, Param) + /** An enum case */ + final val EnumCase = allOf(Enum, Case) + /** A term parameter or parameter accessor */ final val TermParamOrAccessor = Param | ParamAccessor From a669acc585adb94d219c428b9bbec28d687febf6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 29 May 2018 12:31:30 +0200 Subject: [PATCH 08/21] Pickle Enum flag It's useful to know something was an enum when it started, and it's necessary to pickle this when serializing untyped trees. --- .../tools/dotc/core/tasty/TastyFormat.scala | 32 ++++++++++--------- .../tools/dotc/core/tasty/TreePickler.scala | 1 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 1 + 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 6e25c37c0f71..e389e9e081c9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -184,6 +184,7 @@ Standard-Section: "ASTs" TopLevelStat* STATIC // mapped to static Java member OBJECT // an object or its class TRAIT // a trait + ENUM // a enum class or enum case LOCAL // private[this] or protected[this] SYNTHETIC // generated by Scala compiler ARTIFACT // to be tagged Java Synthetic @@ -282,21 +283,22 @@ object TastyFormat { final val STATIC = 17 final val OBJECT = 18 final val TRAIT = 19 - final val LOCAL = 20 - final val SYNTHETIC = 21 - final val ARTIFACT = 22 - final val MUTABLE = 23 - final val LABEL = 24 - final val FIELDaccessor = 25 - final val CASEaccessor = 26 - final val COVARIANT = 27 - final val CONTRAVARIANT = 28 - final val SCALA2X = 29 - final val DEFAULTparameterized = 30 - final val STABLE = 31 - final val MACRO = 32 - final val ERASED = 33 - final val PARAMsetter = 34 + final val ENUM = 20 + final val LOCAL = 21 + final val SYNTHETIC = 22 + final val ARTIFACT = 23 + final val MUTABLE = 24 + final val LABEL = 25 + final val FIELDaccessor = 26 + final val CASEaccessor = 27 + final val COVARIANT = 28 + final val CONTRAVARIANT = 29 + final val SCALA2X = 30 + final val DEFAULTparameterized = 31 + final val STABLE = 32 + final val MACRO = 33 + final val ERASED = 34 + final val PARAMsetter = 35 // Cat. 2: tag Nat diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0c18ef5a61bc..46d6d0b93ae2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -601,6 +601,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is Macro) writeByte(MACRO) if (flags is JavaStatic) writeByte(STATIC) if (flags is Module) writeByte(OBJECT) + if (flags is Enum) writeByte(ENUM) if (flags is Local) writeByte(LOCAL) if (flags is Synthetic) writeByte(SYNTHETIC) if (flags is Artifact) writeByte(ARTIFACT) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 5b35002530f7..b249049c43c9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -591,6 +591,7 @@ class TreeUnpickler(reader: TastyReader, case STATIC => addFlag(JavaStatic) case OBJECT => addFlag(Module) case TRAIT => addFlag(Trait) + case ENUM => addFlag(Enum) case LOCAL => addFlag(Local) case SYNTHETIC => addFlag(Synthetic) case ARTIFACT => addFlag(Artifact) From 1accaf68dae918baa997f24750dcbb082dbe6db8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 29 May 2018 15:37:13 +0200 Subject: [PATCH 09/21] More debug options for printing - Mark splices in code under -print-debug - Print owners of all definitions under -print-debug-owners --- .../dotty/tools/dotc/config/ScalaSettings.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index bbbfb88262f4..fdbfe3011a8d 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -87,7 +87,6 @@ class ScalaSettings extends Settings.SettingGroup { val YdebugFlags = BooleanSetting("-Ydebug-flags", "Print all flags of definitions") val YdebugMissingRefs = BooleanSetting("-Ydebug-missing-refs", "Print a stacktrace when a required symbol is missing") val YdebugNames = BooleanSetting("-Ydebug-names", "Show internal representation of names") - val YdebugOwners = BooleanSetting("-Ydebug-owners", "Print all owners of definitions (requires -Yprint-syms)") val YtermConflict = ChoiceSetting("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error") val Ylog = PhasesSetting("-Ylog", "Log operations during") val YemitTasty = BooleanSetting("-Yemit-tasty", "Generate tasty in separate *.tasty file.") @@ -113,6 +112,7 @@ class ScalaSettings extends Settings.SettingGroup { val YplainPrinter = BooleanSetting("-Yplain-printer", "Pretty-print using a plain printer.") val YprintSyms = BooleanSetting("-Yprint-syms", "when printing trees print info in symbols instead of corresponding info in trees.") val YprintDebug = BooleanSetting("-Yprint-debug", "when printing trees, print some extra information useful for debugging.") + val YprintDebugOwners = BooleanSetting("-Yprint-debug-owners", "when printing trees, print owners of definitions.") val YshowPrintErrors = BooleanSetting("-Yshow-print-errors", "don't suppress exceptions thrown during tree printing.") val YtestPickler = BooleanSetting("-Ytest-pickler", "self-test for pickling functionality; should be used with -Ystop-after:pickler") val YcheckReentrant = BooleanSetting("-Ycheck-reentrant", "check that compiled program does not contain vars that can be accessed from a global root.") diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 791abba99f7b..13b325527517 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -446,9 +446,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case EmptyTree => "" case TypedSplice(t) => - toText(t) + if (ctx.settings.YprintDebug.value) "[" ~ toText(t) ~ "]#TS#" + else toText(t) case tpd.UntypedSplice(t) => - toText(t) + if (ctx.settings.YprintDebug.value) "[" ~ toText(t) ~ ":" ~ toText(tree.typeOpt) ~ "]#US#" + else toText(t) case tree @ ModuleDef(name, impl) => withEnclosingDef(tree) { modText(tree.mods, NoSymbol, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) @@ -605,11 +607,14 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else toText(tree.name) ~ idText(tree) } + private def toTextOwner(tree: Tree[_]) = + "[owner = " ~ tree.symbol.owner.show ~ "]" provided ctx.settings.YprintDebugOwners.value + protected def dclTextOr[T >: Untyped](tree: Tree[T])(treeText: => Text) = - if (useSymbol(tree)) - annotsText(tree.symbol) ~~ dclText(tree.symbol) ~ - ( " " provided ctx.settings.YdebugOwners.value) - else treeText + toTextOwner(tree) ~ { + if (useSymbol(tree)) annotsText(tree.symbol) ~~ dclText(tree.symbol) + else treeText + } def tparamsText[T >: Untyped](params: List[Tree[T]]): Text = "[" ~ toText(params, ", ") ~ "]" provided params.nonEmpty From 3d3a4cb44e2c687b0a43a607c3df0af96e5bfbac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 29 May 2018 16:37:52 +0200 Subject: [PATCH 10/21] Add untyped types to Tasty Allow UNTYPEDSPLICE roots with TYPEDSPLICE subtrees. Trees between them are untyped. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- .../tools/dotc/core/tasty/TastyFormat.scala | 40 ++- .../tools/dotc/core/tasty/TreePickler.scala | 211 ++++++++++- .../tools/dotc/core/tasty/TreeUnpickler.scala | 327 ++++++++++++++---- 4 files changed, 506 insertions(+), 74 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index adaca39dbe29..b2ecf2ca24a2 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -642,7 +642,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { override def skipTransform(tree: Tree)(implicit ctx: Context) = tree.tpe.isError - implicit class TreeOps[ThisTree <: tpd.Tree](val tree: ThisTree) extends AnyVal { + implicit class TreeOps[ThisTree <: tpd.Tree](private val tree: ThisTree) extends AnyVal { def isValue(implicit ctx: Context): Boolean = tree.isTerm && tree.tpe.widen.isValueType diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index e389e9e081c9..9726d15d0dc0 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -55,21 +55,22 @@ Standard-Section: "ASTs" TopLevelStat* Stat Stat = Term - VALDEF Length NameRef type_Term rhs_Term? Modifier* + VALDEF Length NameRef type_Term rhs_Term? Mods DEFDEF Length NameRef TypeParam* Params* returnType_Term rhs_Term? - Modifier* - TYPEDEF Length NameRef (type_Term | Template) Modifier* + Mods + TYPEDEF Length NameRef (type_Term | Template) Mods + OBJECTDEF Length NameRef Template Mods IMPORT Length qual_Term Selector* Selector = IMPORTED name_NameRef RENAMED to_NameRef // Imports are for scala.meta, they are not used in the backend - TypeParam = TYPEPARAM Length NameRef Type Modifier* + TypeParam = TYPEPARAM Length NameRef type_Term Mods Params = PARAMS Length Param* - Param = PARAM Length NameRef Type rhs_Term? Modifier* // rhs_Term is present in the case of an aliased class parameter + Param = PARAM Length NameRef type_Term rhs_Term? Mods // rhs_Term is present in the case of an aliased class parameter Template = TEMPLATE Length TypeParam* Param* parent_Term* Self? Stat* // Stat* always starts with the primary constructor. - Self = SELFDEF selfName_NameRef selfType_Type + Self = SELFDEF selfName_NameRef selfType_Term Term = Path IDENT NameRef Type // used when term ident’s type is not a TermRef @@ -109,6 +110,7 @@ Standard-Section: "ASTs" TopLevelStat* EMPTYTREE SHAREDterm term_ASTRef HOLE Length idx_Nat arg_Tree* + UNTYPEDSPLICE Length splice_TermUntyped splice_Type CaseDef = CASEDEF Length pat_Term rhs_Tree guard_Tree? ImplicitArg = IMPLICITARG arg_Term @@ -166,6 +168,8 @@ Standard-Section: "ASTs" TopLevelStat* NamesTypes = NameType* NameType = paramName_NameRef typeOrBounds_ASTRef + Mods = Modifier* Annotation* + Modifier = PRIVATE INTERNAL // package private PROTECTED @@ -202,6 +206,13 @@ Standard-Section: "ASTs" TopLevelStat* Annotation = ANNOTATION Length tycon_Type fullAnnotation_Term +// --------------- untyped additions ------------------------------------------ + + TermUntyped = Term + FUNCTION Length body_Term arg_Term* + INFIXOP Length op_NameRef left_Term right_Term + TYPEDSPLICE Length splice_Term + Note: Tree tags are grouped into 5 categories that determine what follows, and thus allow to compute the size of the tagged tree in a generic way. Category 1 (tags 1-49) : tag @@ -320,6 +331,7 @@ object TastyFormat { final val IMPORTED = 65 final val RENAMED = 66 final val SYMBOLconst = 67 + final val UIDENT = 68 // Cat. 3: tag AST @@ -400,6 +412,7 @@ object TastyFormat { final val ANNOTATION = 172 final val TERMREFin = 173 final val TYPEREFin = 174 + final val OBJECTDEF = 175 // In binary: 101100EI // I = implicit method type @@ -409,6 +422,13 @@ object TastyFormat { final val ERASEDMETHODtype = 178 final val ERASEDIMPLICITMETHODtype = 179 + final val UNTYPEDSPLICE = 199 + + // Tags for untyped trees only: + final val TYPEDSPLICE = 200 + final val FUNCTION = 201 + final val INFIXOP = 202 + def methodType(isImplicit: Boolean = false, isErased: Boolean = false) = { val implicitOffset = if (isImplicit) 1 else 0 val erasedOffset = if (isErased) 2 else 0 @@ -550,6 +570,7 @@ object TastyFormat { case VALDEF => "VALDEF" case DEFDEF => "DEFDEF" case TYPEDEF => "TYPEDEF" + case OBJECTDEF => "OBJECTDEF" case IMPORT => "IMPORT" case TYPEPARAM => "TYPEPARAM" case PARAMS => "PARAMS" @@ -617,13 +638,18 @@ object TastyFormat { case PRIVATEqualified => "PRIVATEqualified" case PROTECTEDqualified => "PROTECTEDqualified" case HOLE => "HOLE" + + case UNTYPEDSPLICE => "UNTYPEDSPLICE" + case TYPEDSPLICE => "TYPEDSPLICE" + case FUNCTION => "FUNCTION" + case INFIXOP => "INFIXOP" } /** @return If non-negative, the number of leading references (represented as nats) 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 | + case VALDEF | DEFDEF | TYPEDEF | OBJECTDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | HOLE => 1 case RENAMED | PARAMtype => 2 case POLYtype | METHODtype | TYPELAMBDAtype => -1 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 46d6d0b93ae2..b2ff8a444aec 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -4,7 +4,7 @@ package core package tasty import ast.Trees._ -import ast.{untpd, tpd} +import ast.{untpd, tpd, desugar} import TastyFormat._ import Contexts._, Symbols._, Types._, Names._, Constants._, Decorators._, Annotations._, StdNames.tpnme, NameOps._ import collection.mutable @@ -546,6 +546,10 @@ class TreePickler(pickler: TastyPickler) { pickleTree(lo); if (hi ne lo) pickleTree(hi) } + case tpd.UntypedSplice(splice) => + //println(i"UNTYPED: $splice") + writeByte(UNTYPEDSPLICE) + withLength { pickleUntyped(splice); pickleType(tree.tpe) } case Hole(idx, args) => writeByte(HOLE) withLength { @@ -643,6 +647,211 @@ class TreePickler(pickler: TastyPickler) { withLength { pickleType(ann.symbol.typeRef); pickleTree(ann.tree) } } +// ---- pickling untyped trees ---------------------------------- + + def pickleUntyped(tree: untpd.Tree)(implicit ctx: Context): Unit = { + try desugar(tree) match { + case Ident(name) => + writeByte(if (name.isTypeName) TYPEREF else TERMREF) + pickleName(name) + pickleDummyType() + case This(qual) => + writeByte(QUALTHIS) + pickleUntyped(qual) + case Select(qual, name) => + writeByte(if (name.isTypeName) SELECTtpt else SELECT) + pickleName(name) + pickleUntyped(qual) + case Apply(fun, args) => + writeByte(APPLY) + withLength { + pickleUntyped(fun) + args.foreach(pickleUntyped) + } + case untpd.Throw(exc) => + writeByte(THROW) + pickleUntyped(exc) + case TypeApply(fun, args) => + writeByte(TYPEAPPLY) + withLength { + pickleUntyped(fun) + args.foreach(pickleUntyped) + } + case Literal(const) => + pickleConstant(const) + case Super(qual, mix) => + writeByte(SUPER) + withLength { + pickleUntyped(qual); + if (!mix.isEmpty) pickleUntyped(mix) + } + case New(tpt) => + writeByte(NEW) + pickleUntyped(tpt) + case Typed(expr, tpt) => + writeByte(TYPED) + withLength { pickleUntyped(expr); pickleUntyped(tpt) } + case NamedArg(name, arg) => + writeByte(NAMEDARG) + pickleName(name) + pickleUntyped(arg) + case Assign(lhs, rhs) => + writeByte(ASSIGN) + withLength { pickleUntyped(lhs); pickleUntyped(rhs) } + case Block(stats, expr) => + writeByte(BLOCK) + withLength { pickleUntyped(expr); stats.foreach(pickleUntyped) } + case If(cond, thenp, elsep) => + writeByte(IF) + withLength { pickleUntyped(cond); pickleUntyped(thenp); pickleUntyped(elsep) } + case Match(selector, cases) => + writeByte(MATCH) + withLength { pickleUntyped(selector); cases.foreach(pickleUntyped) } + case CaseDef(pat, guard, rhs) => + writeByte(CASEDEF) + withLength { pickleUntyped(pat); pickleUntyped(rhs); pickleUntypedUnlessEmpty(guard) } + case Return(expr, from) => + writeByte(RETURN) + withLength { pickleDummyRef(); pickleUntypedUnlessEmpty(expr) } + case Try(block, cases, finalizer) => + writeByte(TRY) + withLength { pickleUntyped(block); cases.foreach(pickleUntyped); pickleUntypedUnlessEmpty(finalizer) } + case Bind(name, body) => + writeByte(BIND) + withLength { + pickleName(name); pickleDummyType(); pickleUntyped(body) + } + case Alternative(alts) => + writeByte(ALTERNATIVE) + withLength { alts.foreach(pickleUntyped) } + case tree: untpd.ValDef => + pickleUntypedDef(VALDEF, tree, tree.tpt, tree.rhs) + case tree: untpd.DefDef => + pickleUntypedDef(DEFDEF, tree, tree.tpt, tree.rhs, pickleAllUntypedParams(tree)) + case tree: untpd.TypeDef => + pickleUntypedDef(TYPEDEF, tree, tree.rhs) + case tree: untpd.ModuleDef => + pickleUntypedDef(OBJECTDEF, tree, tree.impl) + case tree: untpd.Template => + writeByte(TEMPLATE) + tree.parents.foreach(pickleUntyped) + if (!tree.self.isEmpty) { + writeByte(SELFDEF); pickleName(tree.self.name); pickleUntyped(tree.self.tpt) + } + pickleUntyped(tree.constr) + tree.body.foreach(pickleUntyped) + case Import(expr, selectors) => + writeByte(IMPORT) + withLength { pickleUntyped(expr); pickleSelectors(selectors) } + case tree: untpd.TypeTree => + pickleDummyType() + case SingletonTypeTree(ref) => + writeByte(SINGLETONtpt) + pickleUntyped(ref) + case RefinedTypeTree(parent, refinements) => + writeByte(REFINEDtpt) + withLength { pickleUntyped(parent); refinements.foreach(pickleUntyped) } + case AppliedTypeTree(tycon, args) => + writeByte(APPLIEDtpt) + withLength { pickleUntyped(tycon); args.foreach(pickleUntyped) } + case AndTypeTree(tp1, tp2) => + writeByte(ANDtpt) + withLength { pickleUntyped(tp1); pickleUntyped(tp2) } + case OrTypeTree(tp1, tp2) => + writeByte(ORtpt) + withLength { pickleUntyped(tp1); pickleUntyped(tp2) } + case ByNameTypeTree(tp) => + writeByte(BYNAMEtpt) + pickleUntyped(tp) + case Annotated(tree, annot) => + writeByte(ANNOTATEDtpt) + withLength { pickleUntyped(tree); pickleUntyped(annot) } + case LambdaTypeTree(tparams, body) => + writeByte(LAMBDAtpt) + withLength { pickleUntypedParams(tparams); pickleUntyped(body) } + case TypeBoundsTree(lo, hi) => + writeByte(TYPEBOUNDStpt) + withLength { + pickleUntyped(lo); + if (hi ne lo) pickleUntyped(hi) + } + case untpd.Function(args, body) => + writeByte(FUNCTION) + withLength { pickleUntyped(body); args.foreach(pickleUntyped) } + case untpd.InfixOp(l, op, r) => + writeByte(INFIXOP) + withLength { pickleUntyped(l); pickleUntyped(op); pickleUntyped(r) } + case Thicket(trees) => + trees.foreach(pickleUntyped) + case untpd.TypedSplice(splice) => + writeByte(TYPEDSPLICE) + withLength { pickleTree(splice) } + } + catch { + case ex: AssertionError => + println(i"error when pickling tree $tree") + throw ex + } + } + + def pickleUntypedUnlessEmpty(tree: untpd.Tree)(implicit ctx: Context): Unit = + if (!tree.isEmpty) pickleUntyped(tree) + + def pickleAllUntypedParams(tree: untpd.DefDef)(implicit ctx: Context): Unit = { + pickleUntypedParams(tree.tparams) + for (vparams <- tree.vparamss) { + writeByte(PARAMS) + withLength { pickleUntypedParams(vparams) } + } + } + + def pickleUntypedParams(trees: List[untpd.Tree])(implicit ctx: Context): Unit = + trees.foreach(pickleUntypedParam) + + def pickleUntypedDef(tag: Int, tree: untpd.MemberDef, tpt: untpd.Tree, rhs: untpd.Tree = untpd.EmptyTree, pickleParams: => Unit = ())(implicit ctx: Context) = { + import untpd.modsDeco + writeByte(tag) + withLength { + pickleName(tree.name) + pickleParams + pickleUntyped(tpt) + pickleUntypedUnlessEmpty(rhs) + pickleUntypedModifiers(tree.mods) + } + } + + def pickleUntypedParam(tree: untpd.Tree)(implicit ctx: Context): Unit = tree match { + case tree: untpd.ValDef => pickleUntypedDef(PARAM, tree, tree.tpt) + case tree: untpd.DefDef => pickleUntypedDef(PARAM, tree, tree.tpt, tree.rhs) + case tree: untpd.TypeDef => pickleUntypedDef(TYPEPARAM, tree, tree.rhs) + } + + def pickleUntypedModifiers(mods: untpd.Modifiers)(implicit ctx: Context): Unit = { + import Flags._ + var flags = mods.flags + val privateWithin = mods.privateWithin + if (!privateWithin.isEmpty) { + writeByte(if (flags is Protected) PROTECTEDqualified else PRIVATEqualified) + pickleUntyped(untpd.Ident(privateWithin)) + flags = flags &~ Protected + } + mods.annotations.foreach(pickleUntypedAnnotation) + } + + def pickleUntypedAnnotation(annotTree: untpd.Tree)(implicit ctx: Context) = { + writeByte(ANNOTATION) + withLength { pickleDummyType(); pickleUntyped(annotTree) } + } + + def pickleDummyRef(): Unit = writeNat(0) + + def pickleDummyType(): Unit = { + writeByte(SHAREDtype) + pickleDummyRef() + } + +// ---- main entry points --------------------------------------- + def pickle(trees: List[Tree])(implicit ctx: Context) = { trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree)) def missing = forwardSymRefs.keysIterator.map(_.showLocated).toList diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b249049c43c9..b4d5f70419a9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -228,6 +228,39 @@ class TreeUnpickler(reader: TastyReader, createSymbol() } + def readConstant(tag: Int)(implicit ctx: Context): Constant = (tag: @switch) match { + case UNITconst => + Constant(()) + case TRUEconst => + Constant(true) + case FALSEconst => + Constant(false) + case BYTEconst => + Constant(readInt().toByte) + case SHORTconst => + Constant(readInt().toShort) + case CHARconst => + Constant(readNat().toChar) + case INTconst => + Constant(readInt()) + case LONGconst => + Constant(readLongInt()) + case FLOATconst => + Constant(java.lang.Float.intBitsToFloat(readInt())) + case DOUBLEconst => + Constant(java.lang.Double.longBitsToDouble(readLongInt())) + case STRINGconst => + Constant(readName().toString) + case NULLconst => + Constant(null) + case CLASSconst => + Constant(readType()) + case ENUMconst => + Constant(readTermRef().termSymbol) + case SYMBOLconst => + Constant(scala.Symbol(readName().toString)) + } + /** Read a type */ def readType()(implicit ctx: Context): Type = { val start = currentAddr @@ -313,10 +346,6 @@ class TreeUnpickler(reader: TastyReader, readTypeRef() match { case binder: LambdaType => binder.paramRefs(readNat()) } - case CLASSconst => - ConstantType(Constant(readType())) - case ENUMconst => - ConstantType(Constant(readTermRef().termSymbol)) case HOLE => readHole(end, isType = true).tpe } @@ -356,38 +385,10 @@ class TreeUnpickler(reader: TastyReader, case SHAREDtype => 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)) - case CLASSconst => - ConstantType(Constant(readType())) - case ENUMconst => - ConstantType(Constant(readTermRef().termSymbol)) - case SYMBOLconst => - ConstantType(Constant(scala.Symbol(readName().toString))) case BYNAMEtype => ExprType(readType()) + case _ => + ConstantType(readConstant(tag)) } if (tag < firstLengthTreeTag) readSimpleType() else readLengthType() @@ -512,7 +513,7 @@ class TreeUnpickler(reader: TastyReader, val rhsStart = currentAddr val rhsIsEmpty = noRhs(end) if (!rhsIsEmpty) skipTree() - val (givenFlags, annots, privateWithin) = readModifiers(end) + val (givenFlags, annots, privateWithin) = readModifiers(end, readAnnot, readWithin, NoSymbol) pickling.println(i"creating symbol $name at $start with flags $givenFlags") val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty) def adjustIfModule(completer: LazyType) = @@ -560,10 +561,12 @@ class TreeUnpickler(reader: TastyReader, /** Read modifier list into triplet of flags, annotations and a privateWithin * boundary symbol. */ - def readModifiers(end: Addr)(implicit ctx: Context): (FlagSet, List[Annotation], Symbol) = { + def readModifiers[WithinType, AnnotType] + (end: Addr, readAnnot: Context => AnnotType, readWithin: Context => WithinType, defaultWithin: WithinType) + (implicit ctx: Context): (FlagSet, List[AnnotType], WithinType) = { var flags: FlagSet = EmptyFlags - var annots = new mutable.ListBuffer[Annotation] - var privateWithin: Symbol = NoSymbol + var annots: List[AnnotType] = Nil + var privateWithin = defaultWithin while (currentAddr.index != end.index) { def addFlag(flag: FlagSet) = { flags |= flag @@ -608,23 +611,31 @@ class TreeUnpickler(reader: TastyReader, addFlag(ParamAccessor) case PRIVATEqualified => readByte() - privateWithin = readType().typeSymbol + privateWithin = readWithin(ctx) case PROTECTEDqualified => addFlag(Protected) - privateWithin = readType().typeSymbol + privateWithin = readWithin(ctx) case ANNOTATION => - readByte() - val end = readEnd() - val tp = readType() - val lazyAnnotTree = readLater(end, rdr => ctx => rdr.readTerm()(ctx)) - annots += Annotation.deferredSymAndTree( - implicit ctx => tp.typeSymbol, - implicit ctx => lazyAnnotTree.complete) + annots = readAnnot(ctx) :: annots case tag => assert(false, s"illegal modifier tag $tag at $currentAddr, end = $end") } } - (flags, annots.toList, privateWithin) + (flags, annots.reverse, privateWithin) + } + + private val readWithin: Context => Symbol = + implicit ctx => readType().typeSymbol + + private val readAnnot: Context => Annotation = { + implicit ctx => + readByte() + val end = readEnd() + val tp = readType() + val lazyAnnotTree = readLater(end, rdr => ctx => rdr.readTerm()(ctx)) + Annotation.deferredSymAndTree( + implicit ctx => tp.typeSymbol, + implicit ctx => lazyAnnotTree.complete) } /** Create symbols for the definitions in the statement sequence between @@ -896,26 +907,27 @@ class TreeUnpickler(reader: TastyReader, readByte() readEnd() val expr = readTerm() - def readSelectors(): List[untpd.Tree] = nextByte match { - case IMPORTED => - val start = currentAddr - readByte() - val from = setPos(start, untpd.Ident(readName())) - nextByte match { - case RENAMED => - val start2 = currentAddr - readByte() - val to = setPos(start2, untpd.Ident(readName())) - untpd.Thicket(from, to) :: readSelectors() - case _ => - from :: readSelectors() - } - case _ => - Nil - } setPos(start, Import(expr, readSelectors())) } + def readSelectors()(implicit ctx: Context): List[untpd.Tree] = nextByte match { + case IMPORTED => + val start = currentAddr + readByte() + val from = setPos(start, untpd.Ident(readName())) + nextByte match { + case RENAMED => + val start2 = currentAddr + readByte() + val to = setPos(start2, untpd.Ident(readName())) + untpd.Thicket(from, to) :: readSelectors() + case _ => + from :: readSelectors() + } + case _ => + Nil + } + def readIndexedStats(exprOwner: Symbol, end: Addr)(implicit ctx: Context): List[Tree] = until(end)(readIndexedStat(exprOwner)) @@ -1103,6 +1115,8 @@ class TreeUnpickler(reader: TastyReader, TypeBoundsTree(lo, hi) case HOLE => readHole(end, isType = false) + case UNTYPEDSPLICE => + tpd.UntypedSplice(readUntyped()).withType(readType()) case _ => readPathTerm() } @@ -1169,6 +1183,189 @@ class TreeUnpickler(reader: TastyReader, PickledQuotes.quotedExprToTree(quotedExpr) } } +// ------ Reading untyped trees -------------------------------------------- + + def readUntyped()(implicit ctx: Context): untpd.Tree = { + val start = currentAddr + val tag = readByte() + pickling.println(s"reading term ${astTagToString(tag)} at $start") + + def readDummyType(): Unit = { + assert(readByte() == SHAREDtype) + assert(readNat() == 0) + } + + def readIdent(): untpd.Ident = readUntyped().asInstanceOf[untpd.Ident] + + def readParams[T <: untpd.MemberDef](tag: Int): List[T] = + collectWhile(nextByte == tag)(readUntyped().asInstanceOf[T]) + + def readParamss(): List[List[untpd.ValDef]] = + collectWhile(nextByte == PARAMS) { + readByte() + readEnd() + readParams[untpd.ValDef](PARAM) + } + + def readCases(end: Addr): List[untpd.CaseDef] = + collectWhile((nextUnsharedTag == CASEDEF) && currentAddr != end) { + readUntyped().asInstanceOf[untpd.CaseDef] + } + + def readSimpleTerm(): untpd.Tree = (tag: @switch) match { + case TERMREF => + val name = readName() + readDummyType() + untpd.Ident(name) + case TYPEREF => + val name = readName().toTypeName + readDummyType() + untpd.Ident(name) + case SELECT => + val name = readName() + val qual = readUntyped() + untpd.Select(qual, name) + case SELECTtpt => + val name = readName().toTypeName + val qual = readUntyped() + untpd.Select(qual, name) + case QUALTHIS => + untpd.This(readIdent()) + case NEW => + untpd.New(readUntyped()) + case THROW => + untpd.Throw(readUntyped()) + case SINGLETONtpt => + untpd.SingletonTypeTree(readUntyped()) + case BYNAMEtpt => + untpd.ByNameTypeTree(readUntyped()) + case NAMEDARG => + untpd.NamedArg(readName(), readUntyped()) + case SHAREDtype => + assert(readByte() == 0) + untpd.TypeTree() + case _ => + untpd.Literal(readConstant(tag)) + } + + def readLengthTerm(): untpd.Tree = { + val end = readEnd() + + def readMods(): untpd.Modifiers = { + val (flags, annots, privateWithin) = + readModifiers(end, readUntypedAnnot, readUntypedWithin, EmptyTypeName) + untpd.Modifiers(flags, privateWithin, annots) + } + + def readRhs(): untpd.Tree = + if (noRhs(end)) untpd.EmptyTree else readUntyped() + + val result = (tag: @switch) match { + case SUPER => + val qual = readUntyped() + val mixId = ifBefore(end)(readIdent(), untpd.EmptyTypeIdent) + untpd.Super(qual, mixId) + case APPLY => + val fn = readUntyped() + untpd.Apply(fn, until(end)(readUntyped())) + case TYPEAPPLY => + untpd.TypeApply(readUntyped(), until(end)(readUntyped())) + case TYPED => + val expr = readUntyped() + val tpt = readUntyped() + untpd.Typed(expr, tpt) + case ASSIGN => + untpd.Assign(readUntyped(), readUntyped()) + case BLOCK => + val expr = readUntyped() + val stats = until(end)(readUntyped()) + untpd.Block(stats, expr) + case IF => + untpd.If(readUntyped(), readUntyped(), readUntyped()) + case MATCH => + untpd.Match(readUntyped(), readCases(end)) + case CASEDEF => + val pat = readUntyped() + val rhs = readUntyped() + val guard = ifBefore(end)(readUntyped(), untpd.EmptyTree) + untpd.CaseDef(pat, guard, rhs) + case RETURN => + readNat() + val expr = ifBefore(end)(readUntyped(), untpd.EmptyTree) + untpd.Return(expr, untpd.EmptyTree) + case TRY => + untpd.Try(readUntyped(), readCases(end), ifBefore(end)(readUntyped(), untpd.EmptyTree)) + case BIND => + val name = readName() + readDummyType() + untpd.Bind(name, readUntyped()) + case ALTERNATIVE => + untpd.Alternative(until(end)(readUntyped())) + case DEFDEF => + untpd.DefDef(readName(), readParams[TypeDef](TYPEPARAM), readParamss(), readUntyped(), readRhs()) + .withMods(readMods()) + case VALDEF | PARAM => + untpd.ValDef(readName(), readUntyped(), readRhs()) + .withMods(readMods()) + case TYPEDEF | TYPEPARAM => + untpd.TypeDef(readName().toTypeName, readUntyped()) + .withMods(readMods()) + case OBJECTDEF => + untpd.ModuleDef(readName(), readUntyped().asInstanceOf[untpd.Template]) + .withMods(readMods()) + case TEMPLATE => + val parents = collectWhile(nextByte != SELFDEF && nextByte != DEFDEF)(readUntyped()) + val self = + if (nextByte == SELFDEF) { + readByte() + untpd.ValDef(readName(), readUntyped(), untpd.EmptyTree) + } + else untpd.EmptyValDef + val constr = readUntyped().asInstanceOf[untpd.DefDef] + val body = until(end)(readUntyped()) + untpd.Template(constr, parents, self, body) + case IMPORT => + untpd.Import(readUntyped(), readSelectors()) + case REFINEDtpt => + untpd.RefinedTypeTree(readUntyped(), until(end)(readUntyped())) + case APPLIEDtpt => + untpd.AppliedTypeTree(readUntyped(), until(end)(readUntyped())) + case ANDtpt => + untpd.AndTypeTree(readUntyped(), readUntyped()) + case ORtpt => + untpd.OrTypeTree(readUntyped(), readUntyped()) + case ANNOTATEDtpt => + untpd.Annotated(readUntyped(), readUntyped()) + case LAMBDAtpt => + val tparams = readParams[TypeDef](TYPEPARAM) + val body = readUntyped() + untpd.LambdaTypeTree(tparams, body) + case TYPEBOUNDStpt => + val lo = readUntyped() + val hi = ifBefore(end)(lo, readUntyped()) + untpd.TypeBoundsTree(lo, hi) + case TYPEDSPLICE => + untpd.TypedSplice(readTerm()) + case FUNCTION => + val body = readUntyped() + val params = until(end)(readUntyped()) + untpd.Function(params, body) + case INFIXOP => + untpd.InfixOp(readUntyped(), readIdent(), readUntyped()) + } + assert(currentAddr == end, s"$start $currentAddr $end ${astTagToString(tag)}") + result + } + + val tree = if (tag < firstLengthTreeTag) readSimpleTerm() else readLengthTerm() + setPos(start, tree) + } + + private val readUntypedWithin: Context => TypeName = + implicit ctx => readName().toTypeName + + private val readUntypedAnnot: Context => untpd.Tree = + implicit ctx => readUntyped() // ------ Setting positions ------------------------------------------------ From 97d7062853507ddb6318730af810119e3a09628d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 29 May 2018 18:14:33 +0200 Subject: [PATCH 11/21] Tweaks to pickling and unpickling untyped trees - need to maintain Type mode bit for correct desugaring - need to pickle PatDefs directly since desugaring requires too much context. --- .../tools/dotc/core/tasty/TastyFormat.scala | 19 +- .../tools/dotc/core/tasty/TreePickler.scala | 392 +++++++++--------- .../tools/dotc/core/tasty/TreeUnpickler.scala | 17 +- 3 files changed, 226 insertions(+), 202 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 9726d15d0dc0..4f7436680191 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -55,20 +55,20 @@ Standard-Section: "ASTs" TopLevelStat* Stat Stat = Term - VALDEF Length NameRef type_Term rhs_Term? Mods + VALDEF Length NameRef type_Term rhs_Term? Modifier* DEFDEF Length NameRef TypeParam* Params* returnType_Term rhs_Term? - Mods - TYPEDEF Length NameRef (type_Term | Template) Mods - OBJECTDEF Length NameRef Template Mods + Modifier* + TYPEDEF Length NameRef (type_Term | Template) Modifier* + OBJECTDEF Length NameRef Template Modifier* IMPORT Length qual_Term Selector* Selector = IMPORTED name_NameRef RENAMED to_NameRef // Imports are for scala.meta, they are not used in the backend - TypeParam = TYPEPARAM Length NameRef type_Term Mods + TypeParam = TYPEPARAM Length NameRef type_Term Modifier* Params = PARAMS Length Param* - Param = PARAM Length NameRef type_Term rhs_Term? Mods // rhs_Term is present in the case of an aliased class parameter + Param = PARAM Length NameRef type_Term rhs_Term? Modifier* // rhs_Term is present in the case of an aliased class parameter Template = TEMPLATE Length TypeParam* Param* parent_Term* Self? Stat* // Stat* always starts with the primary constructor. Self = SELFDEF selfName_NameRef selfType_Term @@ -168,8 +168,6 @@ Standard-Section: "ASTs" TopLevelStat* NamesTypes = NameType* NameType = paramName_NameRef typeOrBounds_ASTRef - Mods = Modifier* Annotation* - Modifier = PRIVATE INTERNAL // package private PROTECTED @@ -209,9 +207,10 @@ Standard-Section: "ASTs" TopLevelStat* // --------------- untyped additions ------------------------------------------ TermUntyped = Term + TYPEDSPLICE Length splice_Term FUNCTION Length body_Term arg_Term* INFIXOP Length op_NameRef left_Term right_Term - TYPEDSPLICE Length splice_Term + PATDEF Length type_Term rhs_Term pattern_Term* Modifier* Note: Tree tags are grouped into 5 categories that determine what follows, and thus allow to compute the size of the tagged tree in a generic way. @@ -428,6 +427,7 @@ object TastyFormat { final val TYPEDSPLICE = 200 final val FUNCTION = 201 final val INFIXOP = 202 + final val PATDEF = 203 def methodType(isImplicit: Boolean = false, isErased: Boolean = false) = { val implicitOffset = if (isImplicit) 1 else 0 @@ -643,6 +643,7 @@ object TastyFormat { case TYPEDSPLICE => "TYPEDSPLICE" case FUNCTION => "FUNCTION" case INFIXOP => "INFIXOP" + case PATDEF => "PATDEF" } /** @return If non-negative, the number of leading references (represented as nats) of a length/trees entry. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index b2ff8a444aec..1e6c06ee3f55 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -547,7 +547,6 @@ class TreePickler(pickler: TastyPickler) { if (hi ne lo) pickleTree(hi) } case tpd.UntypedSplice(splice) => - //println(i"UNTYPED: $splice") writeByte(UNTYPEDSPLICE) withLength { pickleUntyped(splice); pickleType(tree.tpe) } case Hole(idx, args) => @@ -650,204 +649,223 @@ class TreePickler(pickler: TastyPickler) { // ---- pickling untyped trees ---------------------------------- def pickleUntyped(tree: untpd.Tree)(implicit ctx: Context): Unit = { - try desugar(tree) match { - case Ident(name) => - writeByte(if (name.isTypeName) TYPEREF else TERMREF) - pickleName(name) - pickleDummyType() - case This(qual) => - writeByte(QUALTHIS) - pickleUntyped(qual) - case Select(qual, name) => - writeByte(if (name.isTypeName) SELECTtpt else SELECT) - pickleName(name) - pickleUntyped(qual) - case Apply(fun, args) => - writeByte(APPLY) - withLength { - pickleUntyped(fun) - args.foreach(pickleUntyped) - } - case untpd.Throw(exc) => - writeByte(THROW) - pickleUntyped(exc) - case TypeApply(fun, args) => - writeByte(TYPEAPPLY) - withLength { - pickleUntyped(fun) - args.foreach(pickleUntyped) - } - case Literal(const) => - pickleConstant(const) - case Super(qual, mix) => - writeByte(SUPER) - withLength { - pickleUntyped(qual); - if (!mix.isEmpty) pickleUntyped(mix) - } - case New(tpt) => - writeByte(NEW) - pickleUntyped(tpt) - case Typed(expr, tpt) => - writeByte(TYPED) - withLength { pickleUntyped(expr); pickleUntyped(tpt) } - case NamedArg(name, arg) => - writeByte(NAMEDARG) - pickleName(name) - pickleUntyped(arg) - case Assign(lhs, rhs) => - writeByte(ASSIGN) - withLength { pickleUntyped(lhs); pickleUntyped(rhs) } - case Block(stats, expr) => - writeByte(BLOCK) - withLength { pickleUntyped(expr); stats.foreach(pickleUntyped) } - case If(cond, thenp, elsep) => - writeByte(IF) - withLength { pickleUntyped(cond); pickleUntyped(thenp); pickleUntyped(elsep) } - case Match(selector, cases) => - writeByte(MATCH) - withLength { pickleUntyped(selector); cases.foreach(pickleUntyped) } - case CaseDef(pat, guard, rhs) => - writeByte(CASEDEF) - withLength { pickleUntyped(pat); pickleUntyped(rhs); pickleUntypedUnlessEmpty(guard) } - case Return(expr, from) => - writeByte(RETURN) - withLength { pickleDummyRef(); pickleUntypedUnlessEmpty(expr) } - case Try(block, cases, finalizer) => - writeByte(TRY) - withLength { pickleUntyped(block); cases.foreach(pickleUntyped); pickleUntypedUnlessEmpty(finalizer) } - case Bind(name, body) => - writeByte(BIND) - withLength { - pickleName(name); pickleDummyType(); pickleUntyped(body) - } - case Alternative(alts) => - writeByte(ALTERNATIVE) - withLength { alts.foreach(pickleUntyped) } - case tree: untpd.ValDef => - pickleUntypedDef(VALDEF, tree, tree.tpt, tree.rhs) - case tree: untpd.DefDef => - pickleUntypedDef(DEFDEF, tree, tree.tpt, tree.rhs, pickleAllUntypedParams(tree)) - case tree: untpd.TypeDef => - pickleUntypedDef(TYPEDEF, tree, tree.rhs) - case tree: untpd.ModuleDef => - pickleUntypedDef(OBJECTDEF, tree, tree.impl) - case tree: untpd.Template => - writeByte(TEMPLATE) - tree.parents.foreach(pickleUntyped) - if (!tree.self.isEmpty) { - writeByte(SELFDEF); pickleName(tree.self.name); pickleUntyped(tree.self.tpt) - } - pickleUntyped(tree.constr) - tree.body.foreach(pickleUntyped) - case Import(expr, selectors) => - writeByte(IMPORT) - withLength { pickleUntyped(expr); pickleSelectors(selectors) } - case tree: untpd.TypeTree => - pickleDummyType() - case SingletonTypeTree(ref) => - writeByte(SINGLETONtpt) - pickleUntyped(ref) - case RefinedTypeTree(parent, refinements) => - writeByte(REFINEDtpt) - withLength { pickleUntyped(parent); refinements.foreach(pickleUntyped) } - case AppliedTypeTree(tycon, args) => - writeByte(APPLIEDtpt) - withLength { pickleUntyped(tycon); args.foreach(pickleUntyped) } - case AndTypeTree(tp1, tp2) => - writeByte(ANDtpt) - withLength { pickleUntyped(tp1); pickleUntyped(tp2) } - case OrTypeTree(tp1, tp2) => - writeByte(ORtpt) - withLength { pickleUntyped(tp1); pickleUntyped(tp2) } - case ByNameTypeTree(tp) => - writeByte(BYNAMEtpt) - pickleUntyped(tp) - case Annotated(tree, annot) => - writeByte(ANNOTATEDtpt) - withLength { pickleUntyped(tree); pickleUntyped(annot) } - case LambdaTypeTree(tparams, body) => - writeByte(LAMBDAtpt) - withLength { pickleUntypedParams(tparams); pickleUntyped(body) } - case TypeBoundsTree(lo, hi) => - writeByte(TYPEBOUNDStpt) - withLength { - pickleUntyped(lo); - if (hi ne lo) pickleUntyped(hi) - } - case untpd.Function(args, body) => - writeByte(FUNCTION) - withLength { pickleUntyped(body); args.foreach(pickleUntyped) } - case untpd.InfixOp(l, op, r) => - writeByte(INFIXOP) - withLength { pickleUntyped(l); pickleUntyped(op); pickleUntyped(r) } - case Thicket(trees) => - trees.foreach(pickleUntyped) - case untpd.TypedSplice(splice) => - writeByte(TYPEDSPLICE) - withLength { pickleTree(splice) } - } - catch { - case ex: AssertionError => - println(i"error when pickling tree $tree") - throw ex - } - } - def pickleUntypedUnlessEmpty(tree: untpd.Tree)(implicit ctx: Context): Unit = - if (!tree.isEmpty) pickleUntyped(tree) + def pickleDummyRef(): Unit = writeNat(0) - def pickleAllUntypedParams(tree: untpd.DefDef)(implicit ctx: Context): Unit = { - pickleUntypedParams(tree.tparams) - for (vparams <- tree.vparamss) { - writeByte(PARAMS) - withLength { pickleUntypedParams(vparams) } + def pickleDummyType(): Unit = { + writeByte(SHAREDtype) + pickleDummyRef() } - } - def pickleUntypedParams(trees: List[untpd.Tree])(implicit ctx: Context): Unit = - trees.foreach(pickleUntypedParam) + def pickleUnlessEmpty(tree: untpd.Tree): Unit = + if (!tree.isEmpty) pickleUntyped(tree) - def pickleUntypedDef(tag: Int, tree: untpd.MemberDef, tpt: untpd.Tree, rhs: untpd.Tree = untpd.EmptyTree, pickleParams: => Unit = ())(implicit ctx: Context) = { - import untpd.modsDeco - writeByte(tag) - withLength { - pickleName(tree.name) - pickleParams - pickleUntyped(tpt) - pickleUntypedUnlessEmpty(rhs) - pickleUntypedModifiers(tree.mods) + def pickleTpt(tree: untpd.Tree) = pickleUntyped(tree)(ctx.addMode(Mode.Type)) + def pickleTerm(tree: untpd.Tree) = pickleUntyped(tree)(ctx.retractMode(Mode.Type)) + + def pickleAllParams(tree: untpd.DefDef): Unit = { + pickleParams(tree.tparams) + for (vparams <- tree.vparamss) { + writeByte(PARAMS) + withLength { pickleParams(vparams) } + } } - } - def pickleUntypedParam(tree: untpd.Tree)(implicit ctx: Context): Unit = tree match { - case tree: untpd.ValDef => pickleUntypedDef(PARAM, tree, tree.tpt) - case tree: untpd.DefDef => pickleUntypedDef(PARAM, tree, tree.tpt, tree.rhs) - case tree: untpd.TypeDef => pickleUntypedDef(TYPEPARAM, tree, tree.rhs) - } + def pickleParams(trees: List[untpd.Tree]): Unit = + trees.foreach(pickleParam) - def pickleUntypedModifiers(mods: untpd.Modifiers)(implicit ctx: Context): Unit = { - import Flags._ - var flags = mods.flags - val privateWithin = mods.privateWithin - if (!privateWithin.isEmpty) { - writeByte(if (flags is Protected) PROTECTEDqualified else PRIVATEqualified) - pickleUntyped(untpd.Ident(privateWithin)) - flags = flags &~ Protected + def pickleParam(tree: untpd.Tree): Unit = tree match { + case tree: untpd.ValDef => pickleDef(PARAM, tree, tree.tpt) + case tree: untpd.DefDef => pickleDef(PARAM, tree, tree.tpt, tree.rhs) + case tree: untpd.TypeDef => pickleDef(TYPEPARAM, tree, tree.rhs) } - mods.annotations.foreach(pickleUntypedAnnotation) - } - def pickleUntypedAnnotation(annotTree: untpd.Tree)(implicit ctx: Context) = { - writeByte(ANNOTATION) - withLength { pickleDummyType(); pickleUntyped(annotTree) } - } + def pickleParent(tree: untpd.Tree): Unit = tree match { + case _: untpd.Apply | _: untpd.TypeApply => pickleUntyped(tree) + case _ => pickleTpt(tree) + } + + def pickleDef(tag: Int, tree: untpd.MemberDef, tpt: untpd.Tree, rhs: untpd.Tree = untpd.EmptyTree, pickleParams: => Unit = ()) = { + import untpd.modsDeco + writeByte(tag) + withLength { + pickleName(tree.name) + pickleParams + pickleTpt(tpt) + pickleUnlessEmpty(rhs) + pickleModifiers(tree.mods) + } + } - def pickleDummyRef(): Unit = writeNat(0) + def pickleModifiers(mods: untpd.Modifiers): Unit = { + import Flags._ + var flags = mods.flags + val privateWithin = mods.privateWithin + if (!privateWithin.isEmpty) { + writeByte(if (flags is Protected) PROTECTEDqualified else PRIVATEqualified) + pickleUntyped(untpd.Ident(privateWithin)) + flags = flags &~ Protected + } + mods.annotations.foreach(pickleAnnotation) + } - def pickleDummyType(): Unit = { - writeByte(SHAREDtype) - pickleDummyRef() + def pickleAnnotation(annotTree: untpd.Tree) = { + writeByte(ANNOTATION) + withLength { pickleDummyType(); pickleUntyped(annotTree) } + } + + try tree match { + case Ident(name) => + writeByte(if (name.isTypeName) TYPEREF else TERMREF) + pickleName(name) + pickleDummyType() + case This(qual) => + writeByte(QUALTHIS) + pickleUntyped(qual) + case Select(qual, name) => + writeByte(if (name.isTypeName) SELECTtpt else SELECT) + pickleName(name) + if (qual.isType) pickleTpt(qual) else pickleTerm(qual) + case Apply(fun, args) => + writeByte(APPLY) + withLength { + pickleUntyped(fun) + args.foreach(pickleUntyped) + } + case untpd.Throw(exc) => + writeByte(THROW) + pickleUntyped(exc) + case TypeApply(fun, args) => + writeByte(TYPEAPPLY) + withLength { + pickleUntyped(fun) + args.foreach(pickleTpt) + } + case Literal(const) => + pickleConstant(const) + case Super(qual, mix) => + writeByte(SUPER) + withLength { + pickleUntyped(qual); + if (!mix.isEmpty) pickleUntyped(mix) + } + case New(tpt) => + writeByte(NEW) + pickleTpt(tpt) + case Typed(expr, tpt) => + writeByte(TYPED) + withLength { pickleUntyped(expr); pickleTpt(tpt) } + case NamedArg(name, arg) => + writeByte(NAMEDARG) + pickleName(name) + pickleUntyped(arg) + case Assign(lhs, rhs) => + writeByte(ASSIGN) + withLength { pickleUntyped(lhs); pickleUntyped(rhs) } + case Block(stats, expr) => + writeByte(BLOCK) + withLength { pickleUntyped(expr); stats.foreach(pickleUntyped) } + case If(cond, thenp, elsep) => + writeByte(IF) + withLength { pickleUntyped(cond); pickleUntyped(thenp); pickleUntyped(elsep) } + case Match(selector, cases) => + writeByte(MATCH) + withLength { pickleUntyped(selector); cases.foreach(pickleUntyped) } + case CaseDef(pat, guard, rhs) => + writeByte(CASEDEF) + withLength { pickleUntyped(pat); pickleUntyped(rhs); pickleUnlessEmpty(guard) } + case Return(expr, from) => + writeByte(RETURN) + withLength { pickleDummyRef(); pickleUnlessEmpty(expr) } + case Try(block, cases, finalizer) => + writeByte(TRY) + withLength { pickleUntyped(block); cases.foreach(pickleUntyped); pickleUnlessEmpty(finalizer) } + case Bind(name, body) => + writeByte(BIND) + withLength { + pickleName(name); pickleDummyType(); pickleUntyped(body) + } + case Alternative(alts) => + writeByte(ALTERNATIVE) + withLength { alts.foreach(pickleUntyped) } + case tree: untpd.ValDef => + pickleDef(VALDEF, tree, tree.tpt, tree.rhs) + case tree: untpd.DefDef => + pickleDef(DEFDEF, tree, tree.tpt, tree.rhs, pickleAllParams(tree)) + case tree: untpd.TypeDef => + pickleDef(TYPEDEF, tree, tree.rhs) + case tree: untpd.ModuleDef => + pickleDef(OBJECTDEF, tree, tree.impl) + case tree: untpd.Template => + writeByte(TEMPLATE) + tree.parents.foreach(pickleParent) + if (!tree.self.isEmpty) { + writeByte(SELFDEF); pickleName(tree.self.name); pickleTpt(tree.self.tpt) + } + pickleUntyped(tree.constr) + tree.body.foreach(pickleUntyped) + case Import(expr, selectors) => + writeByte(IMPORT) + withLength { pickleUntyped(expr); pickleSelectors(selectors) } + case tree: untpd.TypeTree => + pickleDummyType() + case SingletonTypeTree(ref) => + writeByte(SINGLETONtpt) + pickleTerm(ref) + case RefinedTypeTree(parent, refinements) => + writeByte(REFINEDtpt) + withLength { pickleTpt(parent); refinements.foreach(pickleTerm) } + case AppliedTypeTree(tycon, args) => + writeByte(APPLIEDtpt) + withLength { pickleTpt(tycon); args.foreach(pickleTpt) } + case AndTypeTree(tp1, tp2) => + writeByte(ANDtpt) + withLength { pickleTpt(tp1); pickleTpt(tp2) } + case OrTypeTree(tp1, tp2) => + writeByte(ORtpt) + withLength { pickleTpt(tp1); pickleTpt(tp2) } + case ByNameTypeTree(tp) => + writeByte(BYNAMEtpt) + pickleTpt(tp) + case Annotated(tree, annot) => + writeByte(ANNOTATEDtpt) + withLength { pickleTpt(tree); pickleTerm(annot) } + case LambdaTypeTree(tparams, body) => + writeByte(LAMBDAtpt) + withLength { pickleParams(tparams); pickleTpt(body) } + case TypeBoundsTree(lo, hi) => + writeByte(TYPEBOUNDStpt) + withLength { + pickleTpt(lo); + if (hi ne lo) pickleTpt(hi) + } + case untpd.Function(args, body) => + writeByte(FUNCTION) + withLength { pickleUntyped(body); args.foreach(pickleUntyped) } + case untpd.InfixOp(l, op, r) => + writeByte(INFIXOP) + withLength { pickleUntyped(l); pickleUntyped(op); pickleUntyped(r) } + case untpd.PatDef(mods, pats, tpt, rhs) => + writeByte(PATDEF) + withLength { + pickleTpt(tpt) + pickleUntyped(rhs) + pats.foreach(pickleUntyped) + pickleModifiers(mods) + } + case untpd.TypedSplice(splice) => + writeByte(TYPEDSPLICE) + withLength { pickleTree(splice) } + case Thicket(trees) => + trees.foreach(pickleUntyped) + case _ => + pickleUntyped(desugar(tree)) + } + catch { + case ex: AssertionError => + println(i"error when pickling tree $tree") + throw ex + } } // ---- main entry points --------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b4d5f70419a9..ac356a44e5d9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -421,7 +421,7 @@ class TreeUnpickler(reader: TastyReader, // ------ Reading definitions ----------------------------------------------------- - private def noRhs(end: Addr): Boolean = + private def nothingButMods(end: Addr): Boolean = currentAddr == end || isModifierTag(nextByte) private def localContext(owner: Symbol)(implicit ctx: Context) = @@ -511,7 +511,7 @@ class TreeUnpickler(reader: TastyReader, val templateStart = currentAddr skipTree() // tpt val rhsStart = currentAddr - val rhsIsEmpty = noRhs(end) + val rhsIsEmpty = nothingButMods(end) if (!rhsIsEmpty) skipTree() val (givenFlags, annots, privateWithin) = readModifiers(end, readAnnot, readWithin, NoSymbol) pickling.println(i"creating symbol $name at $start with flags $givenFlags") @@ -732,7 +732,7 @@ class TreeUnpickler(reader: TastyReader, val localCtx = localContext(sym) def readRhs(implicit ctx: Context) = - if (noRhs(end)) EmptyTree + if (nothingButMods(end)) EmptyTree else readLater(end, rdr => ctx => rdr.readTerm()(ctx.retractMode(Mode.InSuperCall))) def ValDef(tpt: Tree) = @@ -797,7 +797,7 @@ class TreeUnpickler(reader: TastyReader, } case PARAM => val tpt = readTpt()(localCtx) - if (noRhs(end)) { + if (nothingButMods(end)) { sym.info = tpt.tpe ValDef(tpt) } @@ -1242,7 +1242,7 @@ class TreeUnpickler(reader: TastyReader, case NAMEDARG => untpd.NamedArg(readName(), readUntyped()) case SHAREDtype => - assert(readByte() == 0) + assert(readNat() == 0) untpd.TypeTree() case _ => untpd.Literal(readConstant(tag)) @@ -1258,7 +1258,7 @@ class TreeUnpickler(reader: TastyReader, } def readRhs(): untpd.Tree = - if (noRhs(end)) untpd.EmptyTree else readUntyped() + if (nothingButMods(end)) untpd.EmptyTree else readUntyped() val result = (tag: @switch) match { case SUPER => @@ -1352,6 +1352,11 @@ class TreeUnpickler(reader: TastyReader, untpd.Function(params, body) case INFIXOP => untpd.InfixOp(readUntyped(), readIdent(), readUntyped()) + case PATDEF => + val tpt = readUntyped() + val rhs = readUntyped() + val pats = collectWhile(!nothingButMods(end))(readUntyped()) + untpd.PatDef(readMods(), pats, tpt, rhs) } assert(currentAddr == end, s"$start $currentAddr $end ${astTagToString(tag)}") result From 39a3c181d2a6f266226165eb8d49e52eeb615815 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 May 2018 12:11:57 +0200 Subject: [PATCH 12/21] Fix pickler printing of TERMREFin, TYPEREFin --- compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index fd94296fd93d..1a1f2dadc4ba 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -63,9 +63,9 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) { tag match { case RENAMED => printName(); printName() - case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | BIND => + case VALDEF | DEFDEF | TYPEDEF | OBJECTDEF | TYPEPARAM | PARAM | NAMEDARG | BIND => printName(); printTrees() - case REFINEDtype => + case REFINEDtype | TERMREFin | TYPEREFin => printName(); printTree(); printTrees() case RETURN | HOLE => printNat(); printTrees() From a0dc61f3e3c291a4da9560a27a2c6d0fc7248c93 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 May 2018 12:12:54 +0200 Subject: [PATCH 13/21] Fix pickling of flags in untyped mode --- compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 1e6c06ee3f55..679be32e3aa5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -693,11 +693,11 @@ class TreePickler(pickler: TastyPickler) { pickleParams pickleTpt(tpt) pickleUnlessEmpty(rhs) - pickleModifiers(tree.mods) + pickleModifiers(tree.mods, tree.name.isTermName) } } - def pickleModifiers(mods: untpd.Modifiers): Unit = { + def pickleModifiers(mods: untpd.Modifiers, isTerm: Boolean): Unit = { import Flags._ var flags = mods.flags val privateWithin = mods.privateWithin @@ -706,6 +706,7 @@ class TreePickler(pickler: TastyPickler) { pickleUntyped(untpd.Ident(privateWithin)) flags = flags &~ Protected } + pickleFlags(flags, isTerm) mods.annotations.foreach(pickleAnnotation) } @@ -851,7 +852,7 @@ class TreePickler(pickler: TastyPickler) { pickleTpt(tpt) pickleUntyped(rhs) pats.foreach(pickleUntyped) - pickleModifiers(mods) + pickleModifiers(mods, isTerm = true) } case untpd.TypedSplice(splice) => writeByte(TYPEDSPLICE) From 215046a55407ff46bd82f3e9c8748a3337f8d2d6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 31 May 2018 17:39:16 +0200 Subject: [PATCH 14/21] Enforce that mods in Modifiers don't add new information Everything should be reflected in flags and privateWithin already. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 25 +++++++++++++------ .../dotty/tools/dotc/parsing/Parsers.scala | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 219b468e9391..17c5e45a2740 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -161,15 +161,26 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { if (this.flags == flags) this else copy(flags = flags) - def withAddedMod(mod: Mod): Modifiers = - if (mods.exists(_ eq mod)) this - else withMods(mods :+ mod) + def withAddedMod(mod: Mod): Modifiers = + if (mods.exists(_ eq mod)) this + else withMods(mods :+ mod) - def withMods(ms: List[Mod]): Modifiers = - if (mods eq ms) this - else copy(mods = ms) + /** Modifiers with given list of Mods. It is checked that + * all modifiers are already accounted for in `flags` and `privateWithin`. + */ + def withMods(ms: List[Mod]): Modifiers = { + if (mods eq ms) this + else { + if (ms.nonEmpty) + for (m <- ms) + assert(flags.is(m.flags) || + m.isInstanceOf[Mod.Private] && !privateWithin.isEmpty, + s"unaccounted modifier: $m in $this when adding $ms") + copy(mods = ms) + } + } - def withAddedAnnotation(annot: Tree): Modifiers = + def withAddedAnnotation(annot: Tree): Modifiers = if (annotations.exists(_ eq annot)) this else withAnnotations(annotations :+ annot) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index e4844cf2561a..e35d23e10abd 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2270,7 +2270,7 @@ object Parsers { /** EnumCase = `case' (id ClassConstr [`extends' ConstrApps] | ids) */ def enumCase(start: Offset, mods: Modifiers): DefTree = { - val mods1 = mods.withAddedMod(atPos(in.offset)(Mod.Enum())) | Case + val mods1 = addMod(mods, atPos(in.offset)(Mod.Enum())) | Case accept(CASE) in.adjustSepRegions(ARROW) From b5b04a7395f722eda388bfa0a8679768ea95ae23 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 2 Jun 2018 14:57:26 +0200 Subject: [PATCH 15/21] Constant-fold unary operations in Typer This was previously forgotten, even though unary operations such as `!` and `~` were folded later in FirstTransform. As a consequence, inlining simplifications involving constant expressions using these operations were not done. This affected in particular the trace macro, which was always expanded to an operation taking a closure argument. --- .../src/dotty/tools/dotc/printing/RefinedPrinter.scala | 9 +++++++++ compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 13b325527517..0166cc386cfd 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -548,6 +548,15 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { // add type to term nodes; replace type nodes with their types unless -Yprint-pos is also set. def tp = tree.typeOpt match { case tp: TermRef if tree.isInstanceOf[RefTree] && !tp.denot.isOverloaded => tp.underlying + case tp: ConstantType if homogenizedView => + // constant folded types are forgotten in Tasty, are reconstituted subsequently in FirstTransform. + // Therefore we have to gloss over this when comparing before/after pickling by widening to + // underlying type `T`, or, if expression is a unary primitive operation, to `=> T`. + tree match { + case Select(qual, _) if qual.typeOpt.widen.typeSymbol.isPrimitiveValueClass => + ExprType(tp.widen) + case _ => tp.widen + } case tp => tp } if (!suppressTypes) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9e1222521e89..245bd34798ab 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -437,7 +437,7 @@ class Typer extends Namer val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) val select = typedSelect(tree, pt, qual1) - if (select.tpe ne TryDynamicCallType) checkStableIdentPattern(select, pt) + if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt)) else if (pt.isInstanceOf[PolyProto] || pt.isInstanceOf[FunProto] || pt == AssignProto) select else typedDynamicSelect(tree, Nil, pt) } From c571a8d8a0e81e2733f81019d9a5d79128ad764e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 2 Jun 2018 17:13:20 +0200 Subject: [PATCH 16/21] Don't forget Mode.Printing in RefiendPrinter --- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 0166cc386cfd..dff92ebfa800 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -37,7 +37,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { /** A stack of enclosing DefDef, TypeDef, or ClassDef, or ModuleDefs nodes */ private[this] var enclosingDef: untpd.Tree = untpd.EmptyTree - private[this] var myCtx: Context = _ctx + private[this] var myCtx: Context = super.ctx private[this] var printPos = ctx.settings.YprintPos.value private[this] val printLines = ctx.settings.printLines.value From f1233a812df6b7eeab8fbdf6289e1b94e513efaf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 2 Jun 2018 19:05:32 +0200 Subject: [PATCH 17/21] Streamline trace implementation Avoid creation of `op1` methods if tracing is not enabled. --- .../dotty/tools/dotc/reporting/trace.scala | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index d42008500bbf..20617bb2648d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -14,25 +14,30 @@ object trace { conditionally(ctx.settings.YdebugTrace.value, question, false)(op) @inline - def conditionally[TC](cond: Boolean, question: => String, show: Boolean)(op: => TC)(implicit ctx: Context): TC = { - def op1 = op - if (Config.tracingEnabled && cond) apply[TC](question, Printers.default, show)(op1) - else op1 - } + def conditionally[TC](cond: Boolean, question: => String, show: Boolean)(op: => TC)(implicit ctx: Context): TC = + if (Config.tracingEnabled) { + def op1 = op + if (cond) apply[TC](question, Printers.default, show)(op1) + else op1 + } else op @inline - def apply[T](question: => String, printer: Printers.Printer, showOp: Any => String)(op: => T)(implicit ctx: Context): T = { - def op1 = op - if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 - else doTrace[T](question, printer, showOp)(op1) - } + def apply[T](question: => String, printer: Printers.Printer, showOp: Any => String)(op: => T)(implicit ctx: Context): T = + if (Config.tracingEnabled) { + def op1 = op + if (printer.eq(config.Printers.noPrinter)) op1 + else doTrace[T](question, printer, showOp)(op1) + } + else op @inline - def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = { - def op1 = op - if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 - else doTrace[T](question, printer, if (show) showShowable(_) else alwaysToString)(op1) - } + def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = + if (Config.tracingEnabled) { + def op1 = op + if (printer.eq(config.Printers.noPrinter)) op1 + else doTrace[T](question, printer, if (show) showShowable(_) else alwaysToString)(op1) + } + else op @inline def apply[T](question: => String, printer: Printers.Printer)(op: => T)(implicit ctx: Context): T = From 1300dfe70a33727a5b94aba36564eea4775b9772 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 Jun 2018 11:23:30 +0200 Subject: [PATCH 18/21] Relax assertion to allow printing trees with overloaded denotations after typer --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 79e4a469b00a..8a8388e3adfa 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1825,7 +1825,7 @@ object Types { private def setDenot(denot: Denotation)(implicit ctx: Context): Unit = { if (ctx.isAfterTyper) - assert(!denot.isOverloaded, this) + assert(!denot.isOverloaded || ctx.mode.is(Mode.Printing), this) if (Config.checkNoDoubleBindings) if (ctx.settings.YnoDoubleBindings.value) checkSymAssign(denot.symbol) From ea8f90f02f04594fea8079c15d71c01bc5d6ac4b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 Jun 2018 11:26:17 +0200 Subject: [PATCH 19/21] Don't treat inline closures specially. Rely on call-by-name parameters instead. Fix "unused defs" logic so that referred-to cbn parameters are not eliminated. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 9 +- .../src/dotty/tools/dotc/typer/Checking.scala | 3 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 161 ++++++++++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 5 +- tests/run/i4431/quoted_1.scala | 2 +- tests/run/inlineByName.scala | 37 ++++ tests/run/inlineForeach.scala | 4 +- tests/run/inlinedAssign.scala | 2 +- tests/run/lst/Lst.scala | 14 +- 9 files changed, 184 insertions(+), 53 deletions(-) create mode 100644 tests/run/inlineByName.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index bad09c51b74b..248dec0ebaef 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -810,16 +810,11 @@ object desugar { * ==> * def $anonfun(params) = body * Closure($anonfun) - * - * If `inlineable` is true, tag $anonfun with an @inline annotation. */ - def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = TypeTree(), inlineable: Boolean)(implicit ctx: Context) = { - var mods = synthetic | Artifact - if (inlineable) mods |= Inline + def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = TypeTree())(implicit ctx: Context) = Block( - DefDef(nme.ANON_FUN, Nil, params :: Nil, tpt, body).withMods(mods), + DefDef(nme.ANON_FUN, Nil, params :: Nil, tpt, body).withMods(synthetic | Artifact), Closure(Nil, Ident(nme.ANON_FUN), EmptyTree)) - } /** If `nparams` == 1, expand partial function * diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 5854a2cdeab9..968e291d37dc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -645,9 +645,8 @@ trait Checking { case tp: TermRef if tp.symbol.is(InlineParam) => // ok case tp => tp.widenTermRefExpr match { case tp: ConstantType if exprPurity(tree) >= purityLevel => // ok - case tp if defn.isFunctionType(tp) && exprPurity(tree) >= purityLevel => // ok case _ => - if (!ctx.erasedTypes) ctx.error(em"$what must be a constant expression or a function", tree.pos) + if (!ctx.erasedTypes) ctx.error(em"$what must be a constant expression", tree.pos) } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 5e3b99cf1e2f..7b2fd0b16006 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -218,6 +218,31 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) + /** A binding for the parameter of an inlined method. This is a `val` def for + * by-value parameters and a `def` def for by-name parameters. `val` defs inherit + * inline annotations from their parameters. The generated `def` is appended + * to `bindingsBuf`. + * @param name the name of the parameter + * @param paramtp the type of the parameter + * @param arg the argument corresponding to the parameter + * @param bindingsBuf the buffer to which the definition should be appended + */ + private def paramBindingDef(name: Name, paramtp: Type, arg: Tree, + bindingsBuf: mutable.ListBuffer[ValOrDefDef]): ValOrDefDef = { + val argtpe = arg.tpe.dealias + def isByName = paramtp.dealias.isInstanceOf[ExprType] + val inlineFlag = if (paramtp.hasAnnotation(defn.InlineParamAnnot)) Inline else EmptyFlags + val (bindingFlags, bindingType) = + if (isByName) (Method, ExprType(argtpe.widen)) + else (inlineFlag, argtpe.widen) + val boundSym = newSym(name, bindingFlags, bindingType).asTerm + val binding = + if (isByName) DefDef(boundSym, arg.changeOwner(ctx.owner, boundSym)) + else ValDef(boundSym, arg) + bindingsBuf += binding + binding + } + /** Populate `paramBinding` and `bindingsBuf` by matching parameters with * corresponding arguments. `bindingbuf` will be further extended later by * proxies to this-references. @@ -230,20 +255,9 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { computeParamBindings(tp.resultType, Nil, argss) case tp: MethodType => (tp.paramNames, tp.paramInfos, argss.head).zipped.foreach { (name, paramtp, arg) => - def isByName = paramtp.dealias.isInstanceOf[ExprType] paramBinding(name) = arg.tpe.dealias match { case _: SingletonType if isIdempotentExpr(arg) => arg.tpe - case argtpe => - val inlineFlag = if (paramtp.hasAnnotation(defn.InlineParamAnnot)) Inline else EmptyFlags - val (bindingFlags, bindingType) = - if (isByName) (inlineFlag | Method, ExprType(argtpe.widen)) - else (inlineFlag, argtpe.widen) - val boundSym = newSym(name, bindingFlags, bindingType).asTerm - val binding = - if (isByName) DefDef(boundSym, arg.changeOwner(ctx.owner, boundSym)) - else ValDef(boundSym, arg) - bindingsBuf += binding - boundSym.termRef + case _ => paramBindingDef(name, paramtp, arg, bindingsBuf).symbol.termRef } } computeParamBindings(tp.resultType, targs, argss.tail) @@ -265,7 +279,7 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { * The proxy is not yet entered in `bindingsBuf`; that will come later. * 2. If given type refers to a parameter, make `paramProxy` refer to the entry stored * in `paramNames` under the parameter's name. This roundabout way to bind parameter - * references to proxies is done because we not known a priori what the parameter + * references to proxies is done because we don't know a priori what the parameter * references of a method are (we only know the method's type, but that contains TypeParamRefs * and MethodParams, not TypeRefs or TermRefs. */ @@ -374,16 +388,15 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = InlineTyper.typed(expansion, pt)(inlineCtx) - /** Does given definition bind a closure that will be inlined? */ - def bindsDeadInlineable(defn: ValOrDefDef) = Ident(defn.symbol.termRef) match { - case InlineableArg(_) => !InlineTyper.retainedInlineables.contains(defn.symbol) - case _ => false - } - /** All bindings in `bindingsBuf` except bindings of inlineable closures */ - val bindings = bindingsBuf.toList.filterNot(bindsDeadInlineable).map(_.withPos(call.pos)) + val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) + + inlining.println(i"original bindings = $bindings%\n%") + inlining.println(i"original expansion = $expansion1") - tpd.Inlined(call, bindings, expansion1) + val (finalBindings, finalExpansion) = dropUnusedDefs(bindings, expansion1) + + tpd.Inlined(call, finalBindings, finalExpansion) } } @@ -414,8 +427,6 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { */ private object InlineTyper extends ReTyper { - var retainedInlineables = Set[Symbol]() - override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { tpe match { case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) => @@ -455,13 +466,103 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } } - override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = - tree.asInstanceOf[tpd.Tree] match { - case Apply(Select(InlineableArg(closure(_, fn, _)), nme.apply), args) => - inlining.println(i"reducing $tree with closure $fn") - typed(fn.appliedToArgs(args), pt) - case _ => - super.typedApply(tree, pt) + override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = { + + def betaReduce(tree: Tree) = tree match { + case Apply(Select(cl @ closureDef(ddef), nme.apply), args) => + ddef.tpe.widen match { + case mt: MethodType if ddef.vparamss.head.length == args.length => + val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + val argSyms = (mt.paramNames, mt.paramInfos, args).zipped.map { (name, paramtp, arg) => + arg.tpe.dealias match { + case ref @ TermRef(NoPrefix, _) => ref.symbol + case _ => paramBindingDef(name, paramtp, arg, bindingsBuf).symbol + } + } + val expander = new TreeTypeMap( + oldOwners = ddef.symbol :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = ddef.vparamss.head.map(_.symbol), + substTo = argSyms) + Block(bindingsBuf.toList, expander.transform(ddef.rhs)) + case _ => tree + } + case _ => tree } + + betaReduce(super.typedApply(tree, pt)) + } + } + + /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. + * Inline def bindings that are used only once. + */ + def dropUnusedDefs(bindings: List[ValOrDefDef], tree: Tree)(implicit ctx: Context): (List[ValOrDefDef], Tree) = { + val refCount = newMutableSymbolMap[Int] + val bindingOfSym = newMutableSymbolMap[ValOrDefDef] + def isInlineable(binding: ValOrDefDef) = binding match { + case DefDef(_, Nil, Nil, _, _) => true + case vdef @ ValDef(_, _, _) => isPureExpr(vdef.rhs) + case _ => false + } + for (binding <- bindings if isInlineable(binding)) { + refCount(binding.symbol) = 0 + bindingOfSym(binding.symbol) = binding + } + val countRefs = new TreeTraverser { + override def traverse(t: Tree)(implicit ctx: Context) = { + t match { + case t: RefTree => + refCount.get(t.symbol) match { + case Some(x) => refCount(t.symbol) = x + 1 + case none => + } + case _: New | _: TypeTree => + //println(i"refcount ${t.tpe}") + t.tpe.foreachPart { + case ref: TermRef => + refCount.get(ref.symbol) match { + case Some(x) => refCount(ref.symbol) = x + 2 + case none => + } + case _ => + } + case _ => + } + traverseChildren(t) + } + } + countRefs.traverse(tree) + for (binding <- bindings) countRefs.traverse(binding.rhs) + val inlineBindings = new TreeMap { + override def transform(t: Tree)(implicit ctx: Context) = + super.transform { + t match { + case t: RefTree => + val sym = t.symbol + refCount.get(sym) match { + case Some(1) if sym.is(Method) => + bindingOfSym(sym).rhs.changeOwner(sym, ctx.owner) + case none => t + } + case _ => t + } + } + } + def retain(binding: ValOrDefDef) = refCount.get(binding.symbol) match { + case Some(x) => x > 1 || x == 1 && !binding.symbol.is(Method) + case none => true + } + val retained = bindings.filterConserve(retain) + if (retained `eq` bindings) { + //println(i"DONE\n${bindings}%\n% ;;;\n ${tree}") + (bindings, tree) + } + else { + val expanded = inlineBindings.transform(tree) + //println(i"ref counts: ${refCount.toMap map { case (sym, count) => i"$sym -> $count" }}") + //println(i"""MAPPING\n${bindings}%\n% ;;;\n ${tree} \n------->\n${retained}%\n%;;;\n ${expanded} """) + dropUnusedDefs(retained, expanded) + } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 245bd34798ab..ce0b762945f9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -417,7 +417,7 @@ class Typer extends Namer /** Check that a stable identifier pattern is indeed stable (SLS 8.1.5) */ - private def checkStableIdentPattern(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { + private def checkStableIdentPattern(tree: Tree, pt: Type)(implicit ctx: Context): tree.type = { if (ctx.mode.is(Mode.Pattern) && !tree.isType && !pt.isInstanceOf[ApplyingProto] && @@ -915,8 +915,7 @@ class Typer extends Namer else cpy.ValDef(param)( tpt = untpd.TypeTree( inferredParamType(param, protoFormal(i)).underlyingIfRepeated(isJava = false))) - val inlineable = pt.hasAnnotation(defn.InlineParamAnnot) - desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable) + desugar.makeClosure(inferredParams, fnBody, resultTpt) } typed(desugared, pt) } diff --git a/tests/run/i4431/quoted_1.scala b/tests/run/i4431/quoted_1.scala index b4e89dbbc512..8f0be7b23ef7 100644 --- a/tests/run/i4431/quoted_1.scala +++ b/tests/run/i4431/quoted_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macros { - inline def h(inline f: Int => String): String = ~ '(f(42)) + inline def h(f: => Int => String): String = ~ '(f(42)) } diff --git a/tests/run/inlineByName.scala b/tests/run/inlineByName.scala new file mode 100644 index 000000000000..94f276dcce3e --- /dev/null +++ b/tests/run/inlineByName.scala @@ -0,0 +1,37 @@ +object Test { + + class Range(from: Int, end: Int) { + inline def foreach(op: => Int => Unit): Unit = { + var i = from + while (i < end) { + op(i) + i += 1 + } + } + } + inline def twice(op: => Int => Unit): Unit = { + op(1) + op(2) + } + inline def thrice(op: => Unit): Unit = { + op + op + op + } + + def main(args: Array[String]) = { + var j = 0 + new Range(1, 10).foreach(j += _) + assert(j == 45, j) + twice { x => j = j - x } + thrice { j = j + 1 } + val f = new Range(1, 10).foreach + f(j -= _) + assert(j == 0, j) + new Range(1, 10).foreach { i1 => + new Range(2, 11).foreach { i2 => + j += i1 * i2 + } + } + } +} diff --git a/tests/run/inlineForeach.scala b/tests/run/inlineForeach.scala index 437e58cbfc5f..7f22f1cc3753 100644 --- a/tests/run/inlineForeach.scala +++ b/tests/run/inlineForeach.scala @@ -3,7 +3,7 @@ object Test { class Range(from: Int, end: Int) { inline - def foreach(inline op: Int => Unit): Unit = { + def foreach(op: => Int => Unit): Unit = { var i = from while (i < end) { op(i) @@ -36,7 +36,7 @@ object Test { } implicit class intArrayOps(arr: Array[Int]) { - inline def foreach(inline op: Int => Unit): Unit = { + inline def foreach(op: => Int => Unit): Unit = { var i = 0 while (i < arr.length) { op(arr(i)) diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala index 1b524f92bf24..37e66833a0dc 100644 --- a/tests/run/inlinedAssign.scala +++ b/tests/run/inlinedAssign.scala @@ -1,6 +1,6 @@ object Test { - inline def swap[T](x: T, inline x_= : T => Unit, y: T, inline y_= : T => Unit) = { + inline def swap[T](x: T, x_= : => T => Unit, y: T, y_= : => T => Unit) = { x_=(y) y_=(x) } diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index f501bff72381..34c579b22b2c 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -25,7 +25,7 @@ class Lst[+T](val elems: Any) extends AnyVal { def isEmpty = elems == null def nonEmpty = elems != null - inline def foreach(inline op: T => Unit): Unit = { + inline def foreach(op: => T => Unit): Unit = { def sharedOp(x: T) = op(x) elems match { case null => @@ -39,7 +39,7 @@ class Lst[+T](val elems: Any) extends AnyVal { /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. * Should be used only of `op` is small */ - inline def foreachInlined(inline op: T => Unit): Unit = elems match { + inline def foreachInlined(op: => T => Unit): Unit = elems match { case null => case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] var i = 0 @@ -60,7 +60,7 @@ class Lst[+T](val elems: Any) extends AnyVal { } /** `f` is pulled out, not duplicated */ - inline def map[U](inline f: T => U): Lst[U] = { + inline def map[U](f: => T => U): Lst[U] = { def op(x: T) = f(x) elems match { case null => Empty @@ -144,7 +144,7 @@ class Lst[+T](val elems: Any) extends AnyVal { } def filterNot(p: T => Boolean): Lst[T] = filter(!p(_)) - inline def exists(inline p: T => Boolean): Boolean = { + inline def exists(p: => T => Boolean): Boolean = { def op(x: T) = p(x) elems match { case null => false @@ -157,7 +157,7 @@ class Lst[+T](val elems: Any) extends AnyVal { } } - inline def forall(inline p: T => Boolean): Boolean = { + inline def forall(p: => T => Boolean): Boolean = { def op(x: T) = p(x) elems match { case null => true @@ -180,7 +180,7 @@ class Lst[+T](val elems: Any) extends AnyVal { elem == x } - inline def foldLeft[U](z: U)(inline f: (U, T) => U) = { + inline def foldLeft[U](z: U)(f: => (U, T) => U) = { def op(x: U, y: T) = f(x, y) elems match { case null => z @@ -194,7 +194,7 @@ class Lst[+T](val elems: Any) extends AnyVal { } } - inline def /: [U](z: U)(inline op: (U, T) => U) = foldLeft(z)(op) + inline def /: [U](z: U)(op: => (U, T) => U) = foldLeft(z)(op) def reduceLeft[U >: T](op: (U, U) => U) = elems match { case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] From a16146fd954686021e736fa586ac8dd7947a87ec Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 Jun 2018 14:46:49 +0200 Subject: [PATCH 20/21] Drop Modifiers.hasMod Since all Mod values are now reflected in flags and privateWithin, there is no need anymore to test whether a Mod exists. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 17c5e45a2740..0fb3ddd9bf36 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -195,12 +195,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def hasFlags = flags != EmptyFlags def hasAnnotations = annotations.nonEmpty def hasPrivateWithin = privateWithin != tpnme.EMPTY - def hasMod[T: ClassTag] = { - val cls = implicitly[ClassTag[T]].runtimeClass - mods.exists(mod => cls.isAssignableFrom(mod.getClass)) - } - private def isEnum = hasMod[Mod.Enum] || is(Enum, butNot = JavaDefined) + private def isEnum = is(Enum, butNot = JavaDefined) def isEnumCase = isEnum && is(Case) def isEnumClass = isEnum && !is(Case) From e8c96cbfcc72549949afb169f0c4bc0b3a53f4a4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 Jun 2018 14:54:52 +0200 Subject: [PATCH 21/21] Address reviewer comment on previous PR --- compiler/src/dotty/tools/dotc/transform/AccessProxies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index f5b207926f8a..6824df459bde 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -66,7 +66,7 @@ abstract class AccessProxies { /** An accessor symbol, create a fresh one unless one exists already */ private def accessorSymbol(owner: Symbol, accessorName: TermName, accessorInfo: Type, accessed: Symbol)(implicit ctx: Context) = { - def refersToAccessed(sym: Symbol) = accessedBy.get(sym) == Some(accessed) + def refersToAccessed(sym: Symbol) = accessedBy.get(sym).contains(accessed) owner.info.decl(accessorName).suchThat(refersToAccessed).symbol.orElse { val acc = newAccessorSymbol(owner, accessorName, accessorInfo, accessed.pos) accessedBy(acc) = accessed