Skip to content

Commit ea670d9

Browse files
authored
Merge pull request #6377 from dotty-staging/fix-#6341a
Fix #6341: Revise purity checking
2 parents a47babe + b9d8a2a commit ea670d9

File tree

4 files changed

+66
-37
lines changed

4 files changed

+66
-37
lines changed

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

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -366,31 +366,27 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
366366
// But if we do that the repl/vars test break. Need to figure out why that's the case.
367367
}
368368

369-
/** The purity level of this expression.
370-
* @return SimplyPure if expression has no side effects and cannot contain local definitions
371-
* Pure if expression has no side effects
372-
* Idempotent if running the expression a second time has no side effects
373-
* Impure otherwise
369+
/** The purity level of this expression. See docs for PurityLevel for what that means
374370
*
375-
* Note that purity and idempotency are different. References to modules and lazy
376-
* vals are impure (side-effecting) both because side-effecting code may be executed and because the first reference
371+
* Note that purity and idempotency are treated differently.
372+
* References to modules and lazy vals are impure (side-effecting) both because
373+
* side-effecting code may be executed and because the first reference
377374
* takes a different code path than all to follow; but they are idempotent
378375
* because running the expression a second time gives the cached result.
379376
*/
380377
def exprPurity(tree: Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match {
381378
case EmptyTree
382379
| This(_)
383380
| Super(_, _)
384-
| Literal(_)
385-
| Closure(_, _, _) =>
386-
SimplyPure
381+
| Literal(_) =>
382+
PurePath
387383
case Ident(_) =>
388384
refPurity(tree)
389385
case Select(qual, _) =>
390386
if (tree.symbol.is(Erased)) Pure
391-
else refPurity(tree).min(exprPurity(qual))
392-
case New(_) =>
393-
SimplyPure
387+
else refPurity(tree) `min` exprPurity(qual)
388+
case New(_) | Closure(_, _, _) =>
389+
Pure
394390
case TypeApply(fn, _) =>
395391
if (fn.symbol.is(Erased)) Pure else exprPurity(fn)
396392
case Apply(fn, args) =>
@@ -416,37 +412,49 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
416412
Impure
417413
}
418414

419-
private def minOf(l0: PurityLevel, ls: List[PurityLevel]) = (l0 /: ls)(_ min _)
415+
private def minOf(l0: PurityLevel, ls: List[PurityLevel]) = (l0 /: ls)(_ `min` _)
416+
417+
def isPurePath(tree: Tree)(implicit ctx: Context): Boolean = tree.tpe match {
418+
case tpe: ConstantType => exprPurity(tree) >= Pure
419+
case _ => exprPurity(tree) == PurePath
420+
}
421+
422+
def isPureExpr(tree: Tree)(implicit ctx: Context): Boolean =
423+
exprPurity(tree) >= Pure
424+
425+
def isIdempotentPath(tree: Tree)(implicit ctx: Context): Boolean = tree.tpe match {
426+
case tpe: ConstantType => exprPurity(tree) >= Idempotent
427+
case _ => exprPurity(tree) >= IdempotentPath
428+
}
420429

421-
def isSimplyPure(tree: Tree)(implicit ctx: Context): Boolean = exprPurity(tree) == SimplyPure
422-
def isPureExpr(tree: Tree)(implicit ctx: Context): Boolean = exprPurity(tree) >= Pure
423-
def isIdempotentExpr(tree: Tree)(implicit ctx: Context): Boolean = exprPurity(tree) >= Idempotent
430+
def isIdempotentExpr(tree: Tree)(implicit ctx: Context): Boolean =
431+
exprPurity(tree) >= Idempotent
424432

425433
def isPureBinding(tree: Tree)(implicit ctx: Context): Boolean = statPurity(tree) >= Pure
426434

427435
/** The purity level of this reference.
428436
* @return
429-
* SimplyPure if reference is (nonlazy and stable) or to a parameterized function
430-
* Idempotent if reference is lazy and stable
431-
* Impure otherwise
437+
* PurePath if reference is (nonlazy and stable) or to a parameterized function
438+
* IdempotentPath if reference is lazy and stable
439+
* Impure otherwise
432440
* @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable
433441
* flags set.
434442
*/
435443
def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = {
436444
val sym = tree.symbol
437445
if (!tree.hasType) Impure
438-
else if (!tree.tpe.widen.isParameterless || sym.isEffectivelyErased) SimplyPure
446+
else if (!tree.tpe.widen.isParameterless || sym.isEffectivelyErased) PurePath
439447
else if (!sym.isStableMember) Impure
440448
else if (sym.is(Module))
441-
if (sym.moduleClass.isNoInitsClass) Pure else Idempotent
442-
else if (sym.is(Lazy)) Idempotent
443-
else SimplyPure
449+
if (sym.moduleClass.isNoInitsClass) PurePath else IdempotentPath
450+
else if (sym.is(Lazy)) IdempotentPath
451+
else PurePath
444452
}
445453

446454
def isPureRef(tree: Tree)(implicit ctx: Context): Boolean =
447-
refPurity(tree) == SimplyPure
455+
refPurity(tree) == PurePath
448456
def isIdempotentRef(tree: Tree)(implicit ctx: Context): Boolean =
449-
refPurity(tree) >= Idempotent
457+
refPurity(tree) >= IdempotentPath
450458

451459
/** (1) If `tree` is a constant expression, its value as a Literal,
452460
* or `tree` itself otherwise.
@@ -840,13 +848,29 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
840848
}
841849

842850
object TreeInfo {
851+
/** A purity level is represented as a bitset (expressed as an Int) */
843852
class PurityLevel(val x: Int) extends AnyVal {
844-
def >= (that: PurityLevel): Boolean = x >= that.x
845-
def min(that: PurityLevel): PurityLevel = new PurityLevel(x min that.x)
853+
/** `this` contains the bits of `that` */
854+
def >= (that: PurityLevel): Boolean = (x & that.x) == that.x
855+
856+
/** The intersection of the bits of `this` and `that` */
857+
def min(that: PurityLevel): PurityLevel = new PurityLevel(x & that.x)
846858
}
847859

848-
val SimplyPure: PurityLevel = new PurityLevel(3)
849-
val Pure: PurityLevel = new PurityLevel(2)
860+
/** An expression is a stable path. Requires that expression is at least idempotent */
861+
val Path: PurityLevel = new PurityLevel(4)
862+
863+
/** The expression has no side effects */
864+
val Pure: PurityLevel = new PurityLevel(3)
865+
866+
/** Running the expression a second time has no side effects. Implied by `Pure`. */
850867
val Idempotent: PurityLevel = new PurityLevel(1)
868+
851869
val Impure: PurityLevel = new PurityLevel(0)
870+
871+
/** A stable path that is evaluated without side effects */
872+
val PurePath: PurityLevel = new PurityLevel(Pure.x | Path.x)
873+
874+
/** A stable path that is also idempotent */
875+
val IdempotentPath: PurityLevel = new PurityLevel(Idempotent.x | Path.x)
852876
}

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,10 @@ abstract class Lifter {
121121
* val x0 = pre
122122
* x0.f(...)
123123
*
124-
* unless `pre` is a `New` or `pre` is idempotent.
124+
* unless `pre` is idempotent.
125125
*/
126-
def liftPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = tree match {
127-
case New(_) => tree
128-
case _ => if (isIdempotentExpr(tree)) tree else lift(defs, tree)
129-
}
126+
def liftPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree =
127+
if (isIdempotentExpr(tree)) tree else lift(defs, tree)
130128
}
131129

132130
/** No lifting at all */
@@ -142,7 +140,7 @@ object LiftImpure extends LiftImpure
142140

143141
/** Lift all impure or complex arguments */
144142
class LiftComplex extends Lifter {
145-
def noLift(expr: tpd.Tree)(implicit ctx: Context): Boolean = tpd.isSimplyPure(expr)
143+
def noLift(expr: tpd.Tree)(implicit ctx: Context): Boolean = tpd.isPurePath(expr)
146144
override def exprLifter: Lifter = LiftToDefs
147145
}
148146
object LiftComplex extends LiftComplex

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
285285
(tp.paramNames, tp.paramInfos, argss.head).zipped.foreach { (name, paramtp, arg) =>
286286
paramSpan(name) = arg.span
287287
paramBinding(name) = arg.tpe.dealias match {
288-
case _: SingletonType if isIdempotentExpr(arg) => arg.tpe
288+
case _: SingletonType if isIdempotentPath(arg) => arg.tpe
289289
case _ => paramBindingDef(name, paramtp, arg, bindingsBuf).symbol.termRef
290290
}
291291
}

tests/run/i6341.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Test extends App {
2+
class Config(val t1: Int)
3+
4+
inline def m(t2:Int) = t2
5+
6+
m(new Config(3).t1)
7+
}

0 commit comments

Comments
 (0)