Skip to content

Commit f69bb45

Browse files
authored
Improving tag collection in tag assignment (#1459)
This PR should fix #1198.
1 parent 7e61621 commit f69bb45

File tree

2 files changed

+107
-25
lines changed

2 files changed

+107
-25
lines changed

mathics/eval/assignments/assignment.py

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,9 +1302,44 @@ def find_tag_and_check(
13021302
return tag
13031303

13041304

1305+
def get_lookup_reference_name(expr: BaseElement) -> str:
1306+
"""
1307+
Find the lookup name of the reference expression associated to
1308+
`expr`, or None if there is no such a reference.
1309+
1310+
In general, the lookup reference name coincides with the lookup_name
1311+
of the expression. However, there are some exceptions:
1312+
1313+
* Expressions with heads `HoldPattern`, Condition`, or `PatternTest`
1314+
are not considered *reference* expressions. The reference expression
1315+
is the reference expression of its first element.
1316+
* (named) `Pattern` expressions takes its lookup_reference_name from the
1317+
pattern their hold.
1318+
* `Verbatim` expressions pick the lookup_reference_name from
1319+
the lookup_name of the expression they hold.
1320+
* Blanks pick the lookup_reference_name from the pattern head
1321+
(its unique element if they has one). If the Blank expression does not
1322+
have elements (generic blank) then there is no lookup_reference_name,
1323+
and returns an empty string.
1324+
"""
1325+
expr = get_reference_expression(expr)
1326+
if expr.has_form("System`Pattern", 2):
1327+
return get_lookup_reference_name(expr.elements[1])
1328+
if expr.has_form("System`Verbatim", 1):
1329+
# For Verbatim pick the lookup name directly from the expression.
1330+
return expr.elements[0].get_lookup_name()
1331+
if expr.has_form(
1332+
("System`Blank", "System`BlankSequence", "System`BlankNullSequence"), None
1333+
):
1334+
if len(expr.elements) == 1:
1335+
return get_lookup_reference_name(expr.elements[0])
1336+
return ""
1337+
return expr.get_lookup_name()
1338+
1339+
13051340
def get_reference_expression(lhs: BaseElement) -> BaseElement:
13061341
"""
1307-
Strip `Condition`, `PatternTest` and `HoldPattern` from an expression
1342+
Strip `Condition`, `PatternTest` and `HoldPattern` from an expression.
13081343
"""
13091344
strip_headers = (
13101345
SymbolHoldPattern,
@@ -1379,18 +1414,8 @@ def process_tags_and_upset_allow_custom(
13791414
name = lhs.get_head_name()
13801415
lhs_reference_expr = get_reference_expression(lhs)
13811416

1382-
def get_lookup_name(expr):
1383-
expr = get_reference_expression(expr)
1384-
if expr.has_form("System`Pattern", 2):
1385-
return expr.elements[1].get_lookup_name()
1386-
if expr.has_form(
1387-
("System`Blank", "System`BlankSequence", "System`BlankNullSequence"), 1
1388-
):
1389-
return expr.elements[0].get_lookup_name()
1390-
return expr.get_lookup_name()
1391-
13921417
if upset:
1393-
tags = []
1418+
tags_set = set()
13941419
if isinstance(lhs_reference_expr, Atom):
13951420
symbol_name = self.get_name()
13961421
evaluation.message(
@@ -1401,20 +1426,33 @@ def get_lookup_name(expr):
14011426
)
14021427
raise AssignmentException(lhs, None)
14031428
for element in lhs_reference_expr.get_elements():
1404-
name = get_lookup_name(element)
1405-
tags.append(name)
1406-
return tags, lhs_reference_expr
1429+
# elements of the expression can also be wrapped in `HoldPattern`
1430+
# or `Condition`. Tag candidates are obtained by stripping out
1431+
# these wrappers.
1432+
# Still, if the element is a `Blank*`, the reference is
1433+
# set to its argument. If it does not have arguments (or have many)
1434+
# skip it.
1435+
name = get_lookup_reference_name(element)
1436+
if name is not None:
1437+
tags_set.add(name)
1438+
return list(tags_set), lhs_reference_expr
14071439

14081440
if tags is None:
1409-
name = get_lookup_name(lhs_reference_expr)
1441+
name = get_lookup_reference_name(lhs_reference_expr)
14101442
if not name:
14111443
evaluation.message(self.get_name(), "setraw", lhs_reference_expr)
14121444
raise AssignmentException(lhs, None)
14131445
tags = [name]
14141446
else:
1415-
allowed_names = [get_lookup_name(lhs_reference_expr)]
1447+
allowed_names = set()
1448+
name = get_lookup_reference_name(lhs_reference_expr)
1449+
if name:
1450+
allowed_names.add(name)
1451+
14161452
for element in lhs_reference_expr.get_elements():
1417-
allowed_names.append(get_lookup_name(element))
1453+
name = get_lookup_reference_name(element)
1454+
if name:
1455+
allowed_names.add(name)
14181456
for name in tags:
14191457
if name not in allowed_names:
14201458
evaluation.message(self.get_name(), "tagnfd", Symbol(name))
@@ -1463,19 +1501,34 @@ def process_tags_and_upset_dont_allow_custom(
14631501
the list of allowed tags.
14641502
14651503
"""
1504+
1505+
def get_lookup_name(expr):
1506+
expr = get_reference_expression(expr)
1507+
if expr.has_form("System`Pattern", 2):
1508+
return get_lookup_name(expr.elements[1])
1509+
if expr.has_form(
1510+
("System`Blank", "System`BlankSequence", "System`BlankNullSequence"), None
1511+
):
1512+
if len(expr.elements) == 1:
1513+
return get_lookup_name(expr.elements[0])
1514+
return None
1515+
return expr.get_lookup_name()
1516+
14661517
if isinstance(lhs_reference, Expression):
14671518
lhs_reference = lhs_reference.evaluate_elements(evaluation)
14681519
name = lhs.get_head_name()
14691520
if upset:
1470-
tags = [lhs_reference.get_lookup_name()]
1521+
name = get_lookup_name(lhs_reference)
1522+
tags = [name] if name is not None else None
14711523
elif tags is None:
1472-
name = lhs_reference.get_lookup_name()
1524+
name = get_lookup_name(lhs_reference)
14731525
if not name:
14741526
evaluation.message(self.get_name(), "setraw", lhs_reference)
14751527
raise AssignmentException(lhs, None)
14761528
tags = [name]
14771529
else:
1478-
allowed_names = [lhs_reference.get_lookup_name()]
1530+
name = get_lookup_name(lhs_reference)
1531+
allowed_names = [name] if name else []
14791532
for name in tags:
14801533
if name not in allowed_names:
14811534
evaluation.message(self.get_name(), "tagnfd", Symbol(name))

test/builtin/assignments/test_assignment.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -710,23 +710,52 @@ def test_assignment(expr, expect, fail_msg, expected_msgs):
710710

711711
# Regression check of some assignment issues encountered.
712712
@pytest.mark.parametrize(
713-
["expr", "expect", "fail_msg", "hold_expected"],
713+
["expr", "expect", "fail_msg", "hold_expected", "messages"],
714714
[
715715
(
716716
None,
717-
"Issue #1425 - Erroneous Protected message seen in SetDelayed loading Rubi.",
718717
None,
718+
"Issue #1425 - Erroneous Protected message seen in SetDelayed loading Rubi.",
719719
False,
720+
[],
720721
),
721722
(
722723
"ClearAll[A,x]; f[A_, x_] := x /; x == 2; DownValues[f] // FullForm",
723724
"{RuleDelayed[HoldPattern[f[Pattern[A, Blank[]], Pattern[x, Blank[]]]], Condition[x, Equal[x, 2]]]}",
724725
"Issue #1209 - Another problem seen in loading Rubi.",
725726
True,
727+
[],
728+
),
729+
(
730+
"ClearAll[F, Q];(F[x_] := s_) ^:= Q[x, s];F[1]:=2",
731+
"Q[1,2]",
732+
"Issue 1198 - Blanks are not tags.",
733+
False,
734+
[],
735+
),
736+
(
737+
"ClearAll[F, Q];F[_Q,_]^:=1;{DownValues[F],UpValues[Q]}",
738+
"{{}, {HoldPattern[F[_Q, _]]:>1}}",
739+
"Issue 1198 - Blanks are not tags.",
740+
False,
741+
[],
742+
),
743+
(
744+
"ClearAll[F, Q];F[Verbatim[_Q],Verbatim[_]]^:=1;{DownValues[F],UpValues[Q]}",
745+
"{{}, {}}",
746+
"Issue 1198 - Blanks are not tags.",
747+
False,
748+
["Tag Blank in F[Verbatim[_Q], Verbatim[_]] is Protected."],
726749
),
727750
],
728751
)
729-
def test_regression_of_assignment_issues(expr, expect, fail_msg, hold_expected):
752+
def test_regression_of_assignment_issues(
753+
expr, expect, fail_msg, hold_expected, messages
754+
):
730755
check_evaluation(
731-
expr, expect, failure_message=fail_msg, hold_expected=hold_expected
756+
expr,
757+
expect,
758+
failure_message=fail_msg,
759+
hold_expected=hold_expected,
760+
expected_messages=messages,
732761
)

0 commit comments

Comments
 (0)