Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork33.4k
gh-121141: add support forcopy.replace to AST nodes#121162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Merged
Uh oh!
There was an error while loading.Please reload this page.
Merged
Changes from1 commit
Commits
Show all changes
25 commits Select commitHold shift + click to select a range
2c1e3c4 fix `ast.compare` to handle missing fields at runtime
picnixz28c2c6e add support for `copy.replace` on AST nodes
picnixz262608a add tests
picnixz70b06f5 blurb
picnixz6d45361 update What's New
picnixz8bdc824 update comments
picnixz12b3c07 simplify implementation
picnixzdff189d update tests
picnixz692ab55 fixup checks
picnixzc22ba93 update tests
picnixz93b97da simplify implementation
picnixz1085650 update template
picnixza73d181 regenerate objects
picnixz829d091 ensure that the node attributes are copied
picnixz08385f6 update tests
picnixz0fe8951 update tests
picnixz00c1e36 add strict check for keyword arguments
picnixz59ea13d update tests
picnixz3041f7d add strict check for keyword arguments
picnixz40c3749 simplify implementation
picnixz7afcc05 add comments
picnixz51a1068 fixup! whitespace
picnixz5f4888e Merge branch 'main' into ast-copy-replace
picnixzc04882a update import comments
picnixzbddcb97 address Jelle's comment
picnixzFile filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
add tests
- Loading branch information
Uh oh!
There was an error while loading.Please reload this page.
commit262608aff5c9852586ce43f7203e261a0bbc97fd
There are no files selected for viewing
129 changes: 129 additions & 0 deletionsLib/test/test_ast.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1130,6 +1130,23 @@ def test_none_checks(self) -> None: | ||
| class CopyTests(unittest.TestCase): | ||
| """Test copying and pickling AST nodes.""" | ||
| @staticmethod | ||
| def iter_ast_classes(): | ||
| """Iterate over the subclasses of ast.AST recursively. | ||
| This excludes the special class ast.Index since its constructor | ||
| returns an integer. | ||
| """ | ||
| def do(cls): | ||
| if cls is ast.Index: | ||
| return | ||
| yield cls | ||
| for sub in cls.__subclasses__(): | ||
| yield from do(sub) | ||
| yield from do(ast.AST) | ||
| def test_pickling(self): | ||
| import pickle | ||
| @@ -1199,6 +1216,118 @@ def test_copy_with_parents(self): | ||
| )): | ||
| self.assertEqual(to_tuple(child.parent), to_tuple(node)) | ||
| def test_replace_interface(self): | ||
| for klass in self.iter_ast_classes(): | ||
| with self.subTest(klass=klass): | ||
| self.assertTrue(hasattr(klass, '__replace__')) | ||
| fields = set(klass._fields) | ||
| with self.subTest(klass=klass, fields=fields): | ||
| # check shallow copy | ||
| node = klass(**dict.fromkeys(fields)) | ||
| # positional not allowed | ||
| self.assertRaises(TypeError, copy.replace, node, 1) | ||
| self.assertRaises(TypeError, node.__replace__, 1) | ||
| def test_replace_native(self): | ||
| for klass in self.iter_ast_classes(): | ||
| fields = set(klass._fields) | ||
picnixz marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| with self.subTest(klass=klass, fields=fields): | ||
| # use of object() to ensure that '==' and 'is' | ||
| # behave similarly in ast.compare(node, repl) | ||
| old_value, new_value = object(), object() | ||
| # check shallow copy | ||
| node = klass(**dict.fromkeys(fields, old_value)) | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| repl = copy.replace(node) | ||
| self.assertTrue(ast.compare(node, repl, compare_attributes=True)) | ||
| for field in fields: | ||
| node = klass(**dict.fromkeys(fields, old_value)) | ||
| # only change a single field | ||
| repl = copy.replace(node, **{field: new_value}) | ||
| for f in fields: | ||
| self.assertIs(getattr(node, f), old_value) | ||
| if f != field: | ||
| self.assertIs(getattr(repl, f), old_value) | ||
| else: | ||
| self.assertIs(getattr(repl, f), new_value) | ||
| self.assertFalse(ast.compare(node, repl, compare_attributes=True)) | ||
| def test_replace_accept_known_native_fields(self): | ||
| nid, ctx = object(), object() | ||
| node = ast.Name(id=nid, ctx=ctx) | ||
| self.assertIs(node.id, nid) | ||
| self.assertIs(node.ctx, ctx) | ||
| new_nid = object() | ||
| repl = copy.replace(node, id=new_nid) | ||
| # assert independence | ||
| self.assertIs(node.id, nid) | ||
| self.assertIs(node.ctx, ctx) | ||
| # assert changes | ||
| self.assertIs(repl.id, new_nid) | ||
| self.assertIs(repl.ctx, node.ctx) # no changes | ||
| def test_replace_accept_known_native_attributes(self): | ||
| node = ast.parse('x').body[0].value | ||
| self.assertEqual(node.id, 'x') | ||
| self.assertEqual(node.lineno, 1) | ||
| # constructor allows any type so replace() should do the same | ||
| lineno = object() | ||
| repl = copy.replace(node, lineno=lineno) | ||
| # assert independence | ||
| self.assertEqual(node.lineno, 1) | ||
| # check changes | ||
| self.assertEqual(repl.id, node.id) | ||
| self.assertEqual(repl.ctx, node.ctx) | ||
| self.assertEqual(repl.lineno, lineno) | ||
| _, _, state = node.__reduce__() | ||
| self.assertEqual(state['id'], 'x') | ||
| self.assertEqual(state['ctx'], node.ctx) | ||
| self.assertEqual(state['lineno'], 1) | ||
| _, _, state = repl.__reduce__() | ||
| self.assertEqual(state['id'], 'x') | ||
| self.assertEqual(state['ctx'], node.ctx) | ||
| self.assertEqual(state['lineno'], lineno) | ||
| def test_replace_accept_known_custom_fields(self): | ||
| nid, ctx = object(), object() | ||
| node = ast.Name(id=nid, ctx=ctx) | ||
| node.extra = old_extra = object() # add the 'extra' field | ||
| self.assertIs(node.extra, old_extra) | ||
| # assert shallow copy of extra fields as well | ||
| repl = copy.replace(node) | ||
| self.assertIs(node.extra, old_extra) | ||
| self.assertIs(repl.extra, old_extra) | ||
| new_extra = object() | ||
| repl = copy.replace(node, extra=new_extra) | ||
| self.assertIs(node.id, nid) | ||
| self.assertIs(node.ctx, ctx) | ||
| self.assertIs(node.extra, old_extra) | ||
| self.assertIs(repl.id, nid) | ||
| self.assertIs(repl.ctx, ctx) | ||
| self.assertIs(repl.extra, new_extra) | ||
| def test_replace_reject_unknown_custom_fields(self): | ||
| nid, ctx = object(), object() | ||
| node = ast.Name(id=nid, ctx=ctx) | ||
| with self.assertRaisesRegex(TypeError, "got an unexpected field name: 'extra'"): | ||
| # cannot replace a non-existing field | ||
| copy.replace(node, extra=1) | ||
| # check that we did not have a side-effect | ||
| self.assertIs(node.id, nid) | ||
| self.assertIs(node.ctx, ctx) | ||
| self.assertRaises(AttributeError, getattr, node, 'extra') | ||
| class ASTHelpers_Test(unittest.TestCase): | ||
| maxDiff = None | ||
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.