@@ -242,7 +242,10 @@ object Semantic:
242242 /** The cache for expression values from last iteration */
243243 private var last : ExprValueCache = Map .empty
244244
245- /** The updated cache for expression values based on the cache values from the last iteration */
245+ /** The updated cache for expression values based on the cache values from the last iteration
246+ *
247+ * Both `last` and `current` are required to make sure an expression is evaluated once in each iteration.
248+ */
246249 private var current : ExprValueCache = Map .empty
247250
248251 /** Global cached values for expressions
@@ -294,10 +297,36 @@ object Semantic:
294297 if current.contains(value, expr) then current.get(value, expr)
295298 else stable.get(value, expr)
296299
300+ /** Conditionally perform an operation
301+ *
302+ * If the operation returns true, the changes are commited. Otherwise, the changes are reverted.
303+ */
304+ def conditionally [T ](fn : => (Boolean , T )): T =
305+ val last2 = this .last
306+ val current2 = this .current
307+ val stable2 = this .stable
308+ val heap2 = this .heap
309+ val heapStable2 = this .heapStable
310+ val changed2 = this .changed
311+ val (commit, value) = fn
312+
313+ if commit then
314+ this .last = last2
315+ this .current = current2
316+ this .stable = stable2
317+ this .heap = heap2
318+ this .heapStable = heapStable2
319+ this .changed = changed2
320+
321+ value
322+
297323 /** Copy the value of `(value, expr)` from the last cache to the current cache
298- * (assuming it's `Hot` if it doesn't exist in the cache).
299324 *
300- * Then, runs `fun` and update the caches if the values change.
325+ * It assumes the value is `Hot` if it doesn't exist in the last cache.
326+ *
327+ * It update the current caches if the values change.
328+ *
329+ * The two caches are required because we want to make sure in a new iteration, an expression is evaluated once.
301330 */
302331 def assume (value : Value , expr : Tree , cacheResult : Boolean )(fun : => Value ): Contextual [Value ] =
303332 val assumeValue : Value =
@@ -312,7 +341,6 @@ object Semantic:
312341 val actual = fun
313342 if actual != assumeValue then
314343 this .changed = true
315- this .last = this .last.updatedNested(value, expr, actual)
316344 this .current = this .current.updatedNested(value, expr, actual)
317345 else
318346 // It's tempting to cache the value in stable, but it's unsound.
@@ -339,13 +367,13 @@ object Semantic:
339367 *
340368 * 1. Reset changed flag.
341369 *
342- * 2. Reset current cache (last cache already synced in `assume`).
343- *
344- * 3. Revert heap if instable.
370+ * 2. Use current cache as last cache and set current cache to be empty.
345371 *
372+ * 3. Revert heap to stable.
346373 */
347374 def prepareForNextIteration ()(using Context ) =
348375 this .changed = false
376+ this .last = this .current
349377 this .current = Map .empty
350378 this .heap = this .heapStable
351379
@@ -542,12 +570,15 @@ object Semantic:
542570 // We may reset the outers or params of a populated warm object.
543571 // This is the case if we need access the field of a warm object, which
544572 // requires population of parameters and outers; and later create an
545- // instance of the exact warm object, which requires initialization check.
573+ // instance of the exact warm object, whose initialization will reset
574+ // the outer and constructor parameters.
546575 //
547576 // See tests/init/neg/unsound1.scala
548- assert(! obj.hasField(field) || field.is(Flags .ParamAccessor ) && obj.field(field) == value, field.show + " already init, new = " + value + " , old = " + obj.field(field) + " , ref = " + ref)
577+ val changed = ! obj.hasField(field) || obj.field(field) != value
578+ def isParamUpdate = field.isOneOf(Flags .ParamAccessor | Flags .Param ) && obj.field(field) == value
579+ assert(! obj.hasField(field) || isParamUpdate, field.show + " already init, new = " + value + " , old = " + obj.field(field) + " , ref = " + ref)
549580 val obj2 = obj.copy(fields = obj.fields.updated(field, value))
550- cache.updateObject(ref, obj2)
581+ if changed then cache.updateObject(ref, obj2)
551582 }
552583
553584 /** Update the immediate outer of the given `klass` of the abstract object
0 commit comments