Skip to content

Commit 411e040

Browse files
committed
Introduce readOnly purity level
We now distinguish between read-only and impure expressions. The immediate benefits are: - we don't need to keep unused definitions of pattern variables whose rhs is a case accessor. - we don't need to generate code for accessors of Constant return types.
1 parent 93f0ff3 commit 411e040

File tree

6 files changed

+21
-16
lines changed

6 files changed

+21
-16
lines changed

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
312312
/** The purity level of this statement.
313313
* @return pure if statement has no side effects
314314
* idempotent if running the statement a second time has no side effects
315+
* readonly if statement has only read effects
315316
* impure otherwise
316317
*/
317318
private def statPurity(tree: Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match {
@@ -331,6 +332,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
331332
/** The purity level of this expression.
332333
* @return pure if expression has no side effects
333334
* idempotent if running the expression a second time has no side effects
335+
* readonly if statement has only read effects
334336
* impure otherwise
335337
*
336338
* Note that purity and idempotency are different. References to modules and lazy
@@ -351,12 +353,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
351353
refPurity(tree).min(exprPurity(qual))
352354
case TypeApply(fn, _) =>
353355
exprPurity(fn)
354-
/*
355-
* Not sure we'll need that. Comment out until we find out
356-
case Apply(Select(free @ Ident(_), nme.apply), _) if free.symbol.name endsWith nme.REIFY_FREE_VALUE_SUFFIX =>
357-
// see a detailed explanation of this trick in `GenSymbols.reifyFreeTerm`
358-
free.symbol.hasStableFlag && isIdempotentExpr(free)
359-
*/
360356
case Apply(fn, args) =>
361357
def isKnownPureOp(sym: Symbol) =
362358
sym.owner.isPrimitiveValueClass || sym.owner == defn.StringClass
@@ -381,25 +377,31 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
381377

382378
def isPureExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) == Pure
383379
def isIdempotentExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) >= Idempotent
380+
def isReadOnlyExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) >= ReadOnly
384381

385382
/** The purity level of this reference.
386383
* @return
387384
* pure if reference is (nonlazy and stable) or to a parameterized function
388385
* idempotent if reference is lazy and stable
386+
* readonly if reference is to a val/var or to an accessor
389387
* impure otherwise
390388
* @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable
391389
* flags set.
392390
*/
393391
private def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel =
394392
if (!tree.tpe.widen.isParameterless) Pure
395-
else if (!tree.symbol.isStable) Impure
393+
else if (!tree.symbol.isStable)
394+
if (!tree.symbol.is(Method) || tree.symbol.is(AnyAccessor)) ReadOnly
395+
else Impure
396396
else if (tree.symbol.is(Lazy)) Idempotent // TODO add Module flag, sinxce Module vals or not Lazy from the start.
397397
else Pure
398398

399399
def isPureRef(tree: Tree)(implicit ctx: Context) =
400400
refPurity(tree) == Pure
401401
def isIdempotentRef(tree: Tree)(implicit ctx: Context) =
402402
refPurity(tree) >= Idempotent
403+
def isReadOnlyRef(tree: Tree)(implicit ctx: Context) =
404+
refPurity(tree) >= ReadOnly
403405

404406
/** If `tree` is a constant expression, its value as a Literal,
405407
* or `tree` itself otherwise.
@@ -706,11 +708,12 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
706708

707709
object TreeInfo {
708710
class PurityLevel(val x: Int) extends AnyVal {
709-
def >= (that: PurityLevel) = x >= that.x
710-
def min(that: PurityLevel) = new PurityLevel(x min that.x)
711+
def >= (that: PurityLevel) = (x & that.x) == that.x
712+
def min(that: PurityLevel) = new PurityLevel(x & that.x)
711713
}
712714

713-
val Pure = new PurityLevel(2)
715+
val Pure = new PurityLevel(3)
716+
val ReadOnly = new PurityLevel(2)
714717
val Idempotent = new PurityLevel(1)
715718
val Impure = new PurityLevel(0)
716719
}

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,9 @@ object Flags {
479479
/** Accessors always have these flags set */
480480
final val AccessorCreationFlags = Method | Accessor
481481

482+
/** Case, Param, or field accessors */
483+
final val AnyAccessor = Accessor | CaseAccessor | ParamAccessor
484+
482485
/** Pure interfaces always have these flags */
483486
final val PureInterfaceCreationFlags = Trait | NoInits | PureInterface
484487

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ object Erasure {
172172
}
173173

174174
def constant(tree: Tree, const: Tree)(implicit ctx: Context) =
175-
(if (isPureExpr(tree)) const else Block(tree :: Nil, const))
175+
(if (isReadOnlyExpr(tree)) const else Block(tree :: Nil, const))
176176
.withPos(tree.pos)
177177

178178
final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = ctx.traceIndented(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") {

compiler/src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer =>
9696
if (to && !qualifier.tpe.isNotNull) qualifier.testNotNull
9797
else {
9898
val literal = Literal(Constant(to))
99-
if (!isPureExpr(qualifier)) Block(List(qualifier), literal)
100-
else literal
99+
if (isReadOnlyExpr(qualifier)) literal else Block(List(qualifier), literal)
101100
}
102101

103102
/** Attempts to rewrite type test to either `scrutinee ne null` or a

compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,7 @@ object PatternMatcher {
689689

690690
def toDrop(sym: Symbol) = initializer.get(sym) match {
691691
case Some(rhs) =>
692-
isPatmatGenerated(sym) && refCount(sym) <= 1 && sym != topSym && isPureExpr(rhs)
692+
isPatmatGenerated(sym) && refCount(sym) <= 1 && sym != topSym && isReadOnlyExpr(rhs)
693693
case none =>
694694
false
695695
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1724,8 +1724,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
17241724
traverse(stats ++ rest)
17251725
case stat :: rest =>
17261726
val stat1 = typed(stat)(ctx.exprContext(stat, exprOwner))
1727-
if (!ctx.isAfterTyper && isPureExpr(stat1) && !stat1.tpe.isRef(defn.UnitClass))
1728-
ctx.warning(em"a pure expression does nothing in statement position", stat.pos)
1727+
if (!ctx.isAfterTyper && isReadOnlyExpr(stat1) && !stat1.tpe.isRef(defn.UnitClass))
1728+
ctx.warning(em"a read-only expression does nothing in statement position", stat.pos)
17291729
buf += stat1
17301730
traverse(rest)
17311731
case nil =>

0 commit comments

Comments
 (0)