ast: fix identifier interning (#8361)

* Fix AST identifier interning

Ensure parsed name and function identifier fields use interned strings,
matching CPython behavior.

Assisted-by: Codex:gpt-5.6-sol

* Apply PEP 8 import ordering
This commit is contained in:
이광효 authored and GitHub committed 2026-07-25 19:58:05 +09:00
1 parent 64352495d7
commit 4ea3fd4617
4 files changed
+23 -5

No files matched your search

-3
View File
@@ -1331,7 +1331,6 @@ class CopyTests(unittest.TestCase):
self.assertEqual(repl.x, 0)
self.assertEqual(repl.y, y)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'x' is not 'x'
def test_replace_ignore_known_custom_instance_fields(self):
node = ast.parse('x').body[0].value
node.extra = extra = object() # add instance 'extra' field
@@ -1401,7 +1400,6 @@ class CopyTests(unittest.TestCase):
self.assertIs(node2.returns, None)
self.assertEqual(node2.decorator_list, [])
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "Name\.__replace__\ got\ an\ unexpected\ keyword\ argument\ 'extra'\." does not match "replace() does not support Name objects"
def test_replace_reject_known_custom_instance_fields_commits(self):
node = ast.parse('x').body[0].value
node.extra = extra = object() # add instance 'extra' field
@@ -1417,7 +1415,6 @@ class CopyTests(unittest.TestCase):
self.assertIs(node.ctx, context)
self.assertIs(node.extra, extra)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "Name\.__replace__\ got\ an\ unexpected\ keyword\ argument\ 'unknown'\." does not match "replace() does not support Name objects"
def test_replace_reject_unknown_instance_fields(self):
node = ast.parse('x').body[0].value
context = node.ctx
+2 -1
View File
@@ -1376,7 +1376,8 @@ impl Node for ast::ExprName {
.into_ref_with_type(vm, pyast::NodeExprName::static_type().to_owned())
.unwrap();
let dict = node.as_object().dict().unwrap();
dict.set_item("id", id.to_pyobject(vm), vm).unwrap();
dict.set_item("id", id.ast_to_object(vm, source_file), vm)
.unwrap();
dict.set_item("ctx", ctx.ast_to_object(vm, source_file), vm)
.unwrap();
node_add_location(&dict, range, vm, source_file);
+1 -1
View File
@@ -409,7 +409,7 @@ impl Node for ast::StmtFunctionDef {
let node = NodeAst.into_ref_with_type(vm, cls).unwrap();
let dict = node.as_object().dict().unwrap();
dict.set_item("name", vm.ctx.new_str(name.as_str()).to_pyobject(vm), vm)
dict.set_item("name", name.ast_to_object(vm, source_file), vm)
.unwrap();
dict.set_item("args", parameters.ast_to_object(vm, source_file), vm)
.unwrap();
+20
View File
@@ -1,4 +1,5 @@
import ast
import copy
print(ast)
@@ -39,6 +40,25 @@ assert i.names[0].name == "a"
assert i.names[0].asname is None
# Regression: parsed AST identifier fields are interned, matching CPython.
name_literal = "x"
name = ast.parse("x").body[0].value
assert name.id is name_literal
name.extra = object()
replacement = copy.replace(name)
assert replacement.id is name.id
assert replacement.ctx is name.ctx
assert not hasattr(replacement, "extra")
function_name = "f"
function = ast.parse("def f(): pass").body[0]
assert function.name is function_name
async_function = ast.parse("async def f(): pass").body[0]
assert async_function.name is function_name
# Regression test for issue #4862:
# A cyclic AST fed to compile() used to overflow the Rust stack and SIGSEGV.
# After the fix, the recursion guard in ast_from_object raises RecursionError,