https://github.com/python/cpython/commit/15232a0819a2f7e0f448f28f2e6081912d10e7cb
commit: 15232a0819a2f7e0f448f28f2e6081912d10e7cb
branch: main
author: Bénédikt Tran <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2024-07-02T16:23:17+05:30
summary:
gh-121210: handle nodes with missing attributes/fields in `ast.compare`
(#121211)
files:
A Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst
M Lib/ast.py
M Lib/test/test_ast.py
diff --git a/Lib/ast.py b/Lib/ast.py
index fb4d21b87d8bd0..a954d4a97d3c22 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -422,6 +422,8 @@ def compare(
might differ in whitespace or similar details.
"""
+ sentinel = object() # handle the possibility of a missing attribute/field
+
def _compare(a, b):
# Compare two fields on an AST object, which may themselves be
# AST objects, lists of AST objects, or primitive ASDL types
@@ -449,8 +451,14 @@ def _compare_fields(a, b):
if a._fields != b._fields:
return False
for field in a._fields:
- a_field = getattr(a, field)
- b_field = getattr(b, field)
+ a_field = getattr(a, field, sentinel)
+ b_field = getattr(b, field, sentinel)
+ if a_field is sentinel and b_field is sentinel:
+ # both nodes are missing a field at runtime
+ continue
+ if a_field is sentinel or b_field is sentinel:
+ # one of the node is missing a field
+ return False
if not _compare(a_field, b_field):
return False
else:
@@ -461,8 +469,11 @@ def _compare_attributes(a, b):
return False
# Attributes are always ints.
for attr in a._attributes:
- a_attr = getattr(a, attr)
- b_attr = getattr(b, attr)
+ a_attr = getattr(a, attr, sentinel)
+ b_attr = getattr(b, attr, sentinel)
+ if a_attr is sentinel and b_attr is sentinel:
+ # both nodes are missing an attribute at runtime
+ continue
if a_attr != b_attr:
return False
else:
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index 5d71d524516df2..fbd19620311159 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -948,6 +948,15 @@ def test_compare_fieldless(self):
self.assertTrue(ast.compare(ast.Add(), ast.Add()))
self.assertFalse(ast.compare(ast.Sub(), ast.Add()))
+ # test that missing runtime fields is handled in ast.compare()
+ a1, a2 = ast.Name('a'), ast.Name('a')
+ self.assertTrue(ast.compare(a1, a2))
+ self.assertTrue(ast.compare(a1, a2))
+ del a1.id
+ self.assertFalse(ast.compare(a1, a2))
+ del a2.id
+ self.assertTrue(ast.compare(a1, a2))
+
def test_compare_modes(self):
for mode, sources in (
("exec", exec_tests),
@@ -970,6 +979,16 @@ def parse(a, b):
self.assertTrue(ast.compare(a, b, compare_attributes=False))
self.assertFalse(ast.compare(a, b, compare_attributes=True))
+ def test_compare_attributes_option_missing_attribute(self):
+ # test that missing runtime attributes is handled in ast.compare()
+ a1, a2 = ast.Name('a', lineno=1), ast.Name('a', lineno=1)
+ self.assertTrue(ast.compare(a1, a2))
+ self.assertTrue(ast.compare(a1, a2, compare_attributes=True))
+ del a1.lineno
+ self.assertFalse(ast.compare(a1, a2, compare_attributes=True))
+ del a2.lineno
+ self.assertTrue(ast.compare(a1, a2, compare_attributes=True))
+
def test_positional_only_feature_version(self):
ast.parse('def foo(x, /): ...', feature_version=(3, 8))
ast.parse('def bar(x=1, /): ...', feature_version=(3, 8))
diff --git
a/Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst
b/Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst
new file mode 100644
index 00000000000000..55d5b221bf0765
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst
@@ -0,0 +1,2 @@
+Handle AST nodes with missing runtime fields or attributes in
+:func:`ast.compare`. Patch by Bénédikt Tran.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]