]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-121210: handle nodes with missing attributes/fields in `ast.compare` (#121211)
authorBénédikt Tran <10796600+picnixz@users.noreply.github.com>
Tue, 2 Jul 2024 10:53:17 +0000 (12:53 +0200)
committerGitHub <noreply@github.com>
Tue, 2 Jul 2024 10:53:17 +0000 (16:23 +0530)
Lib/ast.py
Lib/test/test_ast.py
Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst [new file with mode: 0644]

index fb4d21b87d8bd002eb24480d010bc5d1563d0df7..a954d4a97d3c22c2ec76b3e1893021f633920d33 100644 (file)
@@ -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(
         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(
             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:
index 5d71d524516df2becdc32e6083920cf73ca1ffb4..fbd196203111595192dcf4224c0ea7751bc18f6e 100644 (file)
@@ -948,6 +948,15 @@ class AST_Tests(unittest.TestCase):
         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 @@ class AST_Tests(unittest.TestCase):
         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 (file)
index 0000000..55d5b22
--- /dev/null
@@ -0,0 +1,2 @@
+Handle AST nodes with missing runtime fields or attributes in
+:func:`ast.compare`. Patch by Bénédikt Tran.