From d1bbf4bc1a52beda6b7556843b2e00ff716f0925 Mon Sep 17 00:00:00 2001 From: Hanjeong Lee Date: Mon, 31 Aug 2026 14:23:29 +0900 Subject: [PATCH] Report every generated `__text_signature__`, and keep it out of `__doc__` (#8614) * Strip the signature prefix from builtin __doc__ A function's signature and its documentation share one string, separated by `\n--\n\n`, as in CPython. __text_signature__ read the front of it, but __doc__ returned the whole thing: >>> len.__doc__ 'len(obj, /)\n--\n\nReturn the number of items in a container.' # was 'Return the number of items in a container.' # now 44 of the 46 builtin functions leaked the prefix. get_doc_from_internal_doc already stripped it but was reachable only from PyType.__doc__. Split it the way CPython does, into doc_without_signature (_PyType_DocWithoutSignature) and a wrapper mapping an empty result to None (_PyType_GetDocFromInternalDoc), and call it from PyNativeFunction and PyMethodDescriptor as well. Assisted-by: Claude Code:claude-opus-5 * Terminate a generated __text_signature__ that carries no documentation A signature and its documentation share one string, separated by `\n--\n\n`, and readers find the signature by searching for that marker. A #[pyfunction] with no doc comment stored the bare signature, which no reader could parse: >>> time.clock_getres.__doc__ 'clock_getres(clk_id, /)' >>> time.clock_getres.__text_signature__ None Emit the marker in that case too, as Argument Clinic does for an undocumented function. 136 of the 915 functions reachable from the importable modules were affected. __doc__ now reports None for them, matching CPython, because nothing follows the marker. Assisted-by: Claude Code:claude-opus-5 * Report a __text_signature__ for every method A #[pymethod] built its doc only when the method carried a doc comment, so one without a comment dropped the generated signature too: >>> list.append.__text_signature__ None Build it from either part, as #[pyfunction] does. All 1101 method descriptors reachable from the builtin types now report a signature, up from 1, and 713 of the 1030 that CPython also describes match it exactly. A method that takes its receiver as an ordinary first argument instead of `&self` reported that argument by name, so binding the method could not drop it: >>> inspect.signature([].__dir__) (obj, /) # was () # now Mark it $self, or $type for a classmethod, as CPython does. 310 methods were affected. test_unbound_builtin_method_noargs and test_bound_builtin_method_noargs pass now. Assisted-by: Claude Code:claude-opus-5 * Drop the expectedFailure on test_simple_completion rlcompleter closes the parenthesis for a callable whose signature takes no parameters, and leaves it open when the signature cannot be read: def _callable_postfix(self, val, word): if callable(val): word += "(" try: if not inspect.signature(val).parameters: word += ")" except ValueError: pass os.getpid reports `()` since the generated signature carries the `--` terminator, so completing `os.getpid` yields `os.getpid()` and the test passes. Assisted-by: Claude Code:claude-opus-5 * Name every #[pymethod] receiver zelf A method that needs an owned handle cannot take `&self`, so it takes the receiver as its first argument and names it `zelf`, `self` being a Rust keyword. 24 of the 163 such methods named it something else: `instance` (11), `obj` (4), `exc` (3), `_self` (3), `_instance` (2). Rename them. The generated signature is unaffected either way, since the macro marks the receiver by position rather than by name. Assisted-by: Claude Code:claude-opus-5 * Drop the unreachable zelf name check in func_sig The receiver is now marked by position, so the name it carries no longer matters, and no path reaches this check with an argument named zelf: - a #[pymethod] or #[pyclassmethod] without a `&self` receiver has its first argument replaced by the marker before the name is read - a `&self` receiver is handled as syn::FnArg::Receiver - a #[pymethod(raw)] is passed to static_raw_func, whose PyNativeFn bound fixes its signature to (&VirtualMachine, FuncArgs) - no #[pystaticmethod] or #[pyfunction] names an argument zelf - #[pymember], #[pygetset] and #[pyslot] never reach func_sig Every generated signature is byte for byte identical without it. Assisted-by: Claude Code:claude-opus-5 * Keep the argument bundle when marking the receiver A #[pyclassmethod] that takes FuncArgs receives the class inside the bundle along with every other argument, but the marker was spent on the bundle and the arguments went unreported: >>> object.__subclasshook__.__text_signature__ '($type, /)' # was, claims it takes nothing '($type, *args, **kwargs)' # now Report both, the shape CPython uses for __new__. Two methods take their arguments this way, __subclasshook__ on object and on type. Assisted-by: Claude Code:claude-opus-5 --- Lib/test/test_pydoc/test_pydoc.py | 2 - Lib/test/test_pyrepl/test_pyrepl.py | 1 - crates/derive-impl/src/pyclass.rs | 26 ++++++---- crates/derive-impl/src/pymodule.rs | 4 +- crates/derive-impl/src/util.rs | 24 +++++++--- crates/stdlib/src/ssl/error.rs | 6 +-- crates/vm/src/builtins/bool.rs | 4 +- crates/vm/src/builtins/builtin_func.rs | 4 +- crates/vm/src/builtins/descriptor.rs | 5 +- crates/vm/src/builtins/object.rs | 16 +++---- crates/vm/src/builtins/type.rs | 13 +++-- crates/vm/src/exceptions.rs | 18 +++---- crates/vm/src/stdlib/_io.rs | 58 +++++++++++------------ extra_tests/snippets/builtin_signature.py | 12 +++++ 14 files changed, 115 insertions(+), 78 deletions(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index fc6c830be..55825b6bb 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -1597,12 +1597,10 @@ class TestDescriptions(unittest.TestCase): self.assertEqual(self._get_summary_line(_stat.S_IMODE), "S_IMODE(object, /)") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_unbound_builtin_method_noargs(self): self.assertEqual(self._get_summary_line(str.lower), "lower(self, /) unbound builtins.str method") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_bound_builtin_method_noargs(self): self.assertEqual(self._get_summary_line(''.lower), "lower() method of builtins.str instance") diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 1bf3f9715..2d6d42d8c 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -858,7 +858,6 @@ class TestPyReplCompleter(TestCase): reader = ReadlineAlikeReader(console=console, config=config) return reader - @unittest.expectedFailure # TODO: RUSTPYTHON @patch("rlcompleter._readline_available", False) def test_simple_completion(self): events = code_to_events("os.getpid\t\n") diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index dd7973d35..2c7bc1efc 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -1077,16 +1077,25 @@ where } let raw = item_meta.raw()?; - let sig_doc = text_signature(func.sig(), &py_name); let has_receiver = func .sig() .inputs .iter() .any(|arg| matches!(arg, syn::FnArg::Receiver(_))); - let drop_first_typed = match self.inner.attr_name { - AttrName::Method | AttrName::ClassMethod if !has_receiver && !raw => 1, - _ => 0, + // Without a `&self` receiver the first argument is the one the call + // binds to, and CPython names it $self for a method and $type for a + // classmethod. + let implicit_self = if has_receiver || raw { + None + } else { + match self.inner.attr_name { + AttrName::Method => Some("$self"), + AttrName::ClassMethod => Some("$type"), + _ => None, + } }; + let drop_first_typed = usize::from(implicit_self.is_some()); + let sig_doc = text_signature(func.sig(), &py_name, implicit_self); let call_flags = infer_native_call_flags(func.sig(), drop_first_typed); // Add #[allow(non_snake_case)] for setter methods like set___name__ @@ -1096,10 +1105,11 @@ where args.attrs.push(allow_attr); } - let doc = args.attrs.doc().map(|doc| match &sig_doc { - Some(sig_doc) => format_doc(sig_doc, &doc), - None => doc, - }); + let doc = match (sig_doc, args.attrs.doc()) { + (Some(sig_doc), Some(doc)) => Some(format_doc(&sig_doc, &doc)), + (Some(sig_doc), None) => Some(format_doc(&sig_doc, "")), + (None, doc) => doc, + }; args.context.method_items.add_item(MethodNurseryItem { py_name, cfgs: args.cfgs.to_vec(), diff --git a/crates/derive-impl/src/pymodule.rs b/crates/derive-impl/src/pymodule.rs index d74f71356..5d3c7c3fa 100644 --- a/crates/derive-impl/src/pymodule.rs +++ b/crates/derive-impl/src/pymodule.rs @@ -664,7 +664,7 @@ 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 = text_signature(func.sig(), &py_name, None); let module = args.module_name(); // TODO: doc must exist at least one of code or CPython @@ -675,7 +675,7 @@ impl ModuleItem for FunctionItem { }); let doc = match (sig_doc, doc) { (Some(sig_doc), Some(doc)) => Some(format_doc(&sig_doc, &doc)), - (Some(sig_doc), None) => Some(sig_doc), + (Some(sig_doc), None) => Some(format_doc(&sig_doc, "")), (None, doc) => doc, }; diff --git a/crates/derive-impl/src/util.rs b/crates/derive-impl/src/util.rs index a8b4b6ff4..566a74e11 100644 --- a/crates/derive-impl/src/util.rs +++ b/crates/derive-impl/src/util.rs @@ -735,8 +735,12 @@ where // // Unlike CPython, a `#[pyfunction]` doesn't take the module as an argument yet, // so there's no module to mark with `$module`. -pub(crate) fn text_signature(sig: &Signature, name: &str) -> Option { - let signature = func_sig(sig)?; +pub(crate) fn text_signature( + sig: &Signature, + name: &str, + implicit_self: Option<&str>, +) -> Option { + let signature = func_sig(sig, implicit_self)?; // Arguments bind through `FuncArgs::take_positional`, which never consults // the keyword map, so they are positional-only. `*args`/`**kwargs` cannot be // followed by `/`, and an empty parameter list has nothing to mark. @@ -821,7 +825,10 @@ pub(crate) fn infer_native_call_flags(sig: &Signature, drop_first_typed: usize) /// Returns None when an argument has no name to report, in which case no /// signature can be generated for the function. -fn func_sig(sig: &Signature) -> Option { +/// +/// `implicit_self` is the marker to report for a first argument that the call +/// binds to without a `&self` receiver. +fn func_sig(sig: &Signature, mut implicit_self: Option<&str>) -> Option { let mut params = Vec::new(); for arg in &sig.inputs { let arg = match arg { @@ -834,12 +841,19 @@ fn func_sig(sig: &Signature) -> Option { let ty = arg.ty.as_ref(); let ty = quote!(#ty).to_string(); if ty == "FuncArgs" { + // The bundle carries the receiver along with everything else, so + // report both rather than spending the marker on it. + params.extend(implicit_self.take().map(str::to_owned)); params.push("*args, **kwargs".to_owned()); continue; } if ty.starts_with('&') && ty.ends_with("VirtualMachine") { continue; } + if let Some(marker) = implicit_self.take() { + params.push(marker.to_owned()); + continue; + } // An argument bound by a destructuring pattern, e.g. // `fn round(RoundArgs { number, ndigits }: RoundArgs, ..)`, has no name // to report. Stringifying the pattern would emit Rust syntax, which @@ -848,10 +862,6 @@ fn func_sig(sig: &Signature) -> Option { return None; }; let ident = pat.ident.to_string(); - if ident == "zelf" { - params.push("$self".to_owned()); - continue; - } if ident == "vm" { unreachable!("type &VirtualMachine(`{ty}`) must be filtered already"); } diff --git a/crates/stdlib/src/ssl/error.rs b/crates/stdlib/src/ssl/error.rs index 4e5def82b..8b8758708 100644 --- a/crates/stdlib/src/ssl/error.rs +++ b/crates/stdlib/src/ssl/error.rs @@ -42,17 +42,17 @@ pub(crate) mod ssl_error { impl PySSLError { // Returns strerror attribute if available, otherwise str(args) #[pymethod] - fn __str__(exc: &Py, vm: &VirtualMachine) -> PyResult { + fn __str__(zelf: &Py, vm: &VirtualMachine) -> PyResult { use crate::vm::AsObject; // Try to get strerror attribute first (OSError compatibility) - if let Ok(strerror) = exc.as_object().get_attr("strerror", vm) + if let Ok(strerror) = zelf.as_object().get_attr("strerror", vm) && !vm.is_none(&strerror) { return strerror.str(vm); } // Otherwise return str(args) - let args = exc.args(); + let args = zelf.args(); if args.len() == 1 { args.as_slice()[0].str(vm) } else { diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 1cfa8cc27..26e52eff7 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -106,8 +106,8 @@ impl Constructor for PyBool { #[pyclass(with(Constructor, AsNumber, Representable), flags(_MATCH_SELF))] impl PyBool { #[pymethod] - fn __format__(obj: PyObjectRef, spec: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { - let new_bool = obj.try_to_bool(vm)?; + fn __format__(zelf: PyObjectRef, spec: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { + let new_bool = zelf.try_to_bool(vm)?; FormatSpec::parse(spec.as_str()) .and_then(|format_spec| format_spec.format_bool(new_bool)) .map_err(|err| err.into_pyexception(vm)) diff --git a/crates/vm/src/builtins/builtin_func.rs b/crates/vm/src/builtins/builtin_func.rs index b34447b79..873bf065f 100644 --- a/crates/vm/src/builtins/builtin_func.rs +++ b/crates/vm/src/builtins/builtin_func.rs @@ -164,9 +164,11 @@ impl PyNativeFunction { Ok(qualname) } + // meth_get__doc__ in CPython #[pygetset] fn __doc__(zelf: NativeFunctionOrMethod) -> Option<&'static str> { - zelf.0.value.doc + let doc = zelf.0.value.doc?; + type_::get_doc_from_internal_doc(zelf.0.value.name, doc) } // meth_get__self__ in CPython diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index 50bdc841a..6fddb2128 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -134,8 +134,9 @@ impl PyMethodDescriptor { } #[pygetset] - const fn __doc__(&self) -> Option<&'static str> { - self.method.doc + fn __doc__(&self) -> Option<&'static str> { + let doc = self.method.doc?; + type_::get_doc_from_internal_doc(self.method.name, doc) } #[pygetset] diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index f69a54e77..303e36339 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -397,8 +397,8 @@ impl PyBaseObject { fn __init_subclass__(_cls: PyTypeRef) {} #[pymethod] - pub fn __dir__(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - obj.dir(vm) + pub fn __dir__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + zelf.dir(vm) } #[pymethod] @@ -468,22 +468,22 @@ impl PyBaseObject { } #[pymethod] - fn __reduce__(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - common_reduce(obj, 0, vm) + fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + common_reduce(zelf, 0, vm) } #[pymethod] - fn __reduce_ex__(obj: PyObjectRef, proto: usize, vm: &VirtualMachine) -> PyResult { + fn __reduce_ex__(zelf: PyObjectRef, proto: usize, vm: &VirtualMachine) -> PyResult { let __reduce__ = identifier!(vm, __reduce__); - if let Some(reduce) = vm.get_attribute_opt(obj.clone(), __reduce__)? { + if let Some(reduce) = vm.get_attribute_opt(zelf.clone(), __reduce__)? { let object_reduce = vm.ctx.types.object_type.get_attr(__reduce__).unwrap(); - let typ_obj: PyObjectRef = obj.class().to_owned().into(); + let typ_obj: PyObjectRef = zelf.class().to_owned().into(); let class_reduce = typ_obj.get_attr(__reduce__, vm)?; if !class_reduce.is(&object_reduce) { return reduce.call((), vm); } } - common_reduce(obj, proto, vm) + common_reduce(zelf, proto, vm) } #[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")] diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 9e5027946..28b15a9eb 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -2758,9 +2758,8 @@ pub(crate) fn get_text_signature_from_internal_doc<'a>( find_signature(name, internal_doc).and_then(get_signature) } -// _PyType_GetDocFromInternalDoc in CPython -fn get_doc_from_internal_doc<'a>(name: &str, internal_doc: &'a str) -> &'a str { - // Similar to CPython's _PyType_DocWithoutSignature +// _PyType_DocWithoutSignature in CPython +fn doc_without_signature<'a>(name: &str, internal_doc: &'a str) -> &'a str { // If the doc starts with the type name and a '(', it's a signature if let Some(doc_without_sig) = find_signature(name, internal_doc) { // Find where the signature ends @@ -2774,6 +2773,12 @@ fn get_doc_from_internal_doc<'a>(name: &str, internal_doc: &'a str) -> &'a str { internal_doc } +// _PyType_GetDocFromInternalDoc in CPython +pub(crate) fn get_doc_from_internal_doc<'a>(name: &str, internal_doc: &'a str) -> Option<&'a str> { + let doc = doc_without_signature(name, internal_doc); + (!doc.is_empty()).then_some(doc) +} + impl Initializer for PyType { type Args = FuncArgs; @@ -2862,7 +2867,7 @@ impl Py { { // Process internal doc, removing signature if present let doc_str = get_doc_from_internal_doc(&self.name(), internal_doc); - return Ok(vm.ctx.new_str(doc_str).into()); + return Ok(doc_str.map_or_else(|| vm.ctx.none(), |doc| vm.ctx.new_str(doc).into())); } // Check if there's a __doc__ in THIS type's dict only (not MRO) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index e5f296b02..18277eddc 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1916,11 +1916,11 @@ pub(super) mod types { #[pyexception(with(Initializer))] impl PyImportError { #[pymethod] - fn __reduce__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyTupleRef { - let obj = exc.as_object().to_owned(); - let args: PyObjectRef = match exc.get_arg(0) { + fn __reduce__(zelf: PyBaseExceptionRef, vm: &VirtualMachine) -> PyTupleRef { + let obj = zelf.as_object().to_owned(); + let args: PyObjectRef = match zelf.get_arg(0) { Some(arg) => vm.new_tuple((arg,)).into(), - None => exc.args().into(), + None => zelf.args().into(), }; let mut result: Vec = vec![obj.class().to_owned().into(), args]; @@ -2360,15 +2360,15 @@ pub(super) mod types { } #[pymethod] - fn __reduce__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyTupleRef { - let args = exc.args(); - let obj = exc.as_object().to_owned(); + fn __reduce__(zelf: PyBaseExceptionRef, vm: &VirtualMachine) -> PyTupleRef { + let args = zelf.args(); + let obj = zelf.as_object().to_owned(); let mut result: Vec = vec![obj.class().to_owned().into()]; if args.len() >= 2 && args.len() <= 5 { // SAFETY: len() == 2 is checked so get_arg 1 or 2 won't panic - let errno = exc.get_arg(0).unwrap(); - let msg = exc.get_arg(1).unwrap(); + let errno = zelf.get_arg(0).unwrap(); + let msg = zelf.get_arg(1).unwrap(); if let Ok(filename) = obj.get_attr("filename", vm) { if !vm.is_none(&filename) { diff --git a/crates/vm/src/stdlib/_io.rs b/crates/vm/src/stdlib/_io.rs index 08dd0e0ef..a531d5a12 100644 --- a/crates/vm/src/stdlib/_io.rs +++ b/crates/vm/src/stdlib/_io.rs @@ -450,41 +450,41 @@ mod _io { } #[pymethod] - fn __enter__(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - check_closed(&instance, vm)?; - Ok(instance) + fn __enter__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + check_closed(&zelf, vm)?; + Ok(zelf) } #[pymethod] - fn __exit__(instance: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - vm.call_method(&instance, "close", ())?; + fn __exit__(zelf: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + vm.call_method(&zelf, "close", ())?; Ok(()) } #[pymethod] - fn flush(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + fn flush(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { // just check if this is closed; if it isn't, do nothing - check_closed(&instance, vm) + check_closed(&zelf, vm) } #[pymethod] - fn seekable(_self: PyObjectRef) -> bool { + fn seekable(_zelf: PyObjectRef) -> bool { false } #[pymethod] - fn readable(_self: PyObjectRef) -> bool { + fn readable(_zelf: PyObjectRef) -> bool { false } #[pymethod] - fn writable(_self: PyObjectRef) -> bool { + fn writable(_zelf: PyObjectRef) -> bool { false } #[pymethod] - fn isatty(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult { - check_closed(&instance, vm)?; + fn isatty(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + check_closed(&zelf, vm)?; Ok(false) } @@ -494,8 +494,8 @@ mod _io { } #[pymethod] - fn close(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - iobase_close(&instance, vm) + fn close(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + iobase_close(&zelf, vm) } #[pymethod] @@ -560,23 +560,23 @@ mod _io { } #[pymethod(name = "_checkClosed")] - fn check_closed(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - check_closed(&instance, vm) + fn check_closed(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + check_closed(&zelf, vm) } #[pymethod(name = "_checkReadable")] - fn check_readable(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - check_readable(&instance, vm) + fn check_readable(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + check_readable(&zelf, vm) } #[pymethod(name = "_checkWritable")] - fn check_writable(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - check_writable(&instance, vm) + fn check_writable(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + check_writable(&zelf, vm) } #[pymethod(name = "_checkSeekable")] - fn check_seekable(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - check_seekable(&instance, vm) + fn check_seekable(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + check_seekable(&zelf, vm) } } @@ -641,12 +641,12 @@ mod _io { #[pyclass(flags(BASETYPE, HAS_DICT, HAS_WEAKREF))] impl _RawIOBase { #[pymethod] - fn read(instance: PyObjectRef, size: OptionalSize, vm: &VirtualMachine) -> PyResult { + fn read(zelf: PyObjectRef, size: OptionalSize, vm: &VirtualMachine) -> PyResult { if let Some(size) = size.to_usize() { let b = PyByteArray::from(vm.new_zeroed_bytes(size)?).into_ref(&vm.ctx); let n = >::try_from_object( vm, - vm.call_method(&instance, "readinto", (b.clone(),))?, + vm.call_method(&zelf, "readinto", (b.clone(),))?, )?; Ok(match n { None => vm.ctx.none(), @@ -665,18 +665,18 @@ mod _io { } }) } else { - vm.call_method(&instance, "readall", ()) + vm.call_method(&zelf, "readall", ()) } } #[pymethod] - fn readall(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult>> { + fn readall(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult>> { let mut chunks = Vec::new(); let mut total_len = 0; loop { // Loop with EINTR handling (PEP 475) let data = loop { - let res = vm.call_method(&instance, "read", (DEFAULT_BUFFER_SIZE,)); + let res = vm.call_method(&zelf, "read", (DEFAULT_BUFFER_SIZE,)); match trap_eintr(res, vm)? { Some(val) => break val, None => continue, @@ -707,12 +707,12 @@ mod _io { } #[pymethod] - fn readinto(_instance: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { + fn readinto(_zelf: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { Err(vm.new_not_implemented_error(String::new())) } #[pymethod] - fn write(_instance: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { + fn write(_zelf: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { Err(vm.new_not_implemented_error(String::new())) } } diff --git a/extra_tests/snippets/builtin_signature.py b/extra_tests/snippets/builtin_signature.py index 320a395d8..88857ac4b 100644 --- a/extra_tests/snippets/builtin_signature.py +++ b/extra_tests/snippets/builtin_signature.py @@ -1,4 +1,5 @@ import inspect +import os import sys # __text_signature__ is generated from the Rust parameter list, so it must not @@ -47,6 +48,17 @@ assert str(inspect.signature(isinstance)) == "(obj, class_or_tuple, /)" assert str(inspect.signature(issubclass)) == "(cls, class_or_tuple, /)" assert str(inspect.signature(aiter)) == "(async_iterable, /)" +# A generated signature is stored in __doc__ and read back out of it, so it +# needs the `--` terminator even when the function has no documentation. +assert str(inspect.signature(os.getpid)) == "()" +assert str(inspect.signature(os.getcwd)) == "()" + +# Methods report a signature too, with the receiver marked so that binding a +# method drops it. +assert str(inspect.signature(list.__dir__)) == "(self, /)" +assert str(inspect.signature([].__dir__)) == "()" +assert str(inspect.signature(float.fromhex)) == "(string, /)" + 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