use std::fmt::Write;
use crate::{
bytecode::{CallResult, VM},
defer_drop,
exception_private::{ExcType, ExcTypeExt, RunError, RunResult},
hash::{HashValue, identity_hash},
heap::{ContainsHeap, DropWithContext, HeapId, HeapItem, HeapRead},
intern::StringId,
types::{LazyHeapSet, PyTrait, Type, str::allocate_string},
value::{EitherStr, Value},
};
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct DataclassField {
name: StringId,
annotation: Value,
default: Option<Value>,
}
impl DataclassField {
#[must_use]
pub fn new(name: StringId, annotation: Value, default: Option<Value>) -> Self {
Self {
name,
annotation,
default,
}
}
#[must_use]
pub fn name(&self) -> StringId {
self.name
}
#[must_use]
pub fn annotation(&self) -> &Value {
&self.annotation
}
#[must_use]
pub fn default(&self) -> Option<&Value> {
self.default.as_ref()
}
}
impl<C: ContainsHeap> DropWithContext<C> for DataclassField {
fn drop_with(self, ctx: &mut C) {
self.annotation.drop_with(ctx);
self.default.drop_with(ctx);
}
}
const UNMODELLED_ATTRS: [(&str, &str); 3] = [
("default_factory", "dataclasses.MISSING"),
("metadata", "types.MappingProxyType"),
("_field_type", "dataclasses._FIELD"),
];
impl<'h> PyTrait<'h> for HeapRead<'h, DataclassField> {
fn py_type(&self, _vm: &VM<'h>) -> Type {
Type::DataclassField
}
fn py_len(&self, _vm: &VM<'h>) -> Option<usize> {
None
}
fn py_eq_impl(&self, _other: &Value, _vm: &mut VM<'h>) -> RunResult<Option<bool>> {
Ok(None)
}
fn py_hash(&self, self_id: HeapId, _vm: &mut VM<'h>) -> RunResult<Option<HashValue>> {
Ok(Some(identity_hash(self_id)))
}
fn py_repr_fmt(&self, f: &mut impl Write, vm: &mut VM<'h>, heap_ids: &mut LazyHeapSet) -> RunResult<()> {
let Ok(mut guard) = vm.recursion_guard() else {
return Ok(f.write_str("...")?);
};
let vm = &mut *guard;
let (name, annotation, default) = {
let this = self.get(vm.heap);
(
Value::InternString(this.name),
this.annotation.clone_with_heap(vm.heap),
this.default.as_ref().map(|v| v.clone_with_heap(vm.heap)),
)
};
defer_drop!(annotation, vm);
defer_drop!(default, vm);
f.write_str("Field(name=")?;
name.py_repr_fmt(f, vm, heap_ids)?;
f.write_str(",type=")?;
annotation.py_repr_fmt(f, vm, heap_ids)?;
f.write_str(",default=")?;
match default {
Some(default) => default.py_repr_fmt(f, vm, heap_ids)?,
None => f.write_str("MISSING")?,
}
Ok(f.write_str(
",default_factory=MISSING,init=True,repr=True,hash=None,compare=True,\
metadata=mappingproxy({}),kw_only=False,doc=None,_field_type=_FIELD)",
)?)
}
fn py_getattr(&self, attr: &EitherStr, vm: &mut VM<'h>) -> RunResult<Option<CallResult>> {
let attr_str = attr.as_str(vm.interns);
let value = match attr_str {
"name" => {
let name = vm.interns.get_str(self.get(vm.heap).name).to_owned();
allocate_string(name, vm.heap)
}
"type" => self.get(vm.heap).annotation.clone_with_heap(vm.heap),
"default" => match self.get(vm.heap).default.as_ref() {
Some(default) => default.clone_with_heap(vm.heap),
None => return Err(unmodelled_attr_error("default", "dataclasses.MISSING")),
},
"init" | "repr" | "compare" => Value::Bool(true),
"kw_only" => Value::Bool(false),
"hash" | "doc" => Value::None,
_ => match UNMODELLED_ATTRS.iter().find(|&&(name, _)| name == attr_str) {
Some((name, missing)) => return Err(unmodelled_attr_error(name, missing)),
None => return Err(ExcType::attribute_error("Field", attr_str)),
},
};
Ok(Some(CallResult::Value(value)))
}
}
fn unmodelled_attr_error(attr: &str, missing: &str) -> RunError {
ExcType::not_implemented(format!(
"Field.{attr} is not yet supported, {missing} is not implemented"
))
.into()
}
impl HeapItem for DataclassField {
fn py_dec_ref_ids(&mut self, stack: &mut Vec<HeapId>) {
self.annotation.py_dec_ref_ids(stack);
if let Some(default) = &mut self.default {
default.py_dec_ref_ids(stack);
}
}
}