diff options
author | Dmitry Petrashko <dark@d-d.me> | 2015-09-14 15:32:23 +0200 |
---|---|---|
committer | Dmitry Petrashko <dark@d-d.me> | 2015-09-14 15:32:23 +0200 |
commit | e7ef08da8a699bdb96d0469c2a76c65709c29324 (patch) | |
tree | d3e2554aeeb2b0c3452f99937941ade6ee66d9e0 /src/dotty/tools/dotc | |
parent | 2a2940e4a92508b767885c1c5ef63b09b564aa43 (diff) | |
parent | e491cfbc14ec36afccc80a589f7bc18d62f37b2f (diff) | |
download | dotty-e7ef08da8a699bdb96d0469c2a76c65709c29324.tar.gz dotty-e7ef08da8a699bdb96d0469c2a76c65709c29324.tar.bz2 dotty-e7ef08da8a699bdb96d0469c2a76c65709c29324.zip |
Merge pull request #773 from dotty-staging/final-vals2
Memoize: duplicate scala2 behaviour: don't create fields for final vals.
Diffstat (limited to 'src/dotty/tools/dotc')
-rw-r--r-- | src/dotty/tools/dotc/Compiler.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/ast/TreeInfo.scala | 14 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Types.scala | 8 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/Memoize.scala | 25 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/PostTyper.scala | 41 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/ConstFold.scala | 8 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Namer.scala | 13 |
7 files changed, 87 insertions, 23 deletions
diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index e4b328a82..c537d8885 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -56,7 +56,6 @@ class Compiler { List(new VCInlineMethods, new SeqLiterals, new InterceptedMethods, - new Literalize, new Getters, new ClassTags, new ElimByName, diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index 82c8c9d60..1266ebbd9 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -326,18 +326,26 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => // see a detailed explanation of this trick in `GenSymbols.reifyFreeTerm` free.symbol.hasStableFlag && isIdempotentExpr(free) */ - case Apply(fn, Nil) => + case Apply(fn, args) => + def isKnownPureOp(sym: Symbol) = + sym.owner.isPrimitiveValueClass || sym.owner == defn.StringClass // Note: After uncurry, field accesses are represented as Apply(getter, Nil), // so an Apply can also be pure. - if (fn.symbol is Stable) exprPurity(fn) else Impure + if (args.isEmpty && fn.symbol.is(Stable)) exprPurity(fn) + else if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol)) + // A constant expression with pure arguments is pure. + minOf(exprPurity(fn), args.map(exprPurity)) + else Impure case Typed(expr, _) => exprPurity(expr) case Block(stats, expr) => - (exprPurity(expr) /: stats.map(statPurity))(_ min _) + minOf(exprPurity(expr), stats.map(statPurity)) case _ => Impure } + 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 diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 5fd3b81d0..d04da30c0 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -715,6 +715,14 @@ object Types { case _ => this } + /** Widen from TermRef to its underlying non-termref + * base type, while also skipping Expr types. + */ + final def widenTermRefExpr(implicit ctx: Context): Type = stripTypeVar match { + case tp: TermRef if !tp.isOverloaded => tp.underlying.widenExpr.widenTermRefExpr + case _ => this + } + /** Widen from ExprType type to its result type. * (Note: no stripTypeVar needed because TypeVar's can't refer to ExprTypes.) */ diff --git a/src/dotty/tools/dotc/transform/Memoize.scala b/src/dotty/tools/dotc/transform/Memoize.scala index 63466dc46..fbf8ed763 100644 --- a/src/dotty/tools/dotc/transform/Memoize.scala +++ b/src/dotty/tools/dotc/transform/Memoize.scala @@ -70,13 +70,26 @@ import Decorators._ lazy val field = sym.field.orElse(newField).asTerm if (sym.is(Accessor, butNot = NoFieldNeeded)) if (sym.isGetter) { - var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform) - if (isWildcardArg(rhs)) rhs = EmptyTree - val fieldDef = transformFollowing(ValDef(field, rhs)) - val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)) - Thicket(fieldDef, getterDef) + def skipBlocks(t: Tree): Tree = t match { + case Block(_, t1) => skipBlocks(t1) + case _ => t } - else if (sym.isSetter) { + skipBlocks(tree.rhs) match { + case lit: Literal if sym.is(Final) && isIdempotentExpr(tree.rhs) => + // duplicating scalac behavior: for final vals that have rhs as constant, we do not create a field + // and instead return the value. This seemingly minor optimization has huge effect on initialization + // order and the values that can be observed during superconstructor call + + // see remark about idempotency in PostTyper#normalizeTree + cpy.DefDef(tree)(rhs = lit) + case _ => + var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform) + if (isWildcardArg(rhs)) rhs = EmptyTree + val fieldDef = transformFollowing(ValDef(field, rhs)) + val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)) + Thicket(fieldDef, getterDef) + } + } else if (sym.isSetter) { if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion val initializer = Assign(ref(field), ref(tree.vparamss.head.head.symbol)) cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info)) diff --git a/src/dotty/tools/dotc/transform/PostTyper.scala b/src/dotty/tools/dotc/transform/PostTyper.scala index a0670bca0..7a25a9870 100644 --- a/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/src/dotty/tools/dotc/transform/PostTyper.scala @@ -68,8 +68,43 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran // TODO fill in } - private def normalizeTypeTree(tree: Tree)(implicit ctx: Context) = { - def norm(tree: Tree) = if (tree.isType) TypeTree(tree.tpe).withPos(tree.pos) else tree + /** Check bounds of AppliedTypeTrees. + * Replace type trees with TypeTree nodes. + * Replace constant expressions with Literal nodes. + * Note: Demanding idempotency instead of purityin 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 = { + def literalize(tp: Type): Tree = tp.widenTermRefExpr match { + case ConstantType(value) if isIdempotentExpr(tree) => Literal(value) + case _ => tree + } + def norm(tree: Tree) = + if (tree.isType) TypeTree(tree.tpe).withPos(tree.pos) + else literalize(tree.tpe) tree match { case tree: TypeTree => tree @@ -115,7 +150,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran } override def transform(tree: Tree)(implicit ctx: Context): Tree = - try normalizeTypeTree(tree) match { + try normalizeTree(tree) match { case tree: Ident => tree.tpe match { case tpe: ThisType => This(tpe.cls).withPos(tree.pos) diff --git a/src/dotty/tools/dotc/typer/ConstFold.scala b/src/dotty/tools/dotc/typer/ConstFold.scala index ac1c7260b..68a5d05f5 100644 --- a/src/dotty/tools/dotc/typer/ConstFold.scala +++ b/src/dotty/tools/dotc/typer/ConstFold.scala @@ -20,16 +20,16 @@ object ConstFold { def apply(tree: Tree)(implicit ctx: Context): Tree = finish(tree) { tree match { case Apply(Select(xt, op), yt :: Nil) => - xt.tpe match { + xt.tpe.widenTermRefExpr match { case ConstantType(x) => - yt.tpe match { + yt.tpe.widenTermRefExpr match { case ConstantType(y) => foldBinop(op, x, y) case _ => null } case _ => null } case Select(xt, op) => - xt.tpe match { + xt.tpe.widenTermRefExpr match { case ConstantType(x) => foldUnop(op, x) case _ => null } @@ -42,7 +42,7 @@ object ConstFold { */ def apply(tree: Tree, pt: Type)(implicit ctx: Context): Tree = finish(apply(tree)) { - tree.tpe match { + tree.tpe.widenTermRefExpr match { case ConstantType(x) => x convertTo pt case _ => null } diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 0e8b4d8cf..99119acb3 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -707,18 +707,19 @@ class Namer { typer: Typer => // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") def isInline = sym.is(Final, butNot = Method) - def widenRhs(tp: Type): Type = tp match { - case tp: TermRef => widenRhs(tp.underlying) - case tp: ExprType => widenRhs(tp.resultType) + def widenRhs(tp: Type): Type = tp.widenTermRefExpr match { case tp: ConstantType if isInline => tp case _ => tp.widen.approximateUnion } val rhsCtx = ctx.addMode(Mode.InferringReturnType) - def rhsType = typedAheadExpr(mdef.rhs, rhsProto)(rhsCtx).tpe + def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe def cookedRhsType = ctx.deskolemize(widenRhs(rhsType)) - def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.pos) + lazy val lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.pos) //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") - if (inherited.exists) inherited + if (inherited.exists) + if (sym.is(Final, butNot = Method) && lhsType.isInstanceOf[ConstantType]) + lhsType // keep constant types that fill in for a non-constant (to be revised when inline has landed). + else inherited else { if (sym is Implicit) { val resStr = if (mdef.isInstanceOf[DefDef]) "result " else "" |