Skip to content

Commit 98fff96

Browse files
authored
__doc__ handing in the right way (RustPython#6390)
1 parent 90717e5 commit 98fff96

File tree

5 files changed

+26
-26
lines changed

5 files changed

+26
-26
lines changed

Lib/test/test_property.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,6 @@ def documented_getter():
342342
with self.assertRaises(AttributeError):
343343
p = slotted_prop(documented_getter)
344344

345-
@unittest.expectedFailure # TODO: RUSTPYTHON
346345
@unittest.skipIf(sys.flags.optimize >= 2,
347346
"Docstrings are omitted with -O2 and above")
348347
def test_property_with_slots_and_doc_slot_docstring_present(self):

Lib/test/test_typing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5269,7 +5269,7 @@ def test_weakref_all(self):
52695269
for t in things:
52705270
self.assertEqual(weakref.ref(t)(), t)
52715271

5272-
@unittest.expectedFailure # TODO: RUSTPYTHON
5272+
@unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes
52735273
def test_parameterized_slots(self):
52745274
T = TypeVar('T')
52755275
class C(Generic[T]):
@@ -5289,7 +5289,7 @@ def foo(x: C['C']): ...
52895289
self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C])
52905290
self.assertEqual(copy(C[int]), deepcopy(C[int]))
52915291

5292-
@unittest.expectedFailure # TODO: RUSTPYTHON
5292+
@unittest.expectedFailure # TODO: RUSTPYTHON - __slots__ with Generic doesn't prevent new attributes
52935293
def test_parameterized_slots_dict(self):
52945294
T = TypeVar('T')
52955295
class D(Generic[T]):

crates/codegen/src/compile.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2679,10 +2679,12 @@ impl Compiler {
26792679
let qualname_name = self.name("__qualname__");
26802680
emit!(self, Instruction::StoreLocal(qualname_name));
26812681

2682-
// Store __doc__
2683-
self.load_docstring(doc_str);
2684-
let doc = self.name("__doc__");
2685-
emit!(self, Instruction::StoreLocal(doc));
2682+
// Store __doc__ only if there's an explicit docstring
2683+
if let Some(doc) = doc_str {
2684+
self.emit_load_const(ConstantData::Str { value: doc.into() });
2685+
let doc_name = self.name("__doc__");
2686+
emit!(self, Instruction::StoreLocal(doc_name));
2687+
}
26862688

26872689
// Store __firstlineno__ (new in Python 3.12+)
26882690
self.emit_load_const(ConstantData::Integer {
@@ -2876,17 +2878,6 @@ impl Compiler {
28762878
self.store_name(name)
28772879
}
28782880

2879-
fn load_docstring(&mut self, doc_str: Option<String>) {
2880-
// TODO: __doc__ must be default None and no bytecode unless it is Some
2881-
// Duplicate top of stack (the function or class object)
2882-
2883-
// Doc string value:
2884-
self.emit_load_const(match doc_str {
2885-
Some(doc) => ConstantData::Str { value: doc.into() },
2886-
None => ConstantData::None, // set docstring None if not declared
2887-
});
2888-
}
2889-
28902881
fn compile_while(&mut self, test: &Expr, body: &[Stmt], orelse: &[Stmt]) -> CompileResult<()> {
28912882
let while_block = self.new_block();
28922883
let else_block = self.new_block();

crates/vm/src/builtins/type.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,11 +1198,10 @@ impl Constructor for PyType {
11981198
member: member_def,
11991199
});
12001200

1201-
let attr_name = vm.ctx.intern_str(member.to_string());
1202-
if !typ.has_attr(attr_name) {
1203-
typ.set_attr(attr_name, member_descriptor.into());
1204-
}
1205-
1201+
let attr_name = vm.ctx.intern_str(member.as_str());
1202+
// __slots__ attributes always get a member descriptor
1203+
// (this overrides any inherited attribute from MRO)
1204+
typ.set_attr(attr_name, member_descriptor.into());
12061205
offset += 1;
12071206
}
12081207
}
@@ -1236,6 +1235,16 @@ impl Constructor for PyType {
12361235
}
12371236
}
12381237

1238+
// Set __doc__ to None if not already present in the type's dict
1239+
// This matches CPython's behavior in type_dict_set_doc (typeobject.c)
1240+
// which ensures every type has a __doc__ entry in its dict
1241+
{
1242+
let __doc__ = identifier!(vm, __doc__);
1243+
if !typ.attributes.read().contains_key(&__doc__) {
1244+
typ.attributes.write().insert(__doc__, vm.ctx.none());
1245+
}
1246+
}
1247+
12391248
// avoid deadlock
12401249
let attributes = typ
12411250
.attributes
@@ -1377,8 +1386,9 @@ impl Py<PyType> {
13771386
return Ok(vm.ctx.new_str(doc_str).into());
13781387
}
13791388

1380-
// Check if there's a __doc__ in the type's dict
1381-
if let Some(doc_attr) = self.get_attr(vm.ctx.intern_str("__doc__")) {
1389+
// Check if there's a __doc__ in THIS type's dict only (not MRO)
1390+
// CPython returns None if __doc__ is not in the type's own dict
1391+
if let Some(doc_attr) = self.get_direct_attr(vm.ctx.intern_str("__doc__")) {
13821392
// If it's a descriptor, call its __get__ method
13831393
let descr_get = doc_attr
13841394
.class()

extra_tests/snippets/syntax_class.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def b():
186186
class A:
187187
pass
188188

189-
assert A.__doc__ == None
189+
assert A.__doc__ is None, A.__doc__
190190

191191
class B:
192192
"Docstring"

0 commit comments

Comments
 (0)