1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use std::{fmt::Write, mem};
use super::{Dict, LazyHeapSet, PyTrait, Type};
use crate::{
args::ArgValues,
bytecode::{CallResult, VM},
defer_drop,
exception_private::{ExcType, RunResult},
hash::{HashValue, identity_hash},
heap::{BorrowedHeapReadMut, DropWithHeap, HeapId, HeapItem, HeapRead, heap_read_ref_as_field_mut},
resource::ResourceTracker,
types::str::allocate_string,
value::{EitherStr, Value},
};
/// A user-defined class object created by a `class Foo: ...` statement.
///
/// Holds the class name and a `namespace` [`Dict`] mapping member names to values:
/// methods (stored as `DefFunction`/`Closure` values) and class variables. The
/// class's own [`HeapId`] is its type identity — `type(x) is Foo` and `isinstance`
/// work via reference identity, so there is no separate type-id counter.
///
/// Calling a class (`Foo(...)`) constructs an [`Instance`](super::Instance); see
/// `instantiate_class` in the VM's call module. Inheritance is not yet supported,
/// but a future `bases: Vec<HeapId>` field would slot in here without disturbing
/// the rest of the design.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct Class {
/// Class name (e.g. `Foo`), used for `repr` and `__name__`. Interned for
/// compiled `class` statements; heap-owned for classes created at runtime
/// via the 3-arg `type(name, bases, dict)` form, whose name cannot be
/// interned because the intern table is frozen after prepare.
name: EitherStr,
/// Members: method name / class-variable name -> value.
namespace: Dict,
}
impl Class {
/// Creates a new class object from its name and member namespace.
#[must_use]
pub fn new(name: EitherStr, namespace: Dict) -> Self {
Self { name, namespace }
}
/// Returns the class name (interned or heap-owned).
#[must_use]
pub fn name(&self) -> &EitherStr {
&self.name
}
/// Returns a reference to the class member namespace.
#[must_use]
pub fn namespace(&self) -> &Dict {
&self.namespace
}
}
impl<'h> HeapRead<'h, Class> {
fn namespace_mut(&mut self) -> BorrowedHeapReadMut<'_, 'h, Dict> {
heap_read_ref_as_field_mut!(self, Class, namespace)
}
/// Sets a class attribute (`Foo.x = 1`), returning the previous value (if any)
/// for the caller to drop. Takes ownership of both `name` and `value`.
///
/// Existing instances observe the change immediately: instance attribute reads
/// fall through to this namespace.
pub fn set_attr(
&mut self,
name: Value,
value: Value,
vm: &mut VM<'h, impl ResourceTracker>,
) -> RunResult<Option<Value>> {
self.namespace_mut().set(name, value, vm)
}
}
impl<'h> PyTrait<'h> for HeapRead<'h, Class> {
fn py_type(&self, _vm: &VM<'h, impl ResourceTracker>) -> Type {
// The type of a class object is `type` (matching `type(Foo) is type`).
Type::Type
}
fn py_len(&self, _vm: &VM<'h, impl ResourceTracker>) -> Option<usize> {
None
}
fn py_eq_impl(&self, _other: &Value, _vm: &mut VM<'h, impl ResourceTracker>) -> RunResult<Option<bool>> {
// Classes compare by identity, which `Value::py_eq_impl` resolves before
// ever reaching here; from this side every class is `NotImplemented`.
Ok(None)
}
fn py_hash(&self, self_id: HeapId, _vm: &mut VM<'h, impl ResourceTracker>) -> RunResult<Option<HashValue>> {
// Class objects hash by identity (like CPython type objects).
Ok(Some(identity_hash(self_id)))
}
fn py_repr_fmt(
&self,
f: &mut impl Write,
vm: &mut VM<'h, impl ResourceTracker>,
_heap_ids: &mut LazyHeapSet,
) -> RunResult<()> {
Ok(write!(f, "<class '{}'>", self.get(vm.heap).name.as_str(vm.interns))?)
}
fn py_getattr(&self, attr: &EitherStr, vm: &mut VM<'h, impl ResourceTracker>) -> RunResult<Option<CallResult>> {
let attr_str = attr.as_str(vm.interns);
// `Foo.__name__` returns the class name — before the namespace lookup
// because in CPython `type.__name__` is a metaclass data descriptor that
// shadows a same-named class-dict member (`class Foo: __name__ = 'bar'`
// still reads `'Foo'`; only instances see the member).
if attr_str == "__name__" {
let name = self.get(vm.heap).name.as_str(vm.interns).to_owned();
return Ok(Some(CallResult::Value(allocate_string(name, vm.heap)?)));
}
// Otherwise look up a member (method or class variable) in the namespace.
match self.get(vm.heap).namespace.get_by_str(attr_str, vm.heap, vm.interns) {
Some(value) => Ok(Some(CallResult::Value(value.clone_with_heap(vm.heap)))),
None => Err(ExcType::attribute_error_type(
self.get(vm.heap).name.as_str(vm.interns),
attr_str,
)),
}
}
fn py_call_attr(
&mut self,
_self_id: HeapId,
vm: &mut VM<'h, impl ResourceTracker>,
attr: &EitherStr,
args: ArgValues,
) -> RunResult<CallResult> {
let attr_str = attr.as_str(vm.interns);
// `__name__` is a synthesized string, not a namespace member (see
// `py_getattr`), so calling it goes through the normal callable
// dispatch and raises CPython's `TypeError: 'str' object is not
// callable` rather than a spurious `AttributeError`.
if attr_str == "__name__" {
let name = self.get(vm.heap).name.as_str(vm.interns).to_owned();
let name_val = match allocate_string(name, vm.heap) {
Ok(v) => v,
Err(e) => {
args.drop_with_heap(vm);
return Err(e.into());
}
};
defer_drop!(name_val, vm);
return vm.call_function(name_val, args);
}
// `Foo.method(args)` calls the raw (unbound) member with the given args —
// no `self` is inserted, the caller passes the instance explicitly.
let member = self
.get(vm.heap)
.namespace
.get_by_str(attr_str, vm.heap, vm.interns)
.map(|v| v.clone_with_heap(vm.heap));
if let Some(member) = member {
defer_drop!(member, vm);
vm.call_function(member, args)
} else {
args.drop_with_heap(vm);
Err(ExcType::attribute_error_type(
self.get(vm.heap).name.as_str(vm.interns),
attr_str,
))
}
}
}
impl HeapItem for Class {
fn py_estimate_size(&self) -> usize {
mem::size_of::<Self>() + self.name.py_estimate_size() + self.namespace.py_estimate_size()
}
fn py_dec_ref_ids(&mut self, stack: &mut Vec<HeapId>) {
self.namespace.py_dec_ref_ids(stack);
}
}