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
1 change: 0 additions & 1 deletion Lib/test/test_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]):
Expand All @@ -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]):
Expand Down
21 changes: 6 additions & 15 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -2876,17 +2878,6 @@ impl Compiler {
self.store_name(name)
}

fn load_docstring(&mut self, doc_str: Option<String>) {
// 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();
Expand Down
24 changes: 17 additions & 7 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -1236,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
Expand Down Expand Up @@ -1377,8 +1386,9 @@ impl Py<PyType> {
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()
Expand Down
2 changes: 1 addition & 1 deletion extra_tests/snippets/syntax_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def b():
class A:
pass

assert A.__doc__ == None
assert A.__doc__ is None, A.__doc__

class B:
"Docstring"
Expand Down
Loading