From 7967aa97607964f78c92911038412192cfea3778 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 21 Oct 2016 14:50:11 +0200 Subject: Don't convert to literals before pickling We need to keep the original expressions around for accurate linking with the source file. That holds also if the expression has a constant type, so it should not be converted to a literal before pickling. Instead it will be converted in FirstTransform. --- src/dotty/tools/dotc/ast/TreeInfo.scala | 69 ++++++++++++++++++---- .../tools/dotc/transform/FirstTransform.scala | 22 ++++++- src/dotty/tools/dotc/transform/PostTyper.scala | 45 +++----------- 3 files changed, 86 insertions(+), 50 deletions(-) diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index 7911840c6..8b2915174 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -6,6 +6,7 @@ import core._ import Flags._, Trees._, Types._, Contexts._ import Names._, StdNames._, NameOps._, Decorators._, Symbols._ import util.HashSet +import typer.ConstFold trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => import TreeInfo._ @@ -289,13 +290,14 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => import TreeInfo._ + import tpd._ /** The purity level of this statement. * @return pure if statement has no side effects * idempotent if running the statement a second time has no side effects * impure otherwise */ - private def statPurity(tree: tpd.Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { + private def statPurity(tree: Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { case EmptyTree | TypeDef(_, _) | Import(_, _) @@ -319,7 +321,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * takes a different code path than all to follow; but they are idempotent * because running the expression a second time gives the cached result. */ - private def exprPurity(tree: tpd.Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { + private def exprPurity(tree: Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { case EmptyTree | This(_) | Super(_, _) @@ -358,8 +360,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => private def minOf(l0: PurityLevel, ls: List[PurityLevel]) = (l0 /: ls)(_ min _) - def isPureExpr(tree: tpd.Tree)(implicit ctx: Context) = exprPurity(tree) == Pure - def isIdempotentExpr(tree: tpd.Tree)(implicit ctx: Context) = exprPurity(tree) >= Idempotent + def isPureExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) == Pure + def isIdempotentExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) >= Idempotent /** The purity level of this reference. * @return @@ -369,17 +371,62 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable * flags set. */ - private def refPurity(tree: tpd.Tree)(implicit ctx: Context): PurityLevel = + private def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = if (!tree.tpe.widen.isParameterless) Pure else if (!tree.symbol.isStable) Impure else if (tree.symbol.is(Lazy)) Idempotent // TODO add Module flag, sinxce Module vals or not Lazy from the start. else Pure - def isPureRef(tree: tpd.Tree)(implicit ctx: Context) = + def isPureRef(tree: Tree)(implicit ctx: Context) = refPurity(tree) == Pure - def isIdempotentRef(tree: tpd.Tree)(implicit ctx: Context) = + def isIdempotentRef(tree: Tree)(implicit ctx: Context) = refPurity(tree) >= Idempotent + /** If `tree` is a constant expression, its value as a Literal, + * or `tree` itself otherwise. + * + * Note: Demanding idempotency instead of purity in literalize is strictly speaking too loose. + * Example + * + * object O { final val x = 42; println("43") } + * O.x + * + * Strictly speaking we can't replace `O.x` with `42`. But this would make + * most expressions non-constant. Maybe we can change the spec to accept this + * kind of eliding behavior. Or else enforce true purity in the compiler. + * The choice will be affected by what we will do with `inline` and with + * Singleton type bounds (see SIP 23). Presumably + * + * object O1 { val x: Singleton = 42; println("43") } + * object O2 { inline val x = 42; println("43") } + * + * should behave differently. + * + * O1.x should have the same effect as { println("43"); 42 } + * + * whereas + * + * O2.x = 42 + * + * Revisit this issue once we have implemented `inline`. Then we can demand + * purity of the prefix unless the selection goes to an inline val. + * + * Note: This method should be applied to all term tree nodes that are not literals, + * that can be idempotent, and that can have constant types. So far, only nodes + * of the following classes qualify: + * + * Ident + * Select + * TypeApply + */ + def constToLiteral(tree: Tree)(implicit ctx: Context): Tree = { + val tree1 = ConstFold(tree) + tree1.tpe.widenTermRefExpr match { + case ConstantType(value) if isIdempotentExpr(tree1) => Literal(value) + case _ => tree1 + } + } + /** Is symbol potentially a getter of a mutable variable? */ def mayBeVarGetter(sym: Symbol)(implicit ctx: Context): Boolean = { @@ -394,7 +441,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => /** Is tree a reference to a mutable variable, or to a potential getter * that has a setter in the same class? */ - def isVariableOrGetter(tree: tpd.Tree)(implicit ctx: Context) = { + def isVariableOrGetter(tree: Tree)(implicit ctx: Context) = { def sym = tree.symbol def isVar = sym is Mutable def isGetter = @@ -419,8 +466,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => } /** Strips layers of `.asInstanceOf[T]` / `_.$asInstanceOf[T]()` from an expression */ - def stripCast(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = { - def isCast(sel: tpd.Tree) = sel.symbol == defn.Any_asInstanceOf + def stripCast(tree: Tree)(implicit ctx: Context): Tree = { + def isCast(sel: Tree) = sel.symbol == defn.Any_asInstanceOf unsplice(tree) match { case TypeApply(sel @ Select(inner, _), _) if isCast(sel) => stripCast(inner) @@ -456,7 +503,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => } /** If tree is a closure, its body, otherwise tree itself */ - def closureBody(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { + def closureBody(tree: Tree)(implicit ctx: Context): Tree = tree match { case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, Closure(_, _, _)) => meth.rhs case _ => tree } diff --git a/src/dotty/tools/dotc/transform/FirstTransform.scala b/src/dotty/tools/dotc/transform/FirstTransform.scala index adadf3e76..597146514 100644 --- a/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -28,7 +28,9 @@ import StdNames._ * - ensures there are companion objects for all classes except module classes * - eliminates some kinds of trees: Imports, NamedArgs * - stubs out native methods - * - eliminate self tree in Template and self symbol in ClassInfo + * - eliminates self tree in Template and self symbol in ClassInfo + * - collapsess all type trees to trees of class TypeTree + * - converts idempotent expressions with constant types */ class FirstTransform extends MiniPhaseTransform with InfoTransformer with AnnotationTransformer { thisTransformer => import ast.tpd._ @@ -166,10 +168,24 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota } override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = - if (tree.isType) TypeTree(tree.tpe).withPos(tree.pos) else tree + if (tree.isType) TypeTree(tree.tpe).withPos(tree.pos) + else constToLiteral(tree) override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = - if (tree.isType) TypeTree(tree.tpe).withPos(tree.pos) else tree + if (tree.isType) TypeTree(tree.tpe).withPos(tree.pos) + else constToLiteral(tree) + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) = + constToLiteral(tree) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + constToLiteral(tree) + + override def transformTyped(tree: Typed)(implicit ctx: Context, info: TransformerInfo) = + constToLiteral(tree) + + override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo) = + constToLiteral(tree) // invariants: all modules have companion objects // all types are TypeTrees diff --git a/src/dotty/tools/dotc/transform/PostTyper.scala b/src/dotty/tools/dotc/transform/PostTyper.scala index 650bc4968..b72785dce 100644 --- a/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/src/dotty/tools/dotc/transform/PostTyper.scala @@ -77,42 +77,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran } /** Check bounds of AppliedTypeTrees. - * Replace constant expressions with Literal nodes. - * Note: Demanding idempotency instead of purity in literalize is strictly speaking too loose. - * Example - * - * object O { final val x = 42; println("43") } - * O.x - * - * Strictly speaking we can't replace `O.x` with `42`. But this would make - * most expressions non-constant. Maybe we can change the spec to accept this - * kind of eliding behavior. Or else enforce true purity in the compiler. - * The choice will be affected by what we will do with `inline` and with - * Singleton type bounds (see SIP 23). Presumably - * - * object O1 { val x: Singleton = 42; println("43") } - * object O2 { inline val x = 42; println("43") } - * - * should behave differently. - * - * O1.x should have the same effect as { println("43"); 42 } - * - * whereas - * - * O2.x = 42 - * - * Revisit this issue once we have implemented `inline`. Then we can demand - * purity of the prefix unless the selection goes to an inline val. */ - private def normalizeTree(tree: Tree)(implicit ctx: Context): Tree = - if (tree.isType) { - Checking.typeCheck(tree) - tree - } - else tree.tpe.widenTermRefExpr match { - case ConstantType(value) if isIdempotentExpr(tree) => Literal(value) - case _ => tree - } + private def normalizeTree(tree: Tree)(implicit ctx: Context): Tree = { + if (tree.isType) Checking.typeCheck(tree) + tree + } /** If the type of `tree` is a TermRefWithSignature with an underdefined * signature, narrow the type by re-computing the signature (which should @@ -158,7 +127,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran case pkg: PackageClassDenotation if !tree.symbol.maybeOwner.is(Package) => transformSelect(cpy.Select(tree)(qual select pkg.packageObj.symbol, tree.name), targs) case _ => - superAcc.transformSelect(super.transform(tree), targs) + val tree1 = super.transform(tree) + constToLiteral(tree1) match { + case _: Literal => tree1 + case _ => superAcc.transformSelect(tree1, targs) + } } } -- cgit v1.2.3