Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0e52994
Align more parser/SyntaxError messages with CPython 3.14.5
JamesClarke7283 May 29, 2026
535a19d
Fix wasm32_without_js link error: declare host fns as `env` imports
JamesClarke7283 May 29, 2026
3f5db72
Lib/test: drop now-passing expectedFailure markers
JamesClarke7283 May 29, 2026
dc533ce
codegen: align star/double-comma display errors with CPython
JamesClarke7283 May 31, 2026
6f6883e
Lib: un-mark test_pdb_closure as an expected failure
JamesClarke7283 Aug 14, 2026
e7b0fe3
compiler: align 36 more SyntaxError messages with CPython 3.14
JamesClarke7283 Aug 14, 2026
de9d772
Align the remaining runtime error messages with CPython
JamesClarke7283 Aug 14, 2026
cd9c530
sqlite3: fix set_authorizer and align its error messages with CPython
JamesClarke7283 Aug 14, 2026
1d0049b
hashlib: report CPython's argument errors for the hash constructors
JamesClarke7283 Aug 14, 2026
1ac3912
stdlib: align remaining module error messages with CPython 3.14
JamesClarke7283 Aug 16, 2026
77b4c7e
vm: align builtin and runtime error messages with CPython 3.14
JamesClarke7283 Aug 16, 2026
af94627
Lib/test: drop expectedFailure markers that now pass
JamesClarke7283 Aug 16, 2026
93b0351
Fix CI failures from the rebase: macOS/wasm builds, clippy, str.count
JamesClarke7283 Aug 16, 2026
adc4d3a
Fix the remaining CI failures: getaddrinfo port types, macOS clippy
JamesClarke7283 Aug 16, 2026
61bafa0
Fix the last two clippy-only CI failures (macOS, Windows)
JamesClarke7283 Aug 17, 2026
dda93ea
Fix the last Windows clippy failures
JamesClarke7283 Aug 17, 2026
9653df7
array: allow clippy::use_self on the c_ulong conversion for Windows
JamesClarke7283 Aug 17, 2026
57842eb
Code review: close the remaining argument-error gaps in bytes/bytearray
JamesClarke7283 Aug 17, 2026
f79a1d7
expandtabs: convert tabsize with the clinic int converter
JamesClarke7283 Aug 18, 2026
f919d3b
derive: let a function declare its own __text_signature__
JamesClarke7283 Aug 18, 2026
2fc41df
vm: park the overlap test's workers detached
JamesClarke7283 Aug 19, 2026
20a111f
winsound: drop the imports the host_env move left behind
JamesClarke7283 Aug 19, 2026
acce4ce
Code review: fix six error-message defects found against CPython 3.14.7
JamesClarke7283 Aug 19, 2026
88231c8
Merge branch 'main' into align-aug-assign-delete-set-dict-errors
JamesClarke7283 Sep 2, 2026
54dbd95
ci: retrigger after the macOS runner's toolchain download failed
JamesClarke7283 Sep 2, 2026
0db2af0
Name the callee at the bindings that take keywords, and let list.appe…
JamesClarke7283 Sep 2, 2026
43eec46
Declare CPython's text signature on every method that binds FuncArgs …
JamesClarke7283 Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
derive: let a function declare its own __text_signature__
#[pyfunction]/#[pymethod] derive __text_signature__ from the Rust
parameter list, and a function that takes FuncArgs to check its own
arity has no parameters to report, so func_sig emits
"(*args, **kwargs)". Every builtin this branch rewrote that way -
len, abs, hash, chr, callable, bin, ord, divmod, isinstance,
issubclass and the rest of the 27 - stopped reporting the signature
that #8512 had just made accurate:

    inspect.signature(len)
    (*args, **kwargs)     # was (obj, /)

Add `text_signature = "..."`, which overrides the derived parameter
list, and give the affected builtins CPython's own, verified against
CPython 3.14.7. round declares (number, ndigits=None) and so has a
signature now, where before its destructuring pattern left it with
none; builtin_signature.py keeps sum as the signature-less case and
asserts round's instead.

The derived signature is still used wherever no override is given, so
genuinely variadic builtins such as breakpoint keep reporting
(*args, **kwargs).

Assisted-by: Claude:Claude Opus 5
  • Loading branch information
JamesClarke7283 committed Aug 18, 2026
commit f919d3b32ccbf02b4a74e1edc87859426a34d000
7 changes: 5 additions & 2 deletions crates/derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,10 @@ where
}

let raw = item_meta.raw()?;
let sig_doc = text_signature(func.sig(), &py_name);
let sig_doc = match item_meta.explicit_text_signature()? {
Some(params) => Some(format!("{py_name}{params}")),
None => text_signature(func.sig(), &py_name),
};
let has_receiver = func
.sig()
.inputs
Expand Down Expand Up @@ -1600,7 +1603,7 @@ impl ToTokens for MemberNursery {
struct MethodItemMeta(ItemMetaInner);

impl ItemMeta for MethodItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["name", "raw"];
const ALLOWED_NAMES: &'static [&'static str] = &["name", "raw", "text_signature"];

fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
Expand Down
5 changes: 4 additions & 1 deletion crates/derive-impl/src/pymodule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,10 @@ impl ModuleItem for FunctionItem {
let item_meta = SimpleItemMeta::from_attr(ident.clone(), &item_attr)?;

let py_name = item_meta.simple_name()?;
let sig_doc = text_signature(func.sig(), &py_name);
let sig_doc = match item_meta.explicit_text_signature()? {
Some(params) => Some(format!("{py_name}{params}")),
None => text_signature(func.sig(), &py_name),
};

let module = args.module_name();
// TODO: doc must exist at least one of code or CPython
Expand Down
9 changes: 8 additions & 1 deletion crates/derive-impl/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,13 @@ pub(crate) trait ItemMeta: Sized {
self.inner()._optional_str("name").ok().flatten()
}

/// An explicitly declared `__text_signature__` parameter list, for a
/// function whose Rust arguments cannot describe its Python ones, e.g. one
/// that takes `FuncArgs` to check its own arity.
fn explicit_text_signature(&self) -> Result<Option<String>> {
self.inner()._optional_str("text_signature")
}

fn new_meta_error(&self, msg: &str) -> syn::Error {
let inner = self.inner();
err_span!(inner.meta_ident, "#[{}] {}", inner.meta_name(), msg)
Expand All @@ -304,7 +311,7 @@ pub(crate) trait ItemMeta: Sized {
pub(crate) struct SimpleItemMeta(pub ItemMetaInner);

impl ItemMeta for SimpleItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["name"];
const ALLOWED_NAMES: &'static [&'static str] = &["name", "text_signature"];

fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
Expand Down
36 changes: 18 additions & 18 deletions crates/vm/src/stdlib/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ mod builtins {
const CODEGEN_NOT_SUPPORTED: &str =
"can't compile() to bytecode when the `codegen` feature of rustpython is disabled";

#[pyfunction]
#[pyfunction(text_signature = "(x, /)")]
fn abs(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
check_meth_o(vm, "abs", &func_args)?;
let (x,): (PyObjectRef,) = func_args.bind(vm)?;
Expand Down Expand Up @@ -73,14 +73,14 @@ mod builtins {
obj.ascii(vm)
}

#[pyfunction(name = "ascii")]
#[pyfunction(name = "ascii", text_signature = "(obj, /)")]
fn py_ascii(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
check_meth_o(vm, "ascii", &func_args)?;
let (obj,): (PyObjectRef,) = func_args.bind(vm)?;
ascii(obj, vm)
}

#[pyfunction]
#[pyfunction(text_signature = "(number, /)")]
fn bin(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<String> {
check_meth_o(vm, "bin", &func_args)?;
let (x,): (ArgIndex,) = func_args.bind(vm)?;
Expand All @@ -93,14 +93,14 @@ mod builtins {
})
}

#[pyfunction]
#[pyfunction(text_signature = "(obj, /)")]
fn callable(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<bool> {
check_meth_o(vm, "callable", &func_args)?;
let (obj,): (PyObjectRef,) = func_args.bind(vm)?;
Ok(obj.is_callable())
}

#[pyfunction]
#[pyfunction(text_signature = "(i, /)")]
fn chr(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<CodePoint> {
check_meth_o(vm, "chr", &func_args)?;
let (i,): (ArgIndex,) = func_args.bind(vm)?;
Expand Down Expand Up @@ -431,7 +431,7 @@ mod builtins {
vm.dir(obj.into_option())
}

#[pyfunction]
#[pyfunction(text_signature = "(x, y, /)")]
fn divmod(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
check_no_kwargs(vm, "divmod", &func_args)?;
check_positional(vm, "divmod", func_args.args.len(), 2, 2)?;
Expand Down Expand Up @@ -771,7 +771,7 @@ mod builtins {
}
}

#[pyfunction]
#[pyfunction(text_signature = "()")]
fn globals(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<PyDictRef> {
check_noargs(vm, "globals", &func_args)?;
Ok(vm.current_globals())
Expand All @@ -788,7 +788,7 @@ mod builtins {
Ok(vm.get_attribute_opt(obj, attr)?.is_some())
}

#[pyfunction]
#[pyfunction(text_signature = "(obj, /)")]
fn hash(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<PyHash> {
check_meth_o(vm, "hash", &func_args)?;
let (obj,): (PyObjectRef,) = func_args.bind(vm)?;
Expand All @@ -806,7 +806,7 @@ mod builtins {
}
}

#[pyfunction]
#[pyfunction(text_signature = "(number, /)")]
fn hex(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<String> {
check_meth_o(vm, "hex", &func_args)?;
let (number,): (ArgIndex,) = func_args.bind(vm)?;
Expand All @@ -815,7 +815,7 @@ mod builtins {
Ok(format!("{n:#x}"))
}

#[pyfunction]
#[pyfunction(text_signature = "(obj, /)")]
fn id(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<usize> {
check_meth_o(vm, "id", &func_args)?;
let (obj,): (PyObjectRef,) = func_args.bind(vm)?;
Expand Down Expand Up @@ -889,15 +889,15 @@ mod builtins {
false
}

#[pyfunction]
#[pyfunction(text_signature = "(obj, class_or_tuple, /)")]
fn isinstance(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<bool> {
check_no_kwargs(vm, "isinstance", &func_args)?;
check_positional(vm, "isinstance", func_args.args.len(), 2, 2)?;
let (obj, typ): (PyObjectRef, PyObjectRef) = func_args.bind(vm)?;
obj.is_instance(&typ, vm)
}

#[pyfunction]
#[pyfunction(text_signature = "(cls, class_or_tuple, /)")]
fn issubclass(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<bool> {
check_no_kwargs(vm, "issubclass", &func_args)?;
check_positional(vm, "issubclass", func_args.args.len(), 2, 2)?;
Expand Down Expand Up @@ -955,14 +955,14 @@ mod builtins {
}
}

#[pyfunction]
#[pyfunction(text_signature = "(obj, /)")]
fn len(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<usize> {
check_meth_o(vm, "len", &func_args)?;
let (obj,): (PyObjectRef,) = func_args.bind(vm)?;
obj.length(vm)
}

#[pyfunction]
#[pyfunction(text_signature = "()")]
fn locals(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<ArgMapping> {
check_noargs(vm, "locals", &func_args)?;
vm.current_locals()
Expand Down Expand Up @@ -1062,7 +1062,7 @@ mod builtins {
})
}

#[pyfunction]
#[pyfunction(text_signature = "(number, /)")]
fn oct(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
check_meth_o(vm, "oct", &func_args)?;
let (number,): (ArgIndex,) = func_args.bind(vm)?;
Expand All @@ -1077,7 +1077,7 @@ mod builtins {
Ok(vm.ctx.new_str(s).into())
}

#[pyfunction]
#[pyfunction(text_signature = "(character, /)")]
// builtin_ord
fn ord(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<u32> {
check_meth_o(vm, "ord", &func_args)?;
Expand Down Expand Up @@ -1217,7 +1217,7 @@ mod builtins {
Ok(())
}

#[pyfunction]
#[pyfunction(text_signature = "(obj, /)")]
fn repr(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
check_meth_o(vm, "repr", &func_args)?;
let (obj,): (PyObjectRef,) = func_args.bind(vm)?;
Expand Down Expand Up @@ -1252,7 +1252,7 @@ mod builtins {
ndigits: OptionalOption<PyObjectRef>,
}

#[pyfunction]
#[pyfunction(text_signature = "(number, ndigits=None)")]
fn round(func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
if func_args.args.is_empty() && !func_args.kwargs.contains_key("number") {
return Err(vm.new_type_error("round() missing required argument 'number' (pos 1)"));
Expand Down
8 changes: 7 additions & 1 deletion extra_tests/snippets/builtin_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@
assert str(inspect.signature(issubclass)) == "(cls, class_or_tuple, /)"
assert str(inspect.signature(aiter)) == "(async_iterable, /)"

# A function that takes FuncArgs to check its own arity has no Rust parameter
# list to report, so it declares the signature itself.
assert str(inspect.signature(round)) == "(number, ndigits=None)"
assert str(inspect.signature(globals)) == "()"
assert str(inspect.signature(ascii)) == "(obj, /)"

if sys.implementation.name == "rustpython":
# Functions whose Rust arguments are destructuring patterns rather than
# plain names get no signature at all, instead of emitting text that is not
Expand All @@ -56,7 +62,7 @@
# We cannot derive them until FromArgs reports the parameters of its own
# structs, so until then we report no signature, which is at least how
# CPython behaves for the builtins it has no signature for.
for f in (round, sum):
for f in (sum,):
assert f.__text_signature__ is None, f.__name__
try:
inspect.signature(f)
Expand Down