diff --git a/Lib/test/test_genericclass.py b/Lib/test/test_genericclass.py index 498904dd9..e530b4639 100644 --- a/Lib/test/test_genericclass.py +++ b/Lib/test/test_genericclass.py @@ -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): diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 02a712979..9c4dcb043 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -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); } diff --git a/crates/vm/src/stdlib/ast.rs b/crates/vm/src/stdlib/ast.rs index cb7bfc289..bdf908112 100644 --- a/crates/vm/src/stdlib/ast.rs +++ b/crates/vm/src/stdlib/ast.rs @@ -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, &'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, diff --git a/crates/vm/src/stdlib/ast/expression.rs b/crates/vm/src/stdlib/ast/expression.rs index e60fe18b7..ebfa471c8 100644 --- a/crates/vm/src/stdlib/ast/expression.rs +++ b/crates/vm/src/stdlib/ast/expression.rs @@ -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) diff --git a/crates/vm/src/stdlib/ast/python.rs b/crates/vm/src/stdlib/ast/python.rs index e973be22e..5bea02c64 100644 --- a/crates/vm/src/stdlib/ast/python.rs +++ b/crates/vm/src/stdlib/ast/python.rs @@ -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)?; } diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 8bfbffcc6..ee9c26e33 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -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);