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
This commit is contained in:
Jeongseop Lim authored and GitHub committed 2026-08-13 23:32:19 +09:00
1 parent 6cfa079950
commit 7b58a8980d
2 files changed
+14 -3

No files matched your search

+5 -3
View File
@@ -176,16 +176,18 @@ impl Constructor for PyFloat {
type Args = OptionalArg<PyObjectRef>;
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)
}
+9
View File
@@ -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