@@ -282,41 +282,46 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
282
282
283
283
trait TypedTreeInfo extends TreeInfo [Type ] { self : Trees .Instance [Type ] =>
284
284
285
- /** Is tree a definition that has no side effects when
286
- * evaluated as part of a block after the first time?
285
+ /** The purity level of this statement.
286
+ * @return pure if statement has no side effects
287
+ * idempotent if running the statement a second time has no side effects
288
+ * impure otherwise
287
289
*/
288
- def isIdempotentDef (tree : tpd.Tree )(implicit ctx : Context ): Boolean = unsplice(tree) match {
290
+ private def statPurity (tree : tpd.Tree )(implicit ctx : Context ): PurityLevel = unsplice(tree) match {
289
291
case EmptyTree
290
292
| TypeDef (_, _, _)
291
293
| Import (_, _)
292
294
| DefDef (_, _, _, _, _, _) =>
293
- true
295
+ Pure
294
296
case ValDef (mods, _, _, rhs) =>
295
- ! (mods is Mutable ) && isIdempotentExpr (rhs)
297
+ if (mods is Mutable ) Impure else exprPurity (rhs)
296
298
case _ =>
297
- false
299
+ Impure
298
300
}
299
301
300
- /** Is tree an expression which can be inlined without affecting program semantics?
302
+ /** The purity level of this expression.
303
+ * @return pure if expression has no side effects
304
+ * idempotent if running the expression a second time has no side effects
305
+ * impure otherwise
301
306
*
302
- * Note that this is not called "isExprPure" since purity (lack of side-effects)
303
- * is not the litmus test. References to modules and lazy vals are side-effecting,
304
- * both because side-effecting code may be executed and because the first reference
305
- * takes a different code path than all to follow; but they are safe to inline
306
- * because the expression result from evaluating them is always the same.
307
+ * Note that purity and idempotency are different. References to modules and lazy
308
+ * vals are impure (side-effecting) both because side-effecting code may be executed and because the first reference
309
+ * takes a different code path than all to follow; but they are idempotent
310
+ * because running the expression a second time gives the cached result.
307
311
*/
308
- def isIdempotentExpr (tree : tpd.Tree )(implicit ctx : Context ): Boolean = unsplice(tree) match {
312
+ private def exprPurity (tree : tpd.Tree )(implicit ctx : Context ): PurityLevel = unsplice(tree) match {
309
313
case EmptyTree
310
314
| This (_)
311
315
| Super (_, _)
312
316
| Literal (_) =>
313
- true
317
+ Pure
314
318
case Ident (_) =>
315
- isIdempotentRef (tree)
319
+ refPurity (tree)
316
320
case Select (qual, _) =>
317
- isIdempotentRef(tree) && isIdempotentExpr(qual)
321
+ refPurity(tree).min(
322
+ if (tree.symbol.is(Inline )) Pure else exprPurity(qual))
318
323
case TypeApply (fn, _) =>
319
- isIdempotentExpr (fn)
324
+ exprPurity (fn)
320
325
/*
321
326
* Not sure we'll need that. Comment out until we find out
322
327
case Apply(Select(free @ Ident(_), nme.apply), _) if free.symbol.name endsWith nme.REIFY_FREE_VALUE_SUFFIX =>
@@ -326,21 +331,36 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
326
331
case Apply (fn, Nil ) =>
327
332
// Note: After uncurry, field accesses are represented as Apply(getter, Nil),
328
333
// so an Apply can also be pure.
329
- // However, before typing, applications of nullary functional values are also
330
- // Apply(function, Nil) trees. To prevent them from being treated as pure,
331
- // we check that the callee is a method.
332
- // The callee might also be a Block, which has a null symbol, so we guard against that (SI-7185)
333
- fn.symbol != null && (fn.symbol is (Method , butNot = Lazy )) && isIdempotentExpr(fn)
334
+ if (fn.symbol is Stable ) exprPurity(fn) else Impure
334
335
case Typed (expr, _) =>
335
- isIdempotentExpr (expr)
336
+ exprPurity (expr)
336
337
case Block (stats, expr) =>
337
- (stats forall isIdempotentDef) && isIdempotentExpr(expr )
338
+ (exprPurity(expr) /: stats.map(statPurity))(_ min _ )
338
339
case _ =>
339
- false
340
+ Impure
340
341
}
341
342
343
+ def isPureExpr (tree : tpd.Tree )(implicit ctx : Context ) = exprPurity(tree) == Pure
344
+ def isIdempotentExpr (tree : tpd.Tree )(implicit ctx : Context ) = exprPurity(tree) >= Idempotent
345
+
346
+ /** The purity level of this reference.
347
+ * @return
348
+ * pure if reference is (nonlazy and stable) or to a parameterized function
349
+ * idempotent if reference is lazy and stable
350
+ * impure otherwise
351
+ * @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable
352
+ * flags set.
353
+ */
354
+ private def refPurity (tree : tpd.Tree )(implicit ctx : Context ): PurityLevel =
355
+ if (! tree.tpe.widen.isParameterless) Pure
356
+ else if (! tree.symbol.is(Stable )) Impure
357
+ else if (tree.symbol.is(Lazy )) Idempotent // TODO add Module flag, sinxce Module vals or not Lazy from the start.
358
+ else Pure
359
+
360
+ def isPureRef (tree : tpd.Tree )(implicit ctx : Context ) =
361
+ refPurity(tree) == Pure
342
362
def isIdempotentRef (tree : tpd.Tree )(implicit ctx : Context ) =
343
- tree.symbol.isStable || ! tree.tpe.widen.isParameterless
363
+ refPurity( tree) >= Idempotent
344
364
345
365
/** Is symbol potentially a getter of a mutable variable?
346
366
*/
@@ -456,6 +476,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
456
476
case nil =>
457
477
Nil
458
478
}
479
+
480
+ private class PurityLevel (val x : Int ) {
481
+ def >= (that : PurityLevel ) = x >= that.x
482
+ def min (that : PurityLevel ) = new PurityLevel (x min that.x)
483
+ }
484
+
485
+ private val Pure = new PurityLevel (2 )
486
+ private val Idempotent = new PurityLevel (1 )
487
+ private val Impure = new PurityLevel (0 )
459
488
}
460
489
461
490
/** a Match(Typed(_, tpt), _) must be translated into a switch if isSwitchAnnotation(tpt.tpe)
0 commit comments