From 06e4ac4e6538f13489e9d1358f2093f6a37be050 Mon Sep 17 00:00:00 2001 From: Stanislav Date: Fri, 16 Oct 2026 21:53:14 +0400 Subject: [PATCH 1/3] Table: do not mark the result as fully evaluated Table.get_result() gave its list ElementsProperties(elements_fully_evaluated=True). ListExpression then never evaluated the elements again, even when they depend on definitions made later: l = Table[x, {1}]; x = 1; l (* gave {x}; WMA: {1} *) l = Table[f[y], {1}]; f[y_] := 7; l (* gave {f[y]}; WMA: {7} *) The list now works out its properties from its elements, as any other list does. Co-Authored-By: Claude Opus 5.5 --- mathics/builtin/list/constructing.py | 12 ++++++------ test/builtin/list/test_constructing.py | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index bb6212e5fd..05d44f2b2f 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -566,12 +566,12 @@ class Table(IterationFunction): summary_text = "make a table of values of an expression" def get_result(self, elements, is_uniform=False) -> ListExpression: - return ListExpression( - *elements, - elements_properties=ElementsProperties( - elements_fully_evaluated=True, is_uniform=is_uniform - ), - ) + # The elements have just been evaluated, but that does not make them + # "fully evaluated": that would keep the list from ever being evaluated + # again, so that in + # l = Table[x, {1}]; x = 1; l + # l would stay {x}. Let ListExpression work out the properties. + return ListExpression(*elements) class Tuples(Builtin): diff --git a/test/builtin/list/test_constructing.py b/test/builtin/list/test_constructing.py index 22159b9699..b82901cad3 100644 --- a/test/builtin/list/test_constructing.py +++ b/test/builtin/list/test_constructing.py @@ -72,3 +72,29 @@ def test_range(str_expr, str_expected, failure_message): ) def test_table(str_expr, str_expected, failure_message): check_evaluation(str_expr, str_expected, failure_message) + + +@pytest.mark.parametrize( + ("str_expr", "str_expected", "failure_message"), + [ + (None, None, None), + ( + "ClearAll[l, x]; l = Table[x, {1}]; x = 1; l", + "{1}", + "A table sees a definition made after it.", + ), + ( + "ClearAll[l, f, y]; l = Table[f[y], {1}]; f[y_] := 7; l", + "{7}", + "A table sees a rule defined after it.", + ), + ( + "ClearAll[t, x]; t = Table[i + x, {i, 2}]; x = 1; t", + "{2, 3}", + None, + ), + ("ClearAll[l, t, f, x, y]", "Null", None), + ], +) +def test_table_is_evaluated_again(str_expr, str_expected, failure_message): + check_evaluation(str_expr, str_expected, failure_message) From 5d81f3aea86842561d8c7c9ebd51ddde5d160e43 Mon Sep 17 00:00:00 2001 From: Stanislav Date: Fri, 16 Oct 2026 22:41:19 +0400 Subject: [PATCH 2/3] A literal element is fully evaluated, whatever its own properties say Map over an empty list gives {} with pessimistic elements properties (elements_fully_evaluated=False), although {} is literal. A list holding such an element is itself literal, and _build_elements_properties() then failed on `assert self.elements_properties.elements_fully_evaluated`: {f /@ {}} (* AssertionError; WMA: {{}} *) The previous commit makes Table build its list this way too, so that Table[f /@ {}, {2}] and Strickland's TodaChecks.m crashed as well. _build_elements_properties() and convert_expression_elements() now take a literal element as fully evaluated. Co-Authored-By: Claude Opus 5.5 --- mathics/core/expression.py | 9 +++++++-- test/builtin/list/test_constructing.py | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 27143091b3..70c85ec1d4 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -436,9 +436,13 @@ def _build_elements_properties(self): element._build_elements_properties() # and now possibly adjust self.elements_properties.elements_fully_evaluted + # A literal element (e.g. {}, which Map over an empty list + # gives with pessimistic properties) is fully evaluated + # whatever its own properties say. if self.elements_properties.elements_fully_evaluated: self.elements_properties.elements_fully_evaluated = ( - element.elements_properties.elements_fully_evaluated + element.is_literal + or element.elements_properties.elements_fully_evaluated ) if element.is_literal: @@ -2112,7 +2116,8 @@ def convert_expression_elements( if elements_properties.elements_fully_evaluated: elements_properties.elements_fully_evaluated = ( - converted_elt.elements_properties.elements_fully_evaluated + converted_elt.is_literal + or converted_elt.elements_properties.elements_fully_evaluated ) if elements_properties.is_ordered and last_converted_elt is not None: diff --git a/test/builtin/list/test_constructing.py b/test/builtin/list/test_constructing.py index b82901cad3..b861fcff2f 100644 --- a/test/builtin/list/test_constructing.py +++ b/test/builtin/list/test_constructing.py @@ -93,6 +93,8 @@ def test_table(str_expr, str_expected, failure_message): "{2, 3}", None, ), + ("{f /@ {}}", "{{}}", "A list of literals with pessimistic properties."), + ("Table[f /@ {}, {2}]", "{{}, {}}", None), ("ClearAll[l, t, f, x, y]", "Null", None), ], ) From eb082a4213ca8902ad539d8431502c2b18cb3f9b Mon Sep 17 00:00:00 2001 From: Stanislav Date: Sat, 17 Oct 2026 09:54:37 +0400 Subject: [PATCH 3/3] Table: a table of literal elements is fully evaluated from creation As suggested in review: when all the elements Table produces are literal, like a list of numbers, the list is marked as fully evaluated when Table builds it. Other tables still get their properties from ListExpression. Co-Authored-By: Claude Opus 5.5 --- mathics/builtin/list/constructing.py | 8 +++++-- test/builtin/list/test_constructing.py | 29 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index 05d44f2b2f..c9b84c6c30 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -570,8 +570,12 @@ def get_result(self, elements, is_uniform=False) -> ListExpression: # "fully evaluated": that would keep the list from ever being evaluated # again, so that in # l = Table[x, {1}]; x = 1; l - # l would stay {x}. Let ListExpression work out the properties. - return ListExpression(*elements) + # l would stay {x}. Only a list of literal elements, like a list of + # numbers, is fully evaluated, and that is settled here, at creation. + result = ListExpression(*elements) + if result.is_literal: + result._build_elements_properties() + return result class Tuples(Builtin): diff --git a/test/builtin/list/test_constructing.py b/test/builtin/list/test_constructing.py index b861fcff2f..9201fe8cde 100644 --- a/test/builtin/list/test_constructing.py +++ b/test/builtin/list/test_constructing.py @@ -2,6 +2,7 @@ """ Unit tests for mathics.builtins.list.constructing """ + from test.helper import check_evaluation import pytest @@ -100,3 +101,31 @@ def test_table(str_expr, str_expected, failure_message): ) def test_table_is_evaluated_again(str_expr, str_expected, failure_message): check_evaluation(str_expr, str_expected, failure_message) + + +def test_table_fully_evaluated_at_creation(): + """A table of literal elements, like a list of numbers, is known to be + fully evaluated as soon as Table builds it; a table with other elements + is not.""" + from mathics.builtin.list.constructing import Table + from mathics.core.atoms import Integer1, Integer2 + from mathics.core.expression import Expression + from mathics.core.list import ListExpression + from mathics.core.symbols import Symbol + from mathics.core.systemsymbols import SymbolTimes + + numbers = Table.get_result(None, [Integer1, Integer2]) + assert numbers.elements_properties is not None + assert numbers.elements_properties.elements_fully_evaluated + + pairs = Table.get_result( + None, [ListExpression(Integer1, Integer2), ListExpression(Integer2, Integer1)] + ) + assert pairs.elements_properties.elements_fully_evaluated + + x = Symbol("Global`x") + symbolic = Table.get_result(None, [x, Expression(SymbolTimes, Integer2, x)]) + assert not ( + symbolic.elements_properties + and symbolic.elements_properties.elements_fully_evaluated + )