mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
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:
1 parent
8e0174e181
commit
0bd3cb79e1
6 files changed
+86
-49
No files matched your search
Vendored
-1
@@ -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):
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user