use crate::function::OptionalArg;
use crate::obj::objfunction::PyMethod;
use crate::obj::objstr::PyStringRef;
use crate::obj::objtype::{PyClass, PyClassRef};
use crate::pyobject::{
PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
};
use crate::scope::NameProtocol;
use crate::vm::VirtualMachine;
use super::objtype;
pub type PySuperRef = PyRef<PySuper>;
#[pyclass]
#[derive(Debug)]
pub struct PySuper {
obj: PyObjectRef,
typ: PyObjectRef,
obj_type: PyObjectRef,
}
impl PyValue for PySuper {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.super_type()
}
}
#[pyimpl]
impl PySuper {
#[pymethod(name = "__repr__")]
fn repr(&self, _vm: &VirtualMachine) -> String {
let class_type_str = if let Ok(type_class) = self.typ.clone().downcast::<PyClass>() {
type_class.name.clone()
} else {
"NONE".to_string()
};
match self.obj_type.clone().downcast::<PyClass>() {
Ok(obj_class_typ) => format!(
"<super: <class '{}'>, <{} object>>",
class_type_str, obj_class_typ.name
),
_ => format!("<super: <class '{}'> NULL>", class_type_str),
}
}
#[pymethod(name = "__getattribute__")]
fn getattribute(&self, name: PyStringRef, vm: &VirtualMachine) -> PyResult {
let inst = self.obj.clone();
let typ = self.typ.clone();
match typ.payload::<PyClass>() {
Some(PyClass { ref mro, .. }) => {
for class in mro {
if let Ok(item) = vm.get_attribute(class.as_object().clone(), name.clone()) {
if item.payload_is::<PyMethod>() {
return Ok(item);
}
return Ok(vm.ctx.new_bound_method(item, inst.clone()));
}
}
Err(vm.new_attribute_error(format!(
"{} has no attribute '{}'",
inst,
name.as_str()
)))
}
_ => panic!("not Class"),
}
}
#[pymethod(name = "__new__")]
fn new(
cls: PyClassRef,
py_type: OptionalArg<PyClassRef>,
py_obj: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PySuperRef> {
let py_type = if let OptionalArg::Present(ty) = py_type {
ty.clone()
} else {
match vm.current_scope().load_cell(vm, "__class__") {
Some(obj) => PyClassRef::try_from_object(vm, obj)?,
_ => {
return Err(vm.new_type_error(
"super must be called with 1 argument or from inside class method"
.to_string(),
));
}
}
};
if !objtype::isinstance(py_type.as_object(), &vm.get_type()) {
return Err(vm.new_type_error(format!(
"super() argument 1 must be type, not {}",
py_type.class().name
)));
}
let py_obj = if let OptionalArg::Present(obj) = py_obj {
obj.clone()
} else {
let frame = vm.current_frame().expect("no current frame for super()");
if let Some(first_arg) = frame.code.arg_names.get(0) {
match vm.get_locals().get_item_option(first_arg, vm)? {
Some(obj) => obj.clone(),
_ => {
return Err(vm.new_type_error(format!(
"super arguement {} was not supplied",
first_arg
)));
}
}
} else {
return Err(vm.new_type_error(
"super must be called with 1 argument or from inside class method".to_string(),
));
}
};
let obj_type = if !objtype::isinstance(&py_obj, &py_type) {
let is_subclass = if let Ok(py_obj) = PyClassRef::try_from_object(vm, py_obj.clone()) {
objtype::issubclass(&py_obj, &py_type)
} else {
false
};
if !is_subclass {
return Err(vm.new_type_error(
"super(type, obj): obj must be an instance or subtype of type".to_string(),
));
}
PyClassRef::try_from_object(vm, py_obj.clone())?
} else {
py_obj.class()
};
PySuper {
obj: py_obj,
typ: py_type.into_object(),
obj_type: obj_type.into_object(),
}
.into_ref_with_type(vm, cls)
}
}
pub fn init(context: &PyContext) {
let super_type = &context.types.super_type;
PySuper::extend_class(context, super_type);
let super_doc = "super() -> same as super(__class__, <first argument>)\n\
super(type) -> unbound super object\n\
super(type, obj) -> bound super object; requires isinstance(obj, type)\n\
super(type, type2) -> bound super object; requires issubclass(type2, type)\n\
Typical use to call a cooperative superclass method:\n\
class C(B):\n \
def meth(self, arg):\n \
super().meth(arg)\n\
This works for class methods too:\n\
class C(B):\n \
@classmethod\n \
def cmeth(cls, arg):\n \
super().cmeth(arg)\n";
extend_class!(context, super_type, {
"__doc__" => context.new_str(super_doc.to_string()),
});
}