Skip to content

Commit df653ce

Browse files
authored
Name generated signature parameters the way CPython names them (#8725)
* Leave Rust-only name decoration out of a generated signature A leading underscore marks an argument Rust sees as unused and `r#` escapes a Rust keyword. Neither describes the parameter a Python caller passes, and `r#` is not valid Python, so `inspect.signature()` rejects any signature carrying one. Reporting the bare name fixes _sre.template, select.epoll.__exit__ and bytearray.__reduce_ex__ without touching them, and lets a method name a parameter after a Rust keyword. A parameter CPython itself names with a leading underscore, such as compile's `_feature_version`, reaches Python through a FromArgs field, which this never sees. Assisted-by: Claude Code:claude-opus-5 * Name builtin type parameters the way CPython names them A generated __text_signature__ reports the Rust parameter name, so a name chosen for Rust reaches anyone reading help() or inspect. Rename the ones CPython describes differently: list.pop's `i` becomes `index`, list.count's `needle` becomes `value`, __format__'s `spec` becomes `format_spec`, bytes.strip's `chars` becomes `bytes`, and object.__reduce_ex__'s `proto` becomes `protocol`. Left alone are the parameters CPython reports as `object` or `unused`. Those are synthesised from the METH_* flags rather than named: CPython reports slice.indices as ($self, object, /) while its own docstring calls the argument len. Assisted-by: Claude Code:claude-opus-5 * Name os parameters the way CPython names them os.read's `n` becomes `length`, os.lseek's `how` becomes `whence`, os.kill's `sig` becomes `signal`, os.stat's `file` becomes `path`, and os.putenv's `key` becomes `name`, among others. os.chmod is left alone. It reports (path, dir_fd, mode, follow_symlinks) while the call binds mode second and takes follow_symlinks by keyword, so the order and the kind are wrong rather than the names, which a rename cannot fix. os.fstatvfs is left alone too: it shares one Rust function with statvfs, which CPython names `path`, so the two aliases cannot report different names. Assisted-by: Claude Code:claude-opus-5 * Name sys parameters the way CPython names them sys.excepthook's (exc_type, exc_val, exc_tb) become (exctype, value, traceback), sys._getframe's `offset` becomes `depth`, sys.intern's `s` becomes `string`, and sys.settrace's `tracefunc` becomes `function`. sys._clear_type_descriptors takes the name CPython gives it, which Rust spells r#type. Assisted-by: Claude Code:claude-opus-5 * Name vm stdlib parameters the way CPython names them deque.rotate's `mid` becomes `n`, deque.extend's `iter` becomes `iterable`, _sre.ascii_tolower's `ch` becomes `character`, _operator.neg's `pos` becomes `a`, signal.alarm's `time` becomes `seconds`, and codecs.register_error's `name` becomes `errors`. gc.collect and charmap_build keep theirs. Their argument is a FromArgs struct or a PosArgs bundle, so the reported name is the binding rather than a parameter, and renaming the binding would only disguise that. Assisted-by: Claude Code:claude-opus-5 * Name stdlib crate parameters the way CPython names them unicodedata.category's `character` becomes `chr`, math.isqrt's `x` becomes `n`, cmath.polar's `x` becomes `z`, struct.calcsize's `fmt` becomes `format`, binascii.crc32's `init` becomes `crc`, zlib.adler32's `begin_state` becomes `value`, and array.append's `x` becomes `v`. array.fromunicode's parameter is `ustr`, which cspell has to be told about. array.__deepcopy__ keeps `_memo`, which the generator now reports as `memo`; CPython calls it `unused`, a name synthesised from METH_O rather than chosen. Assisted-by: Claude Code:claude-opus-5 * Name BaseExceptionGroup.split's parameter the way CPython names it CPython calls the argument of split and subgroup `matcher_value`. Assisted-by: Claude Code:claude-opus-5 * Strip a generated parameter name with unraw and one prefix check syn already removes r# through IdentExt::unraw, the way from_args.rs does for a field name. The empty-name guard never ran: a bare `_` is Pat::Wild, which func_sig has already refused. Assisted-by: Claude Code:claude-fable-5-1 * Take object and unused from CPython's Clinic signatures CPython also falls back to these names for METH_O, but list.append and insert, set.add, discard and remove, _weakref.getweakrefcount and getweakrefs, sys.displayhook and getrefcount, and array.__deepcopy__ get them from an Argument Clinic signature. The earlier commit that kept array.__deepcopy__'s `_memo` was wrong to call `unused` synthesised. test_pydoc's set.add summary-line tests now pass. Assisted-by: Claude Code:claude-opus-5
1 parent 3191eca commit df653ce

33 files changed

Lines changed: 333 additions & 325 deletions

‎.cspell.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
"TESTFN",
120120
"TZPATH",
121121
"unraisable",
122+
"ustr",
122123
"wasi",
123124
"weaked",
124125
// unix

‎Lib/test/test_pydoc/test_pydoc.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,12 +1604,10 @@ def test_bound_builtin_method_noargs(self):
16041604
self.assertEqual(self._get_summary_line(''.lower),
16051605
"lower() method of builtins.str instance")
16061606

1607-
@unittest.expectedFailure # TODO: RUSTPYTHON
16081607
def test_unbound_builtin_method_o(self):
16091608
self.assertEqual(self._get_summary_line(set.add),
16101609
"add(self, object, /) unbound builtins.set method")
16111610

1612-
@unittest.expectedFailure # TODO: RUSTPYTHON
16131611
def test_bound_builtin_method_o(self):
16141612
self.assertEqual(self._get_summary_line(set().add),
16151613
"add(object, /) method of builtins.set instance")

‎crates/derive-impl/src/util.rs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use itertools::Itertools;
22
use proc_macro2::{Span, TokenStream};
33
use quote::{ToTokens, quote};
44
use std::collections::{HashMap, HashSet};
5-
use syn::{Attribute, FnArg, Ident, Result, Signature, UseTree, spanned::Spanned};
5+
use syn::{Attribute, FnArg, Ident, Result, Signature, UseTree, ext::IdentExt, spanned::Spanned};
66
use syn_ext::{
77
ext::{AttributeExt as SynAttributeExt, *},
88
types::*,
@@ -868,11 +868,13 @@ fn func_sig(sig: &Signature, mut implicit_self: Option<&str>) -> Option<String>
868868
let syn::Pat::Ident(pat) = arg.pat.as_ref() else {
869869
return None;
870870
};
871-
let ident = pat.ident.to_string();
871+
let ident = pat.ident.unraw().to_string();
872872
if ident == "vm" {
873873
unreachable!("type &VirtualMachine(`{ty}`) must be filtered already");
874874
}
875-
params.push(ident);
875+
// A leading `_` only marks the argument unused in Rust. A parameter whose
876+
// Python name really starts with `_` has to be a FromArgs field instead.
877+
params.push(ident.strip_prefix('_').unwrap_or(&ident).to_owned());
876878
}
877879
Some(params.join(", "))
878880
}

‎crates/stdlib/src/array.rs‎

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -798,8 +798,8 @@ pub mod array {
798798
}
799799

800800
#[pymethod]
801-
fn append(zelf: &Py<Self>, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
802-
zelf.try_resizable(vm)?.push(x, vm)
801+
fn append(zelf: &Py<Self>, v: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
802+
zelf.try_resizable(vm)?.push(v, vm)
803803
}
804804

805805
#[pymethod]
@@ -814,24 +814,24 @@ pub mod array {
814814
}
815815

816816
#[pymethod]
817-
fn count(&self, x: PyObjectRef, vm: &VirtualMachine) -> usize {
818-
self.read().count(x, vm)
817+
fn count(&self, v: PyObjectRef, vm: &VirtualMachine) -> usize {
818+
self.read().count(v, vm)
819819
}
820820

821821
#[pymethod]
822-
fn remove(zelf: &Py<Self>, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
823-
zelf.try_resizable(vm)?.remove(x, vm)
822+
fn remove(zelf: &Py<Self>, v: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
823+
zelf.try_resizable(vm)?.remove(v, vm)
824824
}
825825

826826
#[pymethod]
827-
fn extend(zelf: &Py<Self>, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
827+
fn extend(zelf: &Py<Self>, bb: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
828828
let mut w = zelf.try_resizable(vm)?;
829-
if zelf.is(&obj) {
829+
if zelf.is(&bb) {
830830
w.imul(2, vm)
831-
} else if let Some(array) = obj.downcast_ref::<Self>() {
831+
} else if let Some(array) = bb.downcast_ref::<Self>() {
832832
w.iadd(&array.read(), vm)
833833
} else {
834-
let iter = ArgIterable::try_from_object(vm, obj)?;
834+
let iter = ArgIterable::try_from_object(vm, bb)?;
835835
// zelf.extend_from_iterable(iter, vm)
836836
for obj in iter.iter(vm)? {
837837
w.push(obj?, vm)?;
@@ -883,11 +883,11 @@ pub mod array {
883883
}
884884

885885
#[pymethod]
886-
fn fromunicode(zelf: &Py<Self>, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
887-
let wtf8: &Wtf8 = obj.try_to_value(vm).map_err(|_| {
886+
fn fromunicode(zelf: &Py<Self>, ustr: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
887+
let wtf8: &Wtf8 = ustr.try_to_value(vm).map_err(|_| {
888888
vm.new_type_error(format!(
889889
"fromunicode() argument must be str, not {}",
890-
obj.class().name()
890+
ustr.class().name()
891891
))
892892
})?;
893893
if !matches!(zelf.read().typecode(), 'u' | 'w') {
@@ -924,15 +924,15 @@ pub mod array {
924924
}
925925

926926
#[pymethod]
927-
fn frombytes(&self, b: ArgBytesLike, vm: &VirtualMachine) -> PyResult<()> {
927+
fn frombytes(&self, buffer: ArgBytesLike, vm: &VirtualMachine) -> PyResult<()> {
928928
// The source is read as bytes, so items of any other width would
929929
// be reinterpreted rather than appended.
930-
if b.itemsize() != 1 {
930+
if buffer.itemsize() != 1 {
931931
return Err(vm.new_type_error("a bytes-like object is required"));
932932
}
933-
let b = b.borrow_buf();
933+
let buffer = buffer.borrow_buf();
934934
let itemsize = self.read().itemsize();
935-
self._from_bytes(&b, itemsize, vm)
935+
self._from_bytes(&buffer, itemsize, vm)
936936
}
937937

938938
#[pymethod]
@@ -977,9 +977,9 @@ pub mod array {
977977
}
978978

979979
#[pymethod]
980-
fn insert(zelf: &Py<Self>, i: isize, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
980+
fn insert(zelf: &Py<Self>, i: isize, v: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
981981
let mut w = zelf.try_resizable(vm)?;
982-
w.insert(i, x, vm)
982+
w.insert(i, v, vm)
983983
}
984984

985985
#[pymethod]
@@ -1049,7 +1049,7 @@ pub mod array {
10491049
}
10501050

10511051
#[pymethod]
1052-
fn __deepcopy__(&self, _memo: PyObjectRef) -> Self {
1052+
fn __deepcopy__(&self, _unused: PyObjectRef) -> Self {
10531053
self.__copy__()
10541054
}
10551055

@@ -1198,10 +1198,10 @@ pub mod array {
11981198
#[pymethod]
11991199
fn __reduce_ex__(
12001200
zelf: &Py<Self>,
1201-
proto: usize,
1201+
value: usize,
12021202
vm: &VirtualMachine,
12031203
) -> PyResult<(PyObjectRef, PyTupleRef, Option<PyDictRef>)> {
1204-
if proto < 3 {
1204+
if value < 3 {
12051205
return Self::__reduce__(zelf, vm);
12061206
}
12071207
let array = zelf.read();

‎crates/stdlib/src/binascii.rs‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,21 @@ mod decl {
5656

5757
#[pyfunction(name = "a2b_hex")]
5858
#[pyfunction]
59-
fn unhexlify(data: ArgAsciiBuffer, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
60-
data.with_ref(binascii::unhexlify)
59+
fn unhexlify(hexstr: ArgAsciiBuffer, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
60+
hexstr
61+
.with_ref(binascii::unhexlify)
6162
.map_err(|e| new_binascii_error(e, vm))
6263
}
6364

6465
#[pyfunction]
65-
pub(crate) fn crc32(data: ArgBytesLike, init: OptionalArg<PyIntRef>) -> u32 {
66-
let init = init.map_or(0, |i| i.as_u32_mask());
67-
data.with_ref(|bytes| binascii::crc32(bytes, init))
66+
pub(crate) fn crc32(data: ArgBytesLike, crc: OptionalArg<PyIntRef>) -> u32 {
67+
let crc = crc.map_or(0, |i| i.as_u32_mask());
68+
data.with_ref(|bytes| binascii::crc32(bytes, crc))
6869
}
6970

7071
#[pyfunction]
71-
pub(crate) fn crc_hqx(data: ArgBytesLike, init: PyIntRef) -> u32 {
72-
data.with_ref(|bytes| binascii::crc_hqx(bytes, init.as_u32_mask()))
72+
pub(crate) fn crc_hqx(data: ArgBytesLike, crc: PyIntRef) -> u32 {
73+
data.with_ref(|bytes| binascii::crc_hqx(bytes, crc.as_u32_mask()))
7374
}
7475

7576
#[derive(FromArgs)]
@@ -136,8 +137,8 @@ mod decl {
136137
}
137138

138139
#[pyfunction]
139-
fn a2b_uu(s: ArgAsciiBuffer, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
140-
s.with_ref(binascii::a2b_uu)
140+
fn a2b_uu(data: ArgAsciiBuffer, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
141+
data.with_ref(binascii::a2b_uu)
141142
.map_err(|e| new_binascii_error(e, vm))
142143
}
143144

‎crates/stdlib/src/cmath.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ mod cmath {
3232
}
3333

3434
#[pyfunction]
35-
fn polar(x: ArgIntoComplex, vm: &VirtualMachine) -> PyResult<(f64, f64)> {
36-
pymath::cmath::polar(x.into_complex()).map_err(|err| pymath_exception(err, vm))
35+
fn polar(z: ArgIntoComplex, vm: &VirtualMachine) -> PyResult<(f64, f64)> {
36+
pymath::cmath::polar(z.into_complex()).map_err(|err| pymath_exception(err, vm))
3737
}
3838

3939
#[pyfunction]

‎crates/stdlib/src/math.rs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -762,8 +762,8 @@ mod math {
762762
// Integer functions:
763763

764764
#[pyfunction]
765-
fn isqrt(x: ArgIndex, vm: &VirtualMachine) -> PyResult<BigInt> {
766-
let value = x.into_int_ref();
765+
fn isqrt(n: ArgIndex, vm: &VirtualMachine) -> PyResult<BigInt> {
766+
let value = n.into_int_ref();
767767
pymath::math::integer::isqrt(value.as_bigint())
768768
.map_err(|_| vm.new_value_error("isqrt() argument must be nonnegative"))
769769
}
@@ -791,12 +791,12 @@ mod math {
791791
}
792792

793793
#[pyfunction]
794-
fn factorial(x: PyIntRef, vm: &VirtualMachine) -> PyResult<BigInt> {
794+
fn factorial(n: PyIntRef, vm: &VirtualMachine) -> PyResult<BigInt> {
795795
// Check for negative before overflow - negative values are always invalid
796-
if x.as_bigint().is_negative() {
796+
if n.as_bigint().is_negative() {
797797
return Err(vm.new_value_error("factorial() not defined for negative values"));
798798
}
799-
let n: i64 = x.try_to_primitive(vm).map_err(|_| {
799+
let n: i64 = n.try_to_primitive(vm).map_err(|_| {
800800
vm.new_overflow_error("factorial() argument should not exceed 9223372036854775807")
801801
})?;
802802
pymath::math::integer::factorial(n)

‎crates/stdlib/src/pystruct.rs‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,11 @@ pub(crate) mod _struct {
144144

145145
#[pyfunction]
146146
fn unpack(
147-
fmt: IntoStructFormatBytes,
147+
format: IntoStructFormatBytes,
148148
buffer: ArgBytesLike,
149149
vm: &VirtualMachine,
150150
) -> PyResult<PyTupleRef> {
151-
let format_spec = fmt.format_spec(vm)?;
151+
let format_spec = format.format_spec(vm)?;
152152
buffer.with_ref(|buf| format_spec.unpack(buf, vm))
153153
}
154154

@@ -239,17 +239,17 @@ pub(crate) mod _struct {
239239

240240
#[pyfunction]
241241
fn iter_unpack(
242-
fmt: IntoStructFormatBytes,
242+
format: IntoStructFormatBytes,
243243
buffer: ArgBytesLike,
244244
vm: &VirtualMachine,
245245
) -> PyResult<UnpackIterator> {
246-
let format_spec = fmt.format_spec(vm)?;
246+
let format_spec = format.format_spec(vm)?;
247247
UnpackIterator::with_buffer(vm, format_spec, buffer)
248248
}
249249

250250
#[pyfunction]
251-
fn calcsize(fmt: IntoStructFormatBytes, vm: &VirtualMachine) -> PyResult<usize> {
252-
Ok(fmt.format_spec(vm)?.size)
251+
fn calcsize(format: IntoStructFormatBytes, vm: &VirtualMachine) -> PyResult<usize> {
252+
Ok(format.format_spec(vm)?.size)
253253
}
254254

255255
/// What a `Struct` is once a format has been read into it. Held apart
@@ -342,9 +342,9 @@ pub(crate) mod _struct {
342342
}
343343

344344
#[pymethod]
345-
fn unpack(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
345+
fn unpack(&self, buffer: ArgBytesLike, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
346346
let inner = self.ready(vm)?;
347-
data.with_ref(|buf| inner.spec.unpack(buf, vm))
347+
buffer.with_ref(|buf| inner.spec.unpack(buf, vm))
348348
}
349349

350350
#[pymethod]

0 commit comments

Comments
 (0)