Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 0 additions & 2 deletions Lib/test/test_pydoc/test_pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1597,12 +1597,10 @@ def test_module_level_callable_o(self):
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")
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,6 @@ def prepare_reader(self, events, namespace):
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")
Expand Down
26 changes: 18 additions & 8 deletions crates/derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand All @@ -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(),
Expand Down
4 changes: 2 additions & 2 deletions crates/derive-impl/src/pymodule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
};

Expand Down
24 changes: 17 additions & 7 deletions crates/derive-impl/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
let signature = func_sig(sig)?;
pub(crate) fn text_signature(
sig: &Signature,
name: &str,
implicit_self: Option<&str>,
) -> Option<String> {
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.
Expand Down Expand Up @@ -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<String> {
///
/// `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<String> {
let mut params = Vec::new();
for arg in &sig.inputs {
let arg = match arg {
Expand All @@ -834,12 +841,19 @@ fn func_sig(sig: &Signature) -> Option<String> {
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
Expand All @@ -848,10 +862,6 @@ fn func_sig(sig: &Signature) -> Option<String> {
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");
}
Expand Down
6 changes: 3 additions & 3 deletions crates/stdlib/src/ssl/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ pub(crate) mod ssl_error {
impl PySSLError {
// Returns strerror attribute if available, otherwise str(args)
#[pymethod]
fn __str__(exc: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
fn __str__(zelf: &Py<PyBaseException>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
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 {
Expand Down
4 changes: 2 additions & 2 deletions crates/vm/src/builtins/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
let new_bool = obj.try_to_bool(vm)?;
fn __format__(zelf: PyObjectRef, spec: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<String> {
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))
Expand Down
4 changes: 3 additions & 1 deletion crates/vm/src/builtins/builtin_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions crates/vm/src/builtins/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
16 changes: 8 additions & 8 deletions crates/vm/src/builtins/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,8 @@ impl PyBaseObject {
fn __init_subclass__(_cls: PyTypeRef) {}

#[pymethod]
pub fn __dir__(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyList> {
obj.dir(vm)
pub fn __dir__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyList> {
zelf.dir(vm)
}

#[pymethod]
Expand Down Expand Up @@ -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")]
Expand Down
13 changes: 9 additions & 4 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2747,9 +2747,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
Expand All @@ -2763,6 +2762,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;

Expand Down Expand Up @@ -2851,7 +2856,7 @@ impl Py<PyType> {
{
// 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)
Expand Down
18 changes: 9 additions & 9 deletions crates/vm/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PyObjectRef> = vec![obj.class().to_owned().into(), args];

Expand Down Expand Up @@ -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<PyObjectRef> = 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) {
Expand Down
Loading
Loading