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]

Reply via email to