diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index c4b28fb010..10319e36fa 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -706,6 +706,8 @@ class S(str): with assertNumDeprecated(): self.assertNotIsInstance(Constant(S("42")), Num) + # TODO: RUSTPYTHON; will be removed in Python 3.14 + @unittest.expectedFailure def test_constant_subclasses_deprecated(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore", "", DeprecationWarning) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 901f596cc3..79c050bae6 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -952,8 +952,6 @@ def __aiter__(self): self.validate_abstract_methods(AsyncIterable, '__aiter__') self.validate_isinstance(AsyncIterable, '__aiter__') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_AsyncIterator(self): class AI: def __aiter__(self): @@ -1152,8 +1150,6 @@ class NonCol(ColImpl): self.assertFalse(issubclass(NonCol, Collection)) self.assertFalse(isinstance(NonCol(), Collection)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_Iterator(self): non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()] for x in non_samples: @@ -1850,8 +1846,6 @@ def test_Set_hash_matches_frozenset(self): fs = frozenset(s) self.assertEqual(hash(fs), Set._hash(fs), msg=s) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_Mapping(self): for sample in [dict]: self.assertIsInstance(sample(), Mapping) @@ -1868,8 +1862,6 @@ def __iter__(self): self.validate_comparison(MyMapping()) self.assertRaises(TypeError, reversed, MyMapping()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_MutableMapping(self): for sample in [dict]: self.assertIsInstance(sample(), MutableMapping) @@ -1904,8 +1896,6 @@ def test_MutableMapping_subclass(self): mymap['blue'] = 7 # Shouldn't affect 'z' self.assertEqual(z, {('orange', 3), ('red', 5)}) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_Sequence(self): for sample in [tuple, list, bytes, str]: self.assertIsInstance(sample(), Sequence) @@ -1988,8 +1978,6 @@ def test_Buffer(self): self.assertFalse(issubclass(sample, Buffer)) self.validate_abstract_methods(Buffer, '__buffer__') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_MutableSequence(self): for sample in [tuple, str, bytes]: self.assertNotIsInstance(sample(), MutableSequence) diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index 9d25b4b4d2..ffe4dc4f35 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -315,8 +315,6 @@ def setUp(self): self.eg = create_simple_eg() self.eg_template = [ValueError(1), TypeError(int), ValueError(2)] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basics_subgroup_split__bad_arg_type(self): class C: pass diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 5241038a20..1e84d289ec 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2948,8 +2948,6 @@ class E(C, BP): pass self.assertNotIsInstance(D(), E) self.assertNotIsInstance(E(), D) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_no_instantiation(self): class P(Protocol): pass @@ -4500,8 +4498,6 @@ def __init__(self, arg): self.assertEqual(c.from_a, 'foo') self.assertEqual(c.from_c, 'foo') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_new_no_args(self): class A(Generic[T]): diff --git a/vm/src/builtins/object.rs b/vm/src/builtins/object.rs index fc39e2fb08..ee32e35475 100644 --- a/vm/src/builtins/object.rs +++ b/vm/src/builtins/object.rs @@ -31,7 +31,42 @@ impl PyPayload for PyBaseObject { impl Constructor for PyBaseObject { type Args = FuncArgs; - fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + // = object_new + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + if !args.args.is_empty() || !args.kwargs.is_empty() { + // Check if type's __new__ != object.__new__ + let tp_new = cls.get_attr(identifier!(vm, __new__)); + let object_new = vm.ctx.types.object_type.get_attr(identifier!(vm, __new__)); + + if let (Some(tp_new), Some(object_new)) = (tp_new, object_new) { + if !tp_new.is(&object_new) { + // Type has its own __new__, so object.__new__ is being called + // with excess args. This is the first error case in CPython + return Err(vm.new_type_error( + "object.__new__() takes exactly one argument (the type to instantiate)" + .to_owned(), + )); + } + + // If we reach here, tp_new == object_new + // Now check if type's __init__ == object.__init__ + let tp_init = cls.get_attr(identifier!(vm, __init__)); + let object_init = vm.ctx.types.object_type.get_attr(identifier!(vm, __init__)); + + if let (Some(tp_init), Some(object_init)) = (tp_init, object_init) { + if tp_init.is(&object_init) { + // Both __new__ and __init__ are object's versions, + // so the type accepts no arguments + return Err( + vm.new_type_error(format!("{}() takes no arguments", cls.name())) + ); + } + } + // If tp_init != object_init, then the type has custom __init__ + // which might accept arguments, so we allow it + } + } + // more or less __new__ operator let dict = if cls.is(vm.ctx.types.object_type) { None diff --git a/vm/src/stdlib/ast/python.rs b/vm/src/stdlib/ast/python.rs index 0392f04f82..2a4f7cf157 100644 --- a/vm/src/stdlib/ast/python.rs +++ b/vm/src/stdlib/ast/python.rs @@ -4,15 +4,16 @@ use super::{PY_CF_OPTIMIZED_AST, PY_CF_TYPE_COMMENTS, PY_COMPILE_FLAG_AST_ONLY}; pub(crate) mod _ast { use crate::{ AsObject, Context, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyStrRef, PyTupleRef}, + builtins::{PyStrRef, PyTupleRef, PyTypeRef}, function::FuncArgs, + types::Constructor, }; #[pyattr] #[pyclass(module = "_ast", name = "AST")] #[derive(Debug, PyPayload)] pub(crate) struct NodeAst; - #[pyclass(flags(BASETYPE, HAS_DICT))] + #[pyclass(with(Constructor), flags(BASETYPE, HAS_DICT))] impl NodeAst { #[pyslot] #[pymethod] @@ -52,6 +53,34 @@ pub(crate) mod _ast { } } + impl Constructor for NodeAst { + type Args = FuncArgs; + + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // AST nodes accept extra arguments (unlike object.__new__) + // This matches CPython's behavior where AST has its own tp_new + let dict = if cls + .slots + .flags + .contains(crate::types::PyTypeFlags::HAS_DICT) + { + Some(vm.ctx.new_dict()) + } else { + None + }; + let zelf = vm.ctx.new_base_object(cls, dict); + + // Initialize the instance with the provided arguments + NodeAst::__init__(zelf.clone(), args, vm)?; + + Ok(zelf) + } + + fn py_new(_cls: PyTypeRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { + unreachable!("slow_new is implemented"); + } + } + #[pyattr(name = "PyCF_ONLY_AST")] use super::PY_COMPILE_FLAG_AST_ONLY; diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 77feee44c0..2491a5ce29 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -30,7 +30,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { #[pymodule(name = "_typing")] pub(crate) mod decl { use crate::{ - PyObjectRef, PyPayload, PyResult, VirtualMachine, + Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr}, function::{FuncArgs, IntoFuncArgs}, types::{Constructor, Representable}, @@ -88,7 +88,7 @@ pub(crate) mod decl { impl Representable for NoDefault { #[inline(always)] - fn repr_str(_zelf: &crate::Py, _vm: &VirtualMachine) -> PyResult { + fn repr_str(_zelf: &Py, _vm: &VirtualMachine) -> PyResult { Ok("typing.NoDefault".to_owned()) } } @@ -104,7 +104,7 @@ pub(crate) mod decl { // compute_value: PyObjectRef, // module: PyObjectRef, } - #[pyclass(flags(BASETYPE))] + #[pyclass(with(Constructor, Representable), flags(BASETYPE))] impl TypeAliasType { pub const fn new(name: PyObjectRef, type_params: PyTupleRef, value: PyObjectRef) -> Self { Self { @@ -128,10 +128,51 @@ pub(crate) mod decl { fn __type_params__(&self) -> PyTupleRef { self.type_params.clone() } + } + + impl Constructor for TypeAliasType { + type Args = FuncArgs; + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // TypeAliasType(name, value, *, type_params=None) + if args.args.len() < 2 { + return Err(vm.new_type_error(format!( + "TypeAliasType() missing {} required positional argument{}: {}", + 2 - args.args.len(), + if 2 - args.args.len() == 1 { "" } else { "s" }, + if args.args.is_empty() { + "'name' and 'value'" + } else { + "'value'" + } + ))); + } + if args.args.len() > 2 { + return Err(vm.new_type_error(format!( + "TypeAliasType() takes 2 positional arguments but {} were given", + args.args.len() + ))); + } + + let name = args.args[0].clone(); + let value = args.args[1].clone(); + + let type_params = if let Some(tp) = args.kwargs.get("type_params") { + tp.clone() + .downcast::() + .map_err(|_| vm.new_type_error("type_params must be a tuple".to_owned()))? + } else { + vm.ctx.empty_tuple.clone() + }; + + let ta = TypeAliasType::new(name, type_params, value); + ta.into_ref_with_type(vm, cls).map(Into::into) + } + } - #[pymethod(name = "__repr__")] - fn repr(&self, vm: &VirtualMachine) -> PyResult { - let name = self.name.str(vm)?; + impl Representable for TypeAliasType { + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let name = zelf.name.str(vm)?; Ok(name.as_str().to_owned()) } }