diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 1b16da42648..3f1e2331bc2 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -4728,8 +4728,6 @@ class C: b: int = field(kw_only=True) self.assertEqual(C(42, b=10).__match_args__, ('a',)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_KW_ONLY(self): @dataclass class A: diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 6de5d14bf73..047916caf07 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -465,11 +465,9 @@ def __str__(self): self.assertIn('astr', r) self.assertIn("['sth']", r) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_repr(self): return super().test_repr() - @unittest.expectedFailure # TODO: RUSTPYTHON def test_recursive_repr(self): return super().test_recursive_repr() diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index c6f66085c74..8833a102b36 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -479,7 +479,6 @@ def generator(): with self.assertRaises(StopIteration): gen.throw(E) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: DeprecationWarning not triggered def test_gen_3_arg_deprecation_warning(self): def g(): yield 42 diff --git a/Lib/test/test_keywordonlyarg.py b/Lib/test/test_keywordonlyarg.py index e41e7c051f6..8fd2e89122f 100644 --- a/Lib/test/test_keywordonlyarg.py +++ b/Lib/test/test_keywordonlyarg.py @@ -58,7 +58,6 @@ def testSyntaxForManyArguments(self): fundef = "def f(*, %s):\n pass\n" % ', '.join('i%d' % i for i in range(300)) compile(fundef, "", "single") - @unittest.expectedFailure # TODO: RUSTPYTHON def testTooManyPositionalErrorMessage(self): def f(a, b=None, *, c=None): pass diff --git a/Lib/test/test_positional_only_arg.py b/Lib/test/test_positional_only_arg.py index e0d784325ba..4b27f4f4d3f 100644 --- a/Lib/test/test_positional_only_arg.py +++ b/Lib/test/test_positional_only_arg.py @@ -152,8 +152,6 @@ def f(a, b, /, c): with self.assertRaisesRegex(TypeError, r"f\(\) takes 3 positional arguments but 4 were given"): f(1, 2, 3, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_positional_only_and_optional_arg_invalid_calls(self): def f(a, b, /, c=3): pass @@ -198,8 +196,6 @@ def f(a, b, /): with self.assertRaisesRegex(TypeError, r"f\(\) takes 2 positional arguments but 3 were given"): f(1, 2, 3) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_positional_only_with_optional_invalid_calls(self): def f(a, b=2, /): pass diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 74ab94eb5c3..3e6c530cecc 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8144,7 +8144,6 @@ class CNT(NamedTuple): self.assertEqual(struct.__annotations__, {}) self.assertIsInstance(struct(), struct) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_namedtuple_errors(self): with self.assertRaises(TypeError): NamedTuple.__new__() diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs index 483ba6a7f96..9218ccc8a43 100644 --- a/crates/vm/src/builtins/asyncgenerator.rs +++ b/crates/vm/src/builtins/asyncgenerator.rs @@ -4,7 +4,7 @@ use crate::{ builtins::PyBaseExceptionRef, class::PyClassImpl, common::lock::PyMutex, - coroutine::Coro, + coroutine::{Coro, warn_deprecated_throw_signature}, frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, @@ -312,6 +312,7 @@ impl PyAsyncGenASend { return Err(vm.new_runtime_error("cannot reuse already awaited __anext__()/asend()")); } + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; let res = self.ag.inner.throw( self.ag.as_object(), exc_type, @@ -431,6 +432,7 @@ impl PyAsyncGenAThrow { exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; let ret = self.ag.inner.throw( self.ag.as_object(), exc_type, @@ -601,6 +603,7 @@ impl PyAnextAwaitable { vm: &VirtualMachine, ) -> PyResult { self.check_closed(vm)?; + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; self.state.store(AwaitableState::Iter); let awaitable = self.get_awaitable_iter(vm)?; let result = vm.call_method( diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index 21405448693..9e8d5d534f1 100644 --- a/crates/vm/src/builtins/coroutine.rs +++ b/crates/vm/src/builtins/coroutine.rs @@ -2,7 +2,7 @@ use super::{PyCode, PyGenericAlias, PyStrRef, PyType, PyTypeRef}; use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, - coroutine::Coro, + coroutine::{Coro, warn_deprecated_throw_signature}, frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, @@ -108,6 +108,7 @@ impl Py { exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; self.inner.throw( self.as_object(), exc_type, @@ -181,6 +182,7 @@ impl PyCoroutineWrapper { vm: &VirtualMachine, ) -> PyResult { self.check_closed(vm)?; + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; let result = self.coro.throw(exc_type, exc_val, exc_tb, vm); // Mark as closed if exhausted if let Ok(PyIterReturn::StopIteration(_)) = &result { diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 95d70afcbc7..3e5048e133f 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -131,11 +131,25 @@ impl PyFunction { } else { // Check the number of positional arguments if nargs > n_expected_args { + let n_defaults = self + .defaults_and_kwdefaults + .lock() + .0 + .as_ref() + .map_or(0, |d| d.len()); + let n_required = n_expected_args - n_defaults; + let takes_msg = if n_defaults > 0 { + format!("from {} to {}", n_required, n_expected_args) + } else { + n_expected_args.to_string() + }; return Err(vm.new_type_error(format!( - "{}() takes {} positional arguments but {} were given", + "{}() takes {} positional argument{} but {} {} given", self.__qualname__(), - n_expected_args, - nargs + takes_msg, + if n_expected_args == 1 { "" } else { "s" }, + nargs, + if nargs == 1 { "was" } else { "were" } ))); } } diff --git a/crates/vm/src/builtins/generator.rs b/crates/vm/src/builtins/generator.rs index 04cd7dd3456..9a1e737500b 100644 --- a/crates/vm/src/builtins/generator.rs +++ b/crates/vm/src/builtins/generator.rs @@ -6,7 +6,7 @@ use super::{PyCode, PyGenericAlias, PyStrRef, PyType, PyTypeRef}; use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, - coroutine::Coro, + coroutine::{Coro, warn_deprecated_throw_signature}, frame::FrameRef, function::OptionalArg, protocol::PyIterReturn, @@ -99,6 +99,7 @@ impl Py { exc_tb: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + warn_deprecated_throw_signature(&exc_val, &exc_tb, vm)?; self.inner.throw( self.as_object(), exc_type, diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index ebe3107cb36..13dde152391 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -4,6 +4,7 @@ use crate::{ common::lock::PyMutex, exceptions::types::PyBaseException, frame::{ExecutionResult, FrameRef}, + function::OptionalArg, protocol::PyIterReturn, }; use crossbeam_utils::atomic::AtomicCell; @@ -211,3 +212,24 @@ impl Coro { pub fn is_gen_exit(exc: &Py, vm: &VirtualMachine) -> bool { exc.fast_isinstance(vm.ctx.exceptions.generator_exit) } + +/// Emit DeprecationWarning for the deprecated 3-argument throw() signature. +pub fn warn_deprecated_throw_signature( + exc_val: &OptionalArg, + exc_tb: &OptionalArg, + vm: &VirtualMachine, +) -> PyResult<()> { + if exc_val.is_present() || exc_tb.is_present() { + crate::warn::warn( + vm.ctx.new_str( + "the (type, val, tb) signature of throw() is deprecated, \ + use throw(val) instead", + ), + Some(vm.ctx.exceptions.deprecation_warning.to_owned()), + 1, + None, + vm, + )?; + } + Ok(()) +} diff --git a/crates/vm/src/stdlib/functools.rs b/crates/vm/src/stdlib/functools.rs index 77f352c78fc..a59fd48f6b2 100644 --- a/crates/vm/src/stdlib/functools.rs +++ b/crates/vm/src/stdlib/functools.rs @@ -43,7 +43,7 @@ mod _functools { } #[pyattr] - #[pyclass(name = "partial", module = "_functools")] + #[pyclass(name = "partial", module = "functools")] #[derive(Debug, PyPayload)] pub struct PyPartial { inner: PyRwLock, @@ -319,28 +319,22 @@ mod _functools { )); } - let class_name = zelf.class().name(); + let qualname = zelf.class().__qualname__(vm); + let qualname_str = qualname + .downcast::() + .map(|s| s.as_str().to_owned()) + .unwrap_or_else(|_| zelf.class().name().to_owned()); let module = zelf.class().__module__(vm); - let qualified_name = if zelf.class().is(Self::class(&vm.ctx)) { - // For the base partial class, always use functools.partial - "functools.partial".to_owned() - } else { - // For subclasses, check if they're defined in __main__ or test modules - match module.downcast::() { - Ok(module_str) => { - let module_name = module_str.as_str(); - match module_name { - "builtins" | "" | "__main__" => class_name.to_owned(), - name if name.starts_with("test.") || name == "test" => { - // For test modules, just use the class name without module prefix - class_name.to_owned() - } - _ => format!("{module_name}.{class_name}"), - } + let qualified_name = match module.downcast::() { + Ok(module_str) => { + let module_name = module_str.as_str(); + match module_name { + "builtins" | "" => qualname_str, + _ => format!("{module_name}.{qualname_str}"), } - Err(_) => class_name.to_owned(), } + Err(_) => qualname_str, }; Ok(format!(