Skip to main content

rustpython_vm/builtins/
super.rs

1// spell-checker:ignore cmeth
2/*! Python `super` class.
3
4See also [CPython source code.](https://github.com/python/cpython/blob/50b48572d9a90c5bb36e2bef6179548ea927a35a/Objects/typeobject.c#L7663)
5*/
6
7use super::{PyStr, PyType, PyTypeRef};
8use crate::{
9    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
10    builtins::function::PyCell,
11    class::PyClassImpl,
12    function::{FuncArgs, IntoFuncArgs, OptionalArg},
13    object::PyAtomicRef,
14    types::{Callable, Constructor, GetAttr, GetDescriptor, Initializer, Representable},
15};
16
17#[pyclass(module = false, name = "super", traverse)]
18#[derive(Debug)]
19pub struct PySuper {
20    #[pymember(name = "__thisclass__")]
21    typ: PyAtomicRef<Option<PyType>>,
22    #[pymember(name = "__self__")]
23    obj: PyAtomicRef<Option<PyObject>>,
24    #[pymember(name = "__self_class__")]
25    obj_type: PyAtomicRef<Option<PyType>>,
26}
27
28fn bind_super(
29    typ: PyTypeRef,
30    obj: PyObjectRef,
31    vm: &VirtualMachine,
32) -> PyResult<(PyTypeRef, Option<PyObjectRef>, Option<PyTypeRef>)> {
33    if vm.is_none(&obj) {
34        return Ok((typ, None, None));
35    }
36    let obj_type = super_check(&typ, &obj, vm)?;
37    Ok((typ, Some(obj), Some(obj_type)))
38}
39
40impl PySuper {
41    fn empty() -> Self {
42        Self {
43            typ: PyAtomicRef::from(None),
44            obj: PyAtomicRef::from(None),
45            obj_type: PyAtomicRef::from(None),
46        }
47    }
48
49    fn store_bound(
50        &self,
51        typ: Option<PyTypeRef>,
52        obj: Option<PyObjectRef>,
53        obj_type: Option<PyTypeRef>,
54    ) {
55        drop(self.typ.store(typ));
56        drop(self.obj.store(obj));
57        drop(self.obj_type.store(obj_type));
58    }
59}
60
61impl PyPayload for PySuper {
62    #[inline]
63    fn class(ctx: &Context) -> &'static Py<PyType> {
64        ctx.types.super_type
65    }
66}
67
68impl Constructor for PySuper {
69    type Args = FuncArgs;
70
71    fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
72        // `tp_new` leaves every field null. `tp_init` fills them in.
73        Ok(Self::empty())
74    }
75}
76
77#[derive(FromArgs)]
78pub struct InitArgs {
79    #[pyarg(
80        positional,
81        optional,
82        name = "type",
83        error_msg = "super() argument 1 must be a type"
84    )]
85    py_type: OptionalArg<PyTypeRef>,
86    #[pyarg(positional, optional)]
87    object: OptionalArg<PyObjectRef>,
88}
89
90impl Initializer for PySuper {
91    type Args = InitArgs;
92
93    fn init(
94        zelf: &Py<Self>,
95        Self::Args { py_type, object }: Self::Args,
96        vm: &VirtualMachine,
97    ) -> PyResult<()> {
98        let typ = match py_type {
99            OptionalArg::Present(ty) => Some(ty),
100            OptionalArg::Missing => None,
101        };
102        let obj = match object {
103            OptionalArg::Present(obj) => Some(obj),
104            OptionalArg::Missing => None,
105        };
106        super_init_impl(zelf, typ, obj, vm)
107    }
108}
109
110/// `super_init_impl`. A null type takes the zero-argument path.
111fn super_init_impl(
112    zelf: &Py<PySuper>,
113    typ: Option<PyTypeRef>,
114    obj: Option<PyObjectRef>,
115    vm: &VirtualMachine,
116) -> PyResult<()> {
117    let (typ, obj) = match typ {
118        Some(typ) => (typ, obj.unwrap_or_else(|| vm.ctx.none())),
119        None => super_init_without_args(vm)?,
120    };
121    let (typ, obj, obj_type) = bind_super(typ, obj, vm)?;
122    zelf.store_bound(Some(typ), obj, obj_type);
123    Ok(())
124}
125
126fn super_init_without_args(vm: &VirtualMachine) -> PyResult<(PyTypeRef, PyObjectRef)> {
127    // Access the InterpreterFrame directly — no need to materialize
128    // a FrameObject just to read code/locals.
129    let iframe_ptr = crate::vm::thread::get_current_frame();
130    if iframe_ptr.is_null() {
131        return Err(vm.new_runtime_error("super(): no current frame"));
132    }
133    let iframe = unsafe { &*iframe_ptr };
134    let code = iframe.code();
135
136    if code.arg_count == 0 {
137        return Err(vm.new_runtime_error("super(): no arguments"));
138    }
139
140    // SAFETY: InterpreterFrame is current and not concurrently mutated.
141    use rustpython_compiler_core::bytecode::CO_FAST_CELL;
142    let fastlocals = iframe.localsplus.fastlocals();
143    let obj = fastlocals[0]
144        .clone()
145        .and_then(|val| {
146            // If slot 0 is a merged cell (LOCAL|CELL), extract value from cell
147            if code
148                .localspluskinds
149                .first()
150                .is_some_and(|&k| k & CO_FAST_CELL != 0)
151            {
152                val.downcast_ref::<PyCell>().and_then(|c| c.get())
153            } else {
154                Some(val)
155            }
156        })
157        .ok_or_else(|| vm.new_runtime_error("super(): arg[0] deleted"))?;
158
159    let mut typ = None;
160    // Search for __class__ in freevars using localspluskinds
161    let nlocalsplus = code.localspluskinds.len();
162    let nfrees = code.freevars.len();
163    let free_start = nlocalsplus - nfrees;
164    for (i, var) in code.freevars.iter().enumerate() {
165        if var.as_bytes() == b"__class__" {
166            let class = fastlocals[free_start + i]
167                .as_ref()
168                .and_then(|v| v.downcast_ref::<PyCell>())
169                .and_then(|c| c.get())
170                .ok_or_else(|| vm.new_runtime_error("super(): empty __class__ cell"))?;
171            typ = Some(class.downcast().map_err(|o| {
172                vm.new_type_error(format!(
173                    "super(): __class__ is not a type ({})",
174                    o.class().name()
175                ))
176            })?);
177            break;
178        }
179    }
180    let typ = typ.ok_or_else(|| {
181        vm.new_type_error("super must be called with 1 argument or from inside class method")
182    })?;
183
184    Ok((typ, obj))
185}
186
187#[pyclass(
188    with(GetAttr, GetDescriptor, Constructor, Initializer, Representable),
189    flags(BASETYPE)
190)]
191impl PySuper {}
192
193impl GetAttr for PySuper {
194    fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
195        let skip = |zelf: &Py<Self>, name| zelf.as_object().generic_getattr(name, vm);
196        let Some(obj) = zelf.obj.load_owned() else {
197            return skip(zelf, name);
198        };
199        let Some(start_type) = zelf.obj_type.load_owned() else {
200            return skip(zelf, name);
201        };
202
203        // We want __class__ to return the class of the super object
204        // (i.e. super, or a subclass), not the class of su->obj.
205        if name.as_bytes() == b"__class__" {
206            return skip(zelf, name);
207        }
208
209        if let Some(name) = vm.ctx.interned_str(name) {
210            // Walk start_type's MRO by reference (no Vec allocation, no
211            // per-class clone) up to and including zelf.typ, then look for
212            // the first class past it that declares `name` directly.
213            // Both locks are dropped before any arbitrary Python code
214            // (the descriptor call below) runs, so they can't be held
215            // across a call that might re-enter and want them again.
216            let Some(su_type) = zelf.typ.load_owned() else {
217                return skip(zelf, name);
218            };
219            let descr = {
220                let mro = start_type.mro.read();
221                mro.iter()
222                    .skip_while(|cls| !cls.is(&su_type))
223                    .skip(1) // skip su->type (if any)
224                    .find_map(|cls| cls.get_direct_attr(name))
225            };
226            if let Some(descr) = descr {
227                return vm
228                    .call_get_descriptor_specific(
229                        &descr,
230                        // Only pass 'obj' param if this is instance-mode super (See https://bugs.python.org/issue743267)
231                        if obj.is(&start_type) {
232                            None
233                        } else {
234                            Some(&obj)
235                        },
236                        Some(start_type.as_object()),
237                    )
238                    .unwrap_or(Ok(descr));
239            }
240        }
241        skip(zelf, name)
242    }
243}
244
245impl GetDescriptor for PySuper {
246    fn descr_get(
247        zelf_obj: &PyObject,
248        obj: Option<&PyObject>,
249        _cls: Option<&PyObject>,
250        vm: &VirtualMachine,
251    ) -> PyResult {
252        let (zelf, obj) = Self::_unwrap(zelf_obj, obj, vm)?;
253        if vm.is_none(obj) || zelf.obj.deref().is_some() {
254            return Ok(zelf_obj.to_owned());
255        }
256        let zelf_class = zelf.as_object().class();
257        let typ = zelf.typ.load_owned();
258        if zelf_class.is(vm.ctx.types.super_type) {
259            let newobj = Self::empty().into_ref(&vm.ctx);
260            // A null type takes the zero-argument path inside super_init_impl.
261            super_init_impl(&newobj, typ, Some(obj.to_owned()), vm)?;
262            Ok(newobj.into())
263        } else {
264            // Call stops at the first null argument.
265            let args = match typ {
266                Some(typ) => (typ, obj.to_owned()).into_args(vm),
267                None => FuncArgs::default(),
268            };
269            PyType::call(zelf.class(), args, vm)
270        }
271    }
272}
273
274impl Representable for PySuper {
275    #[inline]
276    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
277        let type_name = match zelf.typ.load_owned() {
278            Some(ty) => ty.name().to_owned(),
279            None => "NULL".to_owned(),
280        };
281        let repr = match zelf.obj_type.load_owned() {
282            Some(ty) => format!("<super: <class '{}'>, <{} object>>", type_name, ty.name()),
283            None => format!("<super: <class '{type_name}'>, NULL>"),
284        };
285        Ok(repr)
286    }
287}
288
289fn super_check(ty: &Py<PyType>, obj: &PyObject, vm: &VirtualMachine) -> PyResult<PyTypeRef> {
290    let typ = match obj.to_owned().downcast::<PyType>() {
291        Ok(cls) if cls.fast_issubclass(ty) => return Ok(cls),
292        Ok(cls) => Some(cls),
293        Err(_) => None,
294    };
295
296    if obj.fast_isinstance(ty) {
297        return Ok(obj.class().to_owned());
298    }
299
300    let class_attr = obj.get_attr("__class__", vm)?;
301    if let Ok(cls) = class_attr.downcast::<PyType>()
302        && !cls.is(obj.class())
303        && cls.fast_issubclass(ty)
304    {
305        return Ok(cls);
306    }
307
308    let (type_or_instance, obj_str) = match typ {
309        Some(t) => ("type", t.name().to_owned()),
310        None => ("instance of", obj.class().name().to_owned()),
311    };
312
313    Err(vm.new_type_error(format!(
314        "super(type, obj): obj ({} {}) is not an instance or subtype of type ({}).",
315        type_or_instance,
316        obj_str,
317        ty.name(),
318    )))
319}
320
321pub(crate) fn init(context: &'static Context) {
322    let super_type = &context.types.super_type;
323    PySuper::extend_class(context, super_type);
324
325    const SUPER_DOC: &str = "\
326super() -> same as super(__class__, <first argument>)
327super(type) -> unbound super object
328super(type, obj) -> bound super object; requires isinstance(obj, type)
329super(type, type2) -> bound super object; requires issubclass(type2, type)
330Typical use to call a cooperative superclass method:
331class C(B):
332    def meth(self, arg):
333        super().meth(arg)
334This works for class methods too:
335class C(B):
336    @classmethod
337    def cmeth(cls, arg):
338        super().cmeth(arg)
339";
340
341    extend_class!(context, super_type, {
342        "__doc__" => context.new_str(SUPER_DOC),
343    });
344}