diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index 898111a769..6f6e1317dc 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -357,8 +357,6 @@ def test_delete___dict__(self): else: self.fail("deleting function dictionary should raise TypeError") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_unassigned_dict(self): self.assertEqual(self.b.__dict__, {}) @@ -379,8 +377,6 @@ def test_set_docstring_attr(self): self.assertEqual(self.fi.a.__doc__, docstr) self.cannot_set_attr(self.fi.a, "__doc__", docstr, AttributeError) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_delete_docstring(self): self.b.__doc__ = "The docstring" del self.b.__doc__ diff --git a/extra_tests/snippets/syntax_function2.py b/extra_tests/snippets/syntax_function2.py index dce4cb54eb..d0901af6a1 100644 --- a/extra_tests/snippets/syntax_function2.py +++ b/extra_tests/snippets/syntax_function2.py @@ -52,6 +52,8 @@ def f4(): assert f4.__doc__ == "test4" +assert type(lambda: None).__doc__.startswith("Create a function object."), type(f4).__doc__ + def revdocstr(f): d = f.__doc__ diff --git a/vm/src/builtins/descriptor.rs b/vm/src/builtins/descriptor.rs index 3c294db096..0346a998b7 100644 --- a/vm/src/builtins/descriptor.rs +++ b/vm/src/builtins/descriptor.rs @@ -345,15 +345,28 @@ impl GetDescriptor for PyMemberDescriptor { fn descr_get( zelf: PyObjectRef, obj: Option, - _cls: Option, + cls: Option, vm: &VirtualMachine, ) -> PyResult { + let descr = Self::_as_pyref(&zelf, vm)?; match obj { - Some(x) => { - let zelf = Self::_as_pyref(&zelf, vm)?; - zelf.member.get(x, vm) + Some(x) => descr.member.get(x, vm), + None => { + // When accessed from class (not instance), for __doc__ member descriptor, + // return the class's docstring if available + // When accessed from class (not instance), check if the class has + // an attribute with the same name as this member descriptor + if let Some(cls) = cls { + if let Ok(cls_type) = cls.downcast::() { + if let Some(interned) = vm.ctx.interned_str(descr.member.name.as_str()) { + if let Some(attr) = cls_type.attributes.read().get(&interned) { + return Ok(attr.clone()); + } + } + } + } + Ok(zelf) } - None => Ok(zelf), } } } diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index 33188ce39e..05ffafd1d0 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -417,10 +417,15 @@ impl PyFunction { } #[pymember(magic)] - fn doc(_vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult { - let zelf: PyRef = zelf.downcast().unwrap_or_else(|_| unreachable!()); - let doc = zelf.doc.lock(); - Ok(doc.clone()) + fn doc(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + // When accessed from instance, obj is the PyFunction instance + if let Ok(func) = obj.downcast::() { + let doc = func.doc.lock(); + Ok(doc.clone()) + } else { + // When accessed from class, return None as there's no instance + Ok(vm.ctx.none()) + } } #[pymember(magic, setter)] diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index 7351797dec..490134829e 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -1061,6 +1061,22 @@ 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 + // 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 + if let Some(sig_end_pos) = doc_without_sig.find(SIGNATURE_END_MARKER) { + let after_sig = &doc_without_sig[sig_end_pos + SIGNATURE_END_MARKER.len()..]; + // Return the documentation after the signature, or empty string if none + return after_sig; + } + } + // If no signature found, return the whole doc + internal_doc +} + impl GetAttr for PyType { fn getattro(zelf: &Py, name_str: &Py, vm: &VirtualMachine) -> PyResult { #[cold] @@ -1122,6 +1138,55 @@ impl Py { PyTuple::new_unchecked(elements.into_boxed_slice()) } + #[pygetset(magic)] + fn doc(&self, vm: &VirtualMachine) -> PyResult { + // Similar to CPython's type_get_doc + // For non-heap types (static types), check if there's an internal doc + if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) { + if let Some(internal_doc) = self.slots.doc { + // 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()); + } + } + + // Check if there's a __doc__ in the type's dict + if let Some(doc_attr) = self.get_attr(vm.ctx.intern_str("__doc__")) { + // If it's a descriptor, call its __get__ method + let descr_get = doc_attr + .class() + .mro_find_map(|cls| cls.slots.descr_get.load()); + if let Some(descr_get) = descr_get { + descr_get(doc_attr, None, Some(self.to_owned().into()), vm) + } else { + Ok(doc_attr) + } + } else { + Ok(vm.ctx.none()) + } + } + + #[pygetset(magic, setter)] + fn set_doc(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> { + // Similar to CPython's type_set_doc + let value = value.ok_or_else(|| { + vm.new_type_error(format!( + "cannot delete '__doc__' attribute of type '{}'", + self.name() + )) + })?; + + // Check if we can set this special type attribute + self.check_set_special_type_attr(&value, identifier!(vm, __doc__), vm)?; + + // Set the __doc__ in the type's dict + self.attributes + .write() + .insert(identifier!(vm, __doc__), value); + + Ok(()) + } + #[pymethod(magic)] fn dir(&self) -> PyList { let attributes: Vec = self diff --git a/vm/src/class.rs b/vm/src/class.rs index bc38d6bd61..d3595f980c 100644 --- a/vm/src/class.rs +++ b/vm/src/class.rs @@ -96,7 +96,12 @@ pub trait PyClassImpl: PyClassDef { } Self::impl_extend_class(ctx, class); if let Some(doc) = Self::DOC { - class.set_attr(identifier!(ctx, __doc__), ctx.new_str(doc).into()); + // Only set __doc__ if it doesn't already exist (e.g., as a member descriptor) + // This matches CPython's behavior in type_dict_set_doc + let doc_attr_name = identifier!(ctx, __doc__); + if class.attributes.read().get(doc_attr_name).is_none() { + class.set_attr(doc_attr_name, ctx.new_str(doc).into()); + } } if let Some(module_name) = Self::MODULE_NAME { class.set_attr(