use dupe::Dupe;
use once_cell::sync::OnceCell;
use starlark_map::Hashed;
use crate::__derive_refs::components::NativeCallableComponents;
use crate::collections::symbol::map::SymbolMap;
use crate::collections::symbol::symbol::Symbol;
use crate::docs::DocType;
use crate::environment::common_documentation;
use crate::typing::Ty;
use crate::values::function::NativeAttr;
use crate::values::function::NativeAttribute;
use crate::values::function::NativeMeth;
use crate::values::function::NativeMethod;
use crate::values::types::unbound::UnboundValue;
use crate::values::AllocFrozenValue;
use crate::values::FrozenHeap;
use crate::values::FrozenHeapRef;
use crate::values::FrozenRef;
use crate::values::FrozenValue;
use crate::values::FrozenValueTyped;
use crate::values::Heap;
use crate::values::Value;
#[derive(Clone, Debug)]
pub struct Methods {
#[allow(dead_code)]
heap: FrozenHeapRef,
members: SymbolMap<UnboundValue>,
docstring: Option<String>,
}
#[derive(Debug)]
pub struct MethodsBuilder {
heap: FrozenHeap,
members: SymbolMap<UnboundValue>,
docstring: Option<String>,
}
impl Methods {
pub(crate) fn get<'v>(&'v self, name: &str) -> Option<Value<'v>> {
Some(self.members.get_str(name)?.to_frozen_value().to_value())
}
pub(crate) fn get_ty(&self, name: &str) -> Option<Ty> {
match self.members.get_str(name)? {
UnboundValue::Attr(attr, _) => Some(attr.typ.dupe()),
UnboundValue::Method(method, _) => Some(method.ty.dupe()),
}
}
#[inline]
pub(crate) fn get_hashed(&self, name: Hashed<&str>) -> Option<&UnboundValue> {
self.members.get_hashed_str(name)
}
#[inline]
pub(crate) fn get_frozen_symbol(&self, name: &Symbol) -> Option<&UnboundValue> {
self.members.get(name)
}
pub(crate) fn names(&self) -> Vec<String> {
self.members.keys().map(|x| x.as_str().to_owned()).collect()
}
pub(crate) fn members(&self) -> impl Iterator<Item = (&str, FrozenValue)> {
self.members
.iter()
.map(|(k, v)| (k.as_str(), v.to_frozen_value()))
}
pub fn documentation(&self, ty: Ty) -> DocType {
let (docs, members) = common_documentation(
&self.docstring,
self.members
.iter()
.map(|(n, v)| (n.as_str(), v.to_frozen_value())),
);
DocType {
docs,
members: members
.filter_map(|(n, item)| {
Some((n, item.try_as_member_with_collapsed_object().ok()?))
})
.collect(),
ty,
constructor: None,
}
}
}
impl Methods {
pub fn new() -> Self {
MethodsBuilder::new().build()
}
}
impl MethodsBuilder {
pub fn new() -> Self {
MethodsBuilder {
heap: FrozenHeap::new(),
members: SymbolMap::new(),
docstring: None,
}
}
pub fn build(self) -> Methods {
Methods {
heap: self.heap.into_ref(),
members: self.members,
docstring: self.docstring,
}
}
pub fn with(mut self, f: impl FnOnce(&mut Self)) -> Self {
f(&mut self);
self
}
pub fn set_docstring(&mut self, docstring: &str) {
self.docstring = Some(docstring.to_owned());
}
pub fn set_attribute<'v, V: AllocFrozenValue>(
&'v mut self,
name: &str,
value: V,
docstring: Option<String>,
) {
let value = self.heap.alloc(value);
self.set_attribute_fn(
name,
true,
docstring,
V::starlark_type_repr(),
move |_, _| Ok(value.to_value()),
);
}
pub fn set_attribute_fn<F>(
&mut self,
name: &str,
speculative_exec_safe: bool,
docstring: Option<String>,
typ: Ty,
f: F,
) where
F: for<'v> Fn(Value<'v>, &'v Heap) -> crate::Result<Value<'v>> + Send + Sync + 'static,
{
self.members.insert(
name,
UnboundValue::Attr(
FrozenValueTyped::new(self.heap.alloc(NativeAttribute {
speculative_exec_safe,
docstring,
typ,
}))
.unwrap(),
FrozenRef::<dyn NativeAttr + 'static>::new(
self.heap.alloc_any_debug_type_name(f).as_ref(),
),
),
);
}
pub fn set_method<F>(&mut self, name: &str, components: NativeCallableComponents, f: F)
where
F: NativeMeth,
{
let ty = Ty::from_native_callable_components(&components, None).unwrap();
let function = FrozenRef::<dyn NativeMeth + 'static>::new(
self.heap.alloc_any_debug_type_name(f).as_ref(),
);
self.members.insert(
name,
UnboundValue::Method(
FrozenValueTyped::new(self.heap.alloc(NativeMethod {
function,
name: name.to_owned(),
speculative_exec_safe: components.speculative_exec_safe,
docs: components.into_docs(None),
ty,
}))
.unwrap(),
function,
),
);
}
pub fn alloc<'v, V: AllocFrozenValue>(&'v self, value: V) -> FrozenValue {
value.alloc_frozen_value(&self.heap)
}
}
pub struct MethodsStatic(OnceCell<Methods>);
impl MethodsStatic {
pub const fn new() -> Self {
Self(OnceCell::new())
}
pub fn methods(&'static self, x: impl FnOnce(&mut MethodsBuilder)) -> Option<&'static Methods> {
Some(self.0.get_or_init(|| MethodsBuilder::new().with(x).build()))
}
pub fn populate(&'static self, x: impl FnOnce(&mut MethodsBuilder), out: &mut MethodsBuilder) {
let methods = self.methods(x).unwrap();
for (name, value) in methods.members.iter() {
out.members.insert(name.as_str(), value.clone());
}
out.docstring = methods.docstring.clone();
}
}
#[cfg(test)]
mod tests {
use allocative::Allocative;
use derive_more::Display;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;
use starlark_derive::ProvidesStaticType;
use crate as starlark;
use crate::assert::Assert;
use crate::environment::Methods;
use crate::environment::MethodsStatic;
use crate::starlark_simple_value;
use crate::values::StarlarkValue;
#[test]
fn test_set_attribute() {
#[derive(Debug, Display, ProvidesStaticType, NoSerialize, Allocative)]
#[display("Magic")]
struct Magic;
starlark_simple_value!(Magic);
#[starlark_value(type = "magic")]
impl<'v> StarlarkValue<'v> for Magic {
fn get_methods() -> Option<&'static Methods> {
static RES: MethodsStatic = MethodsStatic::new();
RES.methods(|x| {
x.set_attribute("my_type", "magic", None);
x.set_attribute("my_value", 42, None);
})
}
}
let mut a = Assert::new();
a.globals_add(|x| x.set("magic", Magic));
a.pass(
r#"
assert_eq(magic.my_type, "magic")
assert_eq(magic.my_value, 42)"#,
);
}
}