From b089c9f4fb104d998e973e9a43f4810c5d033d70 Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Sat, 8 Aug 2026 05:57:53 +0900 Subject: [PATCH 1/2] 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 --- crates/vm/src/builtins/float.rs | 1 + extra_tests/snippets/builtin_float.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index 1c861b14fc6..04f86ac5620 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -178,6 +178,7 @@ impl Constructor for PyFloat { fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { // Optimization: return exact float as-is if cls.is(vm.ctx.types.float_type) + && args.args.len() == 1 && args.kwargs.is_empty() && let Some(first) = args.args.first() && first.class().is(vm.ctx.types.float_type) diff --git a/extra_tests/snippets/builtin_float.py b/extra_tests/snippets/builtin_float.py index 1417c5ae174..c459c2d0da6 100644 --- a/extra_tests/snippets/builtin_float.py +++ b/extra_tests/snippets/builtin_float.py @@ -561,3 +561,12 @@ def _check_msg(call, exc_type, expected_msg): 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 From 6fa759e23e1936872d6bc3e72cab936834e3db45 Mon Sep 17 00:00:00 2001 From: Jeongseop Lim Date: Thu, 13 Aug 2026 17:03:37 +0900 Subject: [PATCH 2/2] 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 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index 04f86ac5620..0b739694623 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -176,17 +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.args.len() == 1 - && 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) }