Fix AST field defaults and compile() type check

- Extract empty_arguments_object helper from expression.rs
- Fix LIST_FIELDS ambiguity: "args" and "body" have different
  ASDL types per node (e.g. Lambda.args is `arguments`, not
  `expr*`; Lambda.body is `expr`, not `stmt*`)
- Replace class name string comparison in compile() with
  fast_isinstance to accept AST subclasses
This commit is contained in:
Jeong, YunWon committed 2026-02-03 14:58:43 +09:00
1 parent 8e0174e181
commit 0bd3cb79e1
6 files changed
+86 -49

No files matched your search

-1
View File
@@ -228,7 +228,6 @@ class TestClassGetitem(unittest.TestCase):
with self.assertRaises(TypeError):
C_too_many[int]
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_class_getitem_errors_2(self):
class C:
def __class_getitem__(cls, item):
+1
View File
@@ -704,6 +704,7 @@ impl PyObject {
if let Some(class_getitem) =
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class_getitem__))?
&& !vm.is_none(&class_getitem)
{
return class_getitem.call((needle,), vm);
}
+36
View File
@@ -265,6 +265,42 @@ fn node_add_location(
.unwrap();
}
/// Return the expected AST mod type class for a compile() mode string.
pub(crate) fn mode_type_and_name(
ctx: &Context,
mode: &str,
) -> Option<(PyRef<PyType>, &'static str)> {
match mode {
"exec" => Some((pyast::NodeModModule::make_class(ctx), "Module")),
"eval" => Some((pyast::NodeModExpression::make_class(ctx), "Expression")),
"single" => Some((pyast::NodeModInteractive::make_class(ctx), "Interactive")),
"func_type" => Some((pyast::NodeModFunctionType::make_class(ctx), "FunctionType")),
_ => None,
}
}
/// Create an empty `arguments` AST node (no parameters).
fn empty_arguments_object(vm: &VirtualMachine) -> PyObjectRef {
let node = NodeAst
.into_ref_with_type(vm, pyast::NodeArguments::static_type().to_owned())
.unwrap();
let dict = node.as_object().dict().unwrap();
for list_field in [
"posonlyargs",
"args",
"kwonlyargs",
"kw_defaults",
"defaults",
] {
dict.set_item(list_field, vm.ctx.new_list(vec![]).into(), vm)
.unwrap();
}
for none_field in ["vararg", "kwarg"] {
dict.set_item(none_field, vm.ctx.none(), vm).unwrap();
}
node.into()
}
#[cfg(feature = "parser")]
pub(crate) fn parse(
vm: &VirtualMachine,
+1 -25
View File
@@ -330,31 +330,7 @@ impl Node for ast::ExprLambda {
// Lambda with no parameters should have an empty arguments object, not None
let args = match parameters {
Some(params) => params.ast_to_object(vm, source_file),
None => {
// Create an empty arguments object
let args_node = NodeAst
.into_ref_with_type(vm, pyast::NodeArguments::static_type().to_owned())
.unwrap();
let args_dict = args_node.as_object().dict().unwrap();
args_dict
.set_item("posonlyargs", vm.ctx.new_list(vec![]).into(), vm)
.unwrap();
args_dict
.set_item("args", vm.ctx.new_list(vec![]).into(), vm)
.unwrap();
args_dict.set_item("vararg", vm.ctx.none(), vm).unwrap();
args_dict
.set_item("kwonlyargs", vm.ctx.new_list(vec![]).into(), vm)
.unwrap();
args_dict
.set_item("kw_defaults", vm.ctx.new_list(vec![]).into(), vm)
.unwrap();
args_dict.set_item("kwarg", vm.ctx.none(), vm).unwrap();
args_dict
.set_item("defaults", vm.ctx.new_list(vec![]).into(), vm)
.unwrap();
args_node.into()
}
None => empty_arguments_object(vm),
};
dict.set_item("args", args, vm).unwrap();
dict.set_item("body", body.ast_to_object(vm, source_file), vm)
+24 -7
View File
@@ -95,12 +95,11 @@ pub(crate) mod _ast {
.is_some();
if has_field_types {
// ASDL list fields (type*) default to empty list,
// optional fields (type?) default to None.
// optional/required fields default to None.
// Fields that are always list-typed regardless of node class.
const LIST_FIELDS: &[&str] = &[
"args",
"argtypes",
"bases",
"body",
"cases",
"comparators",
"decorator_list",
@@ -113,11 +112,13 @@ pub(crate) mod _ast {
"items",
"keys",
"kw_defaults",
"kwd_attrs",
"kwd_patterns",
"keywords",
"kwonlyargs",
"names",
"orelse",
"ops",
"patterns",
"posonlyargs",
"targets",
"type_ignores",
@@ -125,19 +126,35 @@ pub(crate) mod _ast {
"values",
];
let class_name = zelf.class().name().to_string();
for field in &fields {
if !set_fields.contains(field.as_str()) {
let default: PyObjectRef = if LIST_FIELDS.contains(&field.as_str()) {
let field_name = field.as_str();
// Some field names have different ASDL types depending on the node.
// For example, "args" is `expr*` in Call but `arguments` in Lambda.
// "body" and "orelse" are `stmt*` in most nodes but `expr` in IfExp.
let is_list_field = if field_name == "args" {
class_name == "Call" || class_name == "arguments"
} else if field_name == "body" || field_name == "orelse" {
!matches!(
class_name.as_str(),
"Lambda" | "Expression" | "IfExp"
)
} else {
LIST_FIELDS.contains(&field_name)
};
let default: PyObjectRef = if is_list_field {
vm.ctx.new_list(vec![]).into()
} else {
vm.ctx.none()
};
zelf.set_attr(vm.ctx.intern_str(field.as_str()), default, vm)?;
zelf.set_attr(vm.ctx.intern_str(field_name), default, vm)?;
}
}
// Special defaults that are not None or empty list
let class_name = &*zelf.class().name();
if class_name == "ImportFrom" && !set_fields.contains("level") {
zelf.set_attr("level", vm.ctx.new_int(0), vm)?;
}
+24 -16
View File
@@ -142,24 +142,32 @@ mod builtins {
.fast_isinstance(&ast::NodeAst::make_class(&vm.ctx))
{
use num_traits::Zero;
let flags = args.flags.map_or(Ok(0), |v| v.try_to_primitive(vm))?;
let flags: i32 = args.flags.map_or(Ok(0), |v| v.try_to_primitive(vm))?;
let is_ast_only = !(flags & ast::PY_COMPILE_FLAG_AST_ONLY).is_zero();
// func_type mode requires PyCF_ONLY_AST
if mode_str == "func_type" && !is_ast_only {
return Err(vm.new_value_error(
"compile() mode 'func_type' requires flag PyCF_ONLY_AST".to_owned(),
));
}
// compile(ast_node, ..., PyCF_ONLY_AST) returns the AST after validation
if !(flags & ast::PY_COMPILE_FLAG_AST_ONLY).is_zero() {
let expected_type = match mode_str {
"exec" => "Module",
"eval" => "Expression",
"single" => "Interactive",
"func_type" => "FunctionType",
_ => {
return Err(vm.new_value_error(format!(
"compile() mode must be 'exec', 'eval', 'single' or 'func_type', got '{mode_str}'"
)));
}
};
let cls_name = args.source.class().name().to_string();
if cls_name != expected_type {
if is_ast_only {
let (expected_type, expected_name) = ast::mode_type_and_name(
&vm.ctx, mode_str,
)
.ok_or_else(|| {
vm.new_value_error(
"compile() mode must be 'exec', 'eval', 'single' or 'func_type'"
.to_owned(),
)
})?;
if !args.source.fast_isinstance(&expected_type) {
return Err(vm.new_type_error(format!(
"expected {expected_type} node, got {cls_name}"
"expected {} node, got {}",
expected_name,
args.source.class().name()
)));
}
return Ok(args.source);