mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
* 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
111 lines
2.8 KiB
Python
111 lines
2.8 KiB
Python
import ast
|
|
import copy
|
|
|
|
print(ast)
|
|
|
|
source = """
|
|
def foo():
|
|
print('bar')
|
|
pass
|
|
"""
|
|
n = ast.parse(source)
|
|
print(n)
|
|
print(n.body)
|
|
print(n.body[0].name)
|
|
assert n.body[0].name == "foo"
|
|
foo = n.body[0]
|
|
assert foo.lineno == 2
|
|
print(foo.body)
|
|
assert len(foo.body) == 2
|
|
print(foo.body[0])
|
|
print(foo.body[0].value.func.id)
|
|
assert foo.body[0].value.func.id == "print"
|
|
assert foo.body[0].lineno == 3
|
|
assert foo.body[1].lineno == 4
|
|
|
|
n = ast.parse("3 < 4 > 5\n")
|
|
assert n.body[0].value.left.value == 3
|
|
assert "Lt" in str(n.body[0].value.ops[0])
|
|
assert "Gt" in str(n.body[0].value.ops[1])
|
|
assert n.body[0].value.comparators[0].value == 4
|
|
assert n.body[0].value.comparators[1].value == 5
|
|
|
|
|
|
n = ast.parse("from ... import a\n")
|
|
print(n)
|
|
i = n.body[0]
|
|
assert i.level == 3
|
|
assert i.module is None
|
|
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,
|
|
# matching CPython's behavior. Covers both Box<T> descents (UnaryOp, BinOp,
|
|
# Call, Attribute) and Vec<T> descents (List, Tuple).
|
|
import warnings
|
|
|
|
|
|
def _cyclic_cases():
|
|
# Box<Expr> descents
|
|
u = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0)
|
|
u.operand = u
|
|
yield "UnaryOp", u
|
|
|
|
b = ast.BinOp(
|
|
op=ast.Add(),
|
|
right=ast.Constant(value=0, lineno=0, col_offset=0),
|
|
lineno=0,
|
|
col_offset=0,
|
|
)
|
|
b.left = b
|
|
yield "BinOp", b
|
|
|
|
c = ast.Call(args=[], keywords=[], lineno=0, col_offset=0)
|
|
c.func = c
|
|
yield "Call", c
|
|
|
|
a = ast.Attribute(attr="x", ctx=ast.Load(), lineno=0, col_offset=0)
|
|
a.value = a
|
|
yield "Attribute", a
|
|
|
|
# Vec<Expr> descents
|
|
lst = ast.List(ctx=ast.Load(), lineno=0, col_offset=0)
|
|
lst.elts = [lst]
|
|
yield "List", lst
|
|
|
|
tup = ast.Tuple(ctx=ast.Load(), lineno=0, col_offset=0)
|
|
tup.elts = [tup]
|
|
yield "Tuple", tup
|
|
|
|
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("ignore")
|
|
for name, node in _cyclic_cases():
|
|
try:
|
|
compile(ast.Expression(node), "<cyclic>", "eval")
|
|
raise AssertionError(f"cyclic {name} should raise RecursionError")
|
|
except RecursionError:
|
|
pass # expected; matches CPython
|