From bf4124293f1317e9d8ec81bcb7cbc1602f649c35 Mon Sep 17 00:00:00 2001 From: Stanislav Date: Fri, 16 Oct 2026 09:36:27 +0400 Subject: [PATCH] $IterationLimit: count the current chain of rewrites, not every lookup Symbol.evaluate incremented evaluation.iteration_count on each ownvalue rewrite and never decremented it, so the count was a total over the whole evaluation. Any long computation that merely looked up symbols with values 4096 times failed: x = {a}; Do[x, {5000}]; x (* $IterationLimit::itlim *) Give the step back once the lookup is done, so that the count is the length of the current chain x -> y -> ..., which is what $IterationLimit bounds. Endless chains such as yin := yang; yang := yin are still stopped. Fixes #1993. Co-Authored-By: Claude Opus 5.5 --- mathics/core/symbols.py | 9 +++++++-- test/builtin/test_evaluation.py | 27 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 75c7f1b51d..cb434f8556 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -465,9 +465,14 @@ def evaluate(self, evaluation): from mathics.core.systemsymbols import SymbolAborted return SymbolAborted + # iteration_count is the length of the current chain of + # rewrites (x -> y -> ...), not a total for the whole + # evaluation: each finished lookup gives its step back. evaluation.iteration_count += 1 - - return result.evaluate(evaluation) + try: + return result.evaluate(evaluation) + finally: + evaluation.iteration_count -= 1 return self def get_head(self) -> "Symbol": diff --git a/test/builtin/test_evaluation.py b/test/builtin/test_evaluation.py index 1d635991d8..e0a8c4169c 100644 --- a/test/builtin/test_evaluation.py +++ b/test/builtin/test_evaluation.py @@ -3,7 +3,6 @@ Unit tests from mathics.builtin.evaluation. """ - import sys from test.helper import check_evaluation_as_in_cli @@ -55,6 +54,32 @@ None, ), ("ClearAll[f];", None, None, None), + # $IterationLimit bounds one chain of rewrites, not the number of + # times symbols are looked up during the whole evaluation. + ( + "Block[{$IterationLimit = 20, itlimX = {itlimA}}, Do[itlimX, {30}]; itlimX]", + None, + "{itlimA}", + "Many separate lookups of a symbol do not add up", + ), + ( + 'Do[Evaluate[Symbol["c" <> ToString[k]]] = Symbol["c" <> ToString[k + 1]], {k, 0, 29}]; Block[{$IterationLimit = 40}, c0]', + None, + "c30", + "A chain of 30 rewrites is within the limit of 40", + ), + ( + "Block[{$IterationLimit = 20}, c0]", + ("Iteration limit of 20 exceeded.",), + "$Aborted", + "A chain of 30 rewrites exceeds the limit of 20", + ), + ( + 'ClearAll[c0, c30]; Do[ClearAll[Evaluate[Symbol["c" <> ToString[k]]]], {k, 1, 29}];', + None, + None, + None, + ), ( "Attributes[h] = Flat;h[items___] := Plus[items];h[1, Unevaluated[Sequence[Unevaluated[2], 3]], Sequence[4, Unevaluated[5]]]", None,