diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 31fd6296451..1450f440dee 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -1331,7 +1331,6 @@ class MyNode(ast.AST): 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 @@ def test_replace_accept_missing_field_with_default(self): 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 @@ def test_replace_reject_known_custom_instance_fields_commits(self): 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 diff --git a/crates/vm/src/stdlib/_ast/expression.rs b/crates/vm/src/stdlib/_ast/expression.rs index 39f42652cc7..10bdf526684 100644 --- a/crates/vm/src/stdlib/_ast/expression.rs +++ b/crates/vm/src/stdlib/_ast/expression.rs @@ -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); diff --git a/crates/vm/src/stdlib/_ast/statement.rs b/crates/vm/src/stdlib/_ast/statement.rs index ad3306fce50..bf8b0347695 100644 --- a/crates/vm/src/stdlib/_ast/statement.rs +++ b/crates/vm/src/stdlib/_ast/statement.rs @@ -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(); diff --git a/extra_tests/snippets/stdlib_ast.py b/extra_tests/snippets/stdlib_ast.py index 7b5c69df49e..2dd2276723c 100644 --- a/extra_tests/snippets/stdlib_ast.py +++ b/extra_tests/snippets/stdlib_ast.py @@ -1,4 +1,5 @@ import ast +import copy print(ast) @@ -39,6 +40,25 @@ def foo(): 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,