From 7b58a8980d0403bdbb43a77f27116122ccafcecc Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Thu, 13 Aug 2026 23:32:19 +0900 Subject: [PATCH] Reject extra positional arguments to `float()` (#8509) * Reject extra positional arguments to float() PyFloat::slot_new's exact-float fast path tested args.args.first(), which succeeds for any number of positional arguments, so float(1.5, True) returned the first one and never reached args.bind(vm). CPython's clinic generated float_new runs _PyArg_CheckPositional("float", nargs, 0, 1) before it fetches the first argument. Add args.args.len() == 1 to the condition, as PyInt::slot_new, PyStr::slot_new and PyComplex::slot_new already do; the extra argument then falls through to args.bind(vm) and raises TypeError. Assisted-by: Claude Code:claude-opus-5 * Bind float() arguments before the fast path The exact-float fast path repeated float()'s argument count inline, which duplicates what FromArgs::arity already knows. Bind first and match on the resulting OptionalArg instead, so the positional and keyword rules stay in one place and the bound value is reused by py_new. Assisted-by: Claude Code:claude-opus-5 --- crates/vm/src/builtins/float.rs | 8 +++++--- extra_tests/snippets/builtin_float.py | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index 1c861b14f..0b7396946 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -176,16 +176,18 @@ impl Constructor for PyFloat { type Args = OptionalArg; fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // Bind before the fast path so FromArgs::arity decides how many arguments + // are acceptable, rather than a count repeated here. + let arg: Self::Args = args.bind(vm)?; + // Optimization: return exact float as-is if cls.is(vm.ctx.types.float_type) - && args.kwargs.is_empty() - && let Some(first) = args.args.first() + && let OptionalArg::Present(first) = &arg && first.class().is(vm.ctx.types.float_type) { return Ok(first.clone()); } - let arg: Self::Args = args.bind(vm)?; let payload = Self::py_new(&cls, arg, vm)?; payload.into_ref_with_type(vm, cls).map(Into::into) } diff --git a/extra_tests/snippets/builtin_float.py b/extra_tests/snippets/builtin_float.py index 1417c5ae1..c459c2d0d 100644 --- a/extra_tests/snippets/builtin_float.py +++ b/extra_tests/snippets/builtin_float.py @@ -561,3 +561,12 @@ assert str(161852602146008.12) == "161852602146008.12" assert repr(1.5) == "1.5" assert repr(0.1) == "0.1" assert repr(100.0) == "100.0" + + +# float() takes at most one positional argument; the exact-float fast path +# must not let extra ones through. +assert_raises(TypeError, float, 1.5, True) +assert_raises(TypeError, float, 1.5, 2, 3) +assert_raises(TypeError, float, "1.5", 2) +assert float(1.5) == 1.5 +assert float() == 0.0