From 7670655853a269a4ef6c71009054ef699099ee5a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 10 Dec 2025 11:22:05 +0900 Subject: [PATCH 1/2] __doc__ handing in the right way --- Lib/test/test_typing.py | 4 ++-- crates/codegen/src/compile.rs | 21 ++++++--------------- crates/vm/src/builtins/type.rs | 14 +++++++------- extra_tests/snippets/syntax_class.py | 2 +- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 3f268c62384..4dd3cd8b78a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5269,7 +5269,7 @@ def test_weakref_all(self): for t in things: self.assertEqual(weakref.ref(t)(), t) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes def test_parameterized_slots(self): T = TypeVar('T') class C(Generic[T]): @@ -5289,7 +5289,7 @@ def foo(x: C['C']): ... self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C]) self.assertEqual(copy(C[int]), deepcopy(C[int])) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes def test_parameterized_slots_dict(self): T = TypeVar('T') class D(Generic[T]): diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 5e2c7b81b33..3e7640cc1e0 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -2679,10 +2679,12 @@ impl Compiler { let qualname_name = self.name("__qualname__"); emit!(self, Instruction::StoreLocal(qualname_name)); - // Store __doc__ - self.load_docstring(doc_str); - let doc = self.name("__doc__"); - emit!(self, Instruction::StoreLocal(doc)); + // Store __doc__ only if there's an explicit docstring + if let Some(doc) = doc_str { + self.emit_load_const(ConstantData::Str { value: doc.into() }); + let doc_name = self.name("__doc__"); + emit!(self, Instruction::StoreLocal(doc_name)); + } // Store __firstlineno__ (new in Python 3.12+) self.emit_load_const(ConstantData::Integer { @@ -2876,17 +2878,6 @@ impl Compiler { self.store_name(name) } - fn load_docstring(&mut self, doc_str: Option) { - // TODO: __doc__ must be default None and no bytecode unless it is Some - // Duplicate top of stack (the function or class object) - - // Doc string value: - self.emit_load_const(match doc_str { - Some(doc) => ConstantData::Str { value: doc.into() }, - None => ConstantData::None, // set docstring None if not declared - }); - } - fn compile_while(&mut self, test: &Expr, body: &[Stmt], orelse: &[Stmt]) -> CompileResult<()> { let while_block = self.new_block(); let else_block = self.new_block(); diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 849c03bc78d..8179e45fc6b 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1198,11 +1198,10 @@ impl Constructor for PyType { member: member_def, }); - let attr_name = vm.ctx.intern_str(member.to_string()); - if !typ.has_attr(attr_name) { - typ.set_attr(attr_name, member_descriptor.into()); - } - + let attr_name = vm.ctx.intern_str(member.as_str()); + // __slots__ attributes always get a member descriptor + // (this overrides any inherited attribute from MRO) + typ.set_attr(attr_name, member_descriptor.into()); offset += 1; } } @@ -1377,8 +1376,9 @@ impl Py { 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__")) { + // Check if there's a __doc__ in THIS type's dict only (not MRO) + // CPython returns None if __doc__ is not in the type's own dict + if let Some(doc_attr) = self.get_direct_attr(vm.ctx.intern_str("__doc__")) { // If it's a descriptor, call its __get__ method let descr_get = doc_attr .class() diff --git a/extra_tests/snippets/syntax_class.py b/extra_tests/snippets/syntax_class.py index fb702f33044..4e80e7edf8c 100644 --- a/extra_tests/snippets/syntax_class.py +++ b/extra_tests/snippets/syntax_class.py @@ -186,7 +186,7 @@ def b(): class A: pass -assert A.__doc__ == None +assert A.__doc__ is None, A.__doc__ class B: "Docstring" From 32a51b142cae4bb183c0e00f0fdcad36fb7f3e74 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 10 Dec 2025 16:03:46 +0900 Subject: [PATCH 2/2] fixit --- Lib/test/test_property.py | 1 - crates/vm/src/builtins/type.rs | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 49dc2331e94..cea241b0f20 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -342,7 +342,6 @@ def documented_getter(): with self.assertRaises(AttributeError): p = slotted_prop(documented_getter) - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_with_slots_and_doc_slot_docstring_present(self): diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 8179e45fc6b..883be4f8bfc 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1235,6 +1235,16 @@ impl Constructor for PyType { } } + // Set __doc__ to None if not already present in the type's dict + // This matches CPython's behavior in type_dict_set_doc (typeobject.c) + // which ensures every type has a __doc__ entry in its dict + { + let __doc__ = identifier!(vm, __doc__); + if !typ.attributes.read().contains_key(&__doc__) { + typ.attributes.write().insert(__doc__, vm.ctx.none()); + } + } + // avoid deadlock let attributes = typ .attributes