use std::{
cell::RefCell,
collections::HashMap,
sync::{Arc, Mutex},
};
use derive_more::Display;
use gazebo::{any::AnyLifetime, cell::AsARef};
use crate as starlark;
use crate::{
assert,
assert::Assert,
environment::{GlobalsBuilder, Module},
eval::Evaluator,
syntax::{AstModule, Dialect},
values::{any::StarlarkAny, none::NoneType, Freeze, NoSerialize, StarlarkValue, Value},
};
#[test]
fn test_export_as() {
use std::fmt::{self, Debug, Display};
use gazebo::any::AnyLifetime;
use crate as starlark;
use crate::values::{AllocValue, Freezer, Heap, StarlarkValue, Trace, Value};
#[derive(Debug, Trace, AnyLifetime, NoSerialize)]
struct Exporter<T> {
named: T,
value: i32,
}
impl<T: AsARef<String>> Display for Exporter<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}={}", AsARef::as_aref(&self.named), self.value)
}
}
impl<'v, T: AsARef<String> + Debug> StarlarkValue<'v> for Exporter<T>
where
Self: AnyLifetime<'v>,
{
starlark_type!("exporter");
fn export_as(&self, variable_name: &str, _eval: &mut Evaluator<'v, '_>) {
if let Some(named) = AsARef::as_ref_cell(&self.named) {
*named.borrow_mut() = variable_name.to_owned();
}
}
}
impl AllocValue<'_> for Exporter<RefCell<String>> {
fn alloc_value(self, heap: &Heap) -> Value {
heap.alloc_complex(self)
}
}
impl<'v> Freeze for Exporter<RefCell<String>> {
type Frozen = Exporter<String>;
fn freeze(self, _freezer: &Freezer) -> anyhow::Result<Self::Frozen> {
Ok(Exporter {
named: self.named.into_inner(),
value: self.value,
})
}
}
#[starlark_module]
fn exporter(builder: &mut GlobalsBuilder) {
fn exporter(value: i32) -> anyhow::Result<Exporter<RefCell<String>>> {
Ok(Exporter {
named: RefCell::new("unnamed".to_owned()),
value,
})
}
}
let mut a = Assert::new();
a.globals_add(exporter);
a.module(
"a",
"x = exporter(1); y = x; longer_name = exporter(2); arrayed = [exporter(3)]",
);
let opt1 = "(x=1, x=1, longer_name=2, unnamed=3)".to_owned();
let opt2 = opt1.replace("x=", "y=");
a.is_true(&format!(
r#"
load('a', 'x', 'y', 'longer_name', 'arrayed')
v = str((x, y, longer_name, arrayed[0]))
v == '{}' or v == '{}'"#,
opt1, opt2
))
}
#[test]
fn test_load_symbols() {
#[starlark_module]
fn module(builder: &mut GlobalsBuilder) {
fn load_symbol<'v>(name: &str, value: Value<'v>) -> anyhow::Result<NoneType> {
eval.set_module_variable_at_some_point(name, value)?;
Ok(NoneType)
}
}
let mut a = Assert::new();
a.globals_add(module);
a.module("a", "load_symbol('x', 6*7)");
a.is_true("load('a', 'x'); x == 42")
}
#[test]
fn test_load_public_symbols_does_not_reexport() -> anyhow::Result<()> {
let mut a = Assert::new();
let module_b = a.module("b", "x = 5");
let module_a = Module::new();
module_a.import_public_symbols(&module_b);
a.module_add("a", module_a.freeze()?);
a.fail("load('a', 'x')", "Module symbol `x` is not exported");
Ok(())
}
#[test]
fn test_load_symbols_extra() -> anyhow::Result<()> {
#[starlark_module]
fn module(builder: &mut GlobalsBuilder) {
fn load_symbol<'v>(name: &str, value: Value<'v>) -> anyhow::Result<NoneType> {
let extra = eval.extra_v.unwrap().downcast_ref::<Extra<'v>>().unwrap();
extra.0.lock().unwrap().insert(name.to_owned(), value);
Ok(NoneType)
}
}
#[derive(AnyLifetime, Default)]
struct Extra<'v>(Arc<Mutex<HashMap<String, Value<'v>>>>);
let modu = Module::new();
let globals = GlobalsBuilder::extended().with(module).build();
let mut eval = Evaluator::new(&modu);
let extra = Extra::default();
eval.extra_v = Some(&extra);
eval.eval_module(
AstModule::parse("a", "load_symbol('x', 6*7)".to_owned(), &Dialect::Extended)?,
&globals,
)?;
for (name, value) in extra.0.lock().unwrap().iter() {
modu.set(name, *value);
}
let mut a = Assert::new();
a.globals(globals);
a.module_add("a", modu.freeze()?);
a.is_true("load('a', 'x'); x == 42");
Ok(())
}
#[test]
fn test_repr_str() {
#[derive(AnyLifetime, Debug, Display)]
#[display(fmt = "{:?}", self)]
struct Foo(Option<usize>);
#[starlark_module]
fn module(builder: &mut GlobalsBuilder) {
fn mk_foo() -> anyhow::Result<StarlarkAny<Foo>> {
Ok(StarlarkAny::new(Foo(Some(42))))
}
}
let mut a = Assert::new();
a.globals_add(module);
a.pass("assert_eq(repr(mk_foo()), 'Foo(Some(42))')");
}
#[test]
fn test_starlark_module() {
#[starlark_module]
fn global(builder: &mut GlobalsBuilder) {
fn cc_binary(name: &str, srcs: Vec<&str>) -> anyhow::Result<String> {
Ok(format!("{:?} {:?}", name, srcs))
}
}
let mut a = Assert::new();
a.globals_add(global);
let v = a.pass("cc_binary(name='star', srcs=['a.cc', 'b.cc'])");
assert_eq!(
v.value().unpack_str().unwrap(),
r#""star" ["a.cc", "b.cc"]"#
);
}
mod value_of {
use either::Either;
use itertools::Itertools;
use super::*;
use crate::{
self as starlark,
assert::Assert,
environment::GlobalsBuilder,
values::{dict::DictOf, list::ListOf, structs::StructOf, ValueOf},
};
#[starlark_module]
fn validate_module(builder: &mut GlobalsBuilder) {
fn with_int<'v>(v: ValueOf<i32>) -> anyhow::Result<(Value<'v>, String)> {
Ok((*v, format!("{}", v.typed)))
}
fn with_int_list<'v>(v: ListOf<i32>) -> anyhow::Result<(Value<'v>, String)> {
let repr = v.to_vec().iter().join(", ");
Ok((*v, repr))
}
fn with_list_list<'v>(v: ListOf<ListOf<i32>>) -> anyhow::Result<(Value<'v>, String)> {
let repr = v
.to_vec()
.iter()
.map(|l| l.to_vec().iter().join(", "))
.join(" + ");
Ok((*v, repr))
}
fn with_dict_list<'v>(v: ListOf<DictOf<i32, i32>>) -> anyhow::Result<(Value<'v>, String)> {
let repr = v
.to_vec()
.iter()
.map(|l| {
l.to_dict()
.iter()
.map(|(k, v)| format!("{}: {}", k, v))
.join(", ")
})
.join(" + ");
Ok((*v, repr))
}
fn with_int_dict<'v>(v: DictOf<i32, i32>) -> anyhow::Result<(Value<'v>, String)> {
let repr = v
.to_dict()
.iter()
.map(|(k, v)| format!("{}: {}", k, v))
.join(" + ");
Ok((*v, repr))
}
fn with_list_dict<'v>(v: DictOf<i32, ListOf<i32>>) -> anyhow::Result<(Value<'v>, String)> {
let repr = v
.to_dict()
.iter()
.map(|(k, v)| format!("{}: {}", k, v.to_vec().iter().join(", ")))
.join(" + ");
Ok((*v, repr))
}
fn with_dict_dict<'v>(
v: DictOf<i32, DictOf<i32, i32>>,
) -> anyhow::Result<(Value<'v>, String)> {
let repr = v
.to_dict()
.iter()
.map(|(k, v)| {
let inner_repr = v
.to_dict()
.iter()
.map(|(k2, v2)| format!("{}:{}", k2, v2))
.join(", ");
format!("{}: {}", k, inner_repr)
})
.join(" + ");
Ok((*v, repr))
}
fn with_struct_int<'v>(v: StructOf<i32>) -> anyhow::Result<(Value<'v>, String)> {
let repr = v
.to_map()
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.join(" + ");
Ok((v.to_value(), repr))
}
fn with_either(v: Either<i32, Either<String, ListOf<i32>>>) -> anyhow::Result<String> {
match v {
Either::Left(i) => Ok(i.to_string()),
Either::Right(nested) => match nested {
Either::Left(s) => Ok(s),
Either::Right(l) => Ok(l.to_repr()),
},
}
}
}
const BAD: &str = "Type of parameter";
#[test]
fn test_value_of() {
let mut a = Assert::new();
a.globals_add(validate_module);
a.eq("(1, '1')", "with_int(1)");
a.fail("with_int(None)", BAD);
}
#[test]
fn test_list_of() {
let mut a = Assert::new();
a.globals_add(validate_module);
a.eq("([1, 2, 3], '1, 2, 3')", "with_int_list([1, 2, 3])");
a.fail("with_int_list(1)", BAD);
a.fail("with_int_list([1, 'foo'])", BAD);
a.fail("with_int_list([[]])", BAD);
a.eq(
"([[1, 2], [3]], '1, 2 + 3')",
"with_list_list([[1, 2], [3]])",
);
let expected = r#"([{1: 2, 3: 4}, {5: 6}], "1: 2, 3: 4 + 5: 6")"#;
let test = r#"with_dict_list([{1: 2, 3: 4}, {5: 6}])"#;
a.eq(expected, test);
}
#[test]
fn test_dict_of() {
let mut a = Assert::new();
a.globals_add(validate_module);
a.eq("({1: 2}, '1: 2')", "with_int_dict({1: 2})");
a.fail(r#"with_int_dict(1)"#, BAD);
a.fail(r#"with_int_dict({1: "str"})"#, BAD);
a.fail(r#"with_int_dict({1: {}})"#, BAD);
let expected = r#"({1: [2, 3], 4: [5]}, "1: 2, 3 + 4: 5")"#;
let test = r#"with_list_dict({1: [2, 3], 4: [5]})"#;
a.eq(expected, test);
let expected = r#"({1: {2: 3, 4: 5}, 6: {7: 8}}, "1: 2:3, 4:5 + 6: 7:8")"#;
let test = r#"with_dict_dict({1: {2: 3, 4: 5}, 6: {7: 8}})"#;
a.eq(expected, test);
}
#[test]
fn test_struct_of() {
let mut a = Assert::new();
a.globals_add(validate_module);
a.eq("(struct(a=1), '\"a\"=1')", "with_struct_int(struct(a=1))");
a.fail("with_struct_int(struct(a=True))", BAD);
}
#[test]
fn test_either_of() {
let mut a = Assert::new();
a.globals_add(validate_module);
a.eq("'2'", "with_either(2)");
a.eq("'[2, 3]'", "with_either([2,3])");
a.eq("'s'", "with_either('s')");
a.fail("with_either(None)", BAD);
a.fail("with_either({})", BAD);
}
}
#[test]
fn test_derive_attrs() {
#[derive(Debug, StarlarkAttrs, Display, AnyLifetime, NoSerialize)]
#[display(fmt = "{:?}", self)]
struct Example {
hello: String,
#[starlark(skip)]
answer: i64,
#[starlark(clone)]
nested: Nested,
}
starlark_simple_value!(Example);
impl<'v> StarlarkValue<'v> for Example {
starlark_type!("example");
starlark_attrs!();
}
#[derive(Debug, Clone, StarlarkAttrs, Display, AnyLifetime, NoSerialize)]
#[display(fmt = "{}", foo)]
struct Nested {
foo: String,
}
starlark_simple_value!(Nested);
impl<'v> StarlarkValue<'v> for Nested {
starlark_type!("nested");
starlark_attrs!();
}
let mut a = Assert::new();
a.globals_add(|gb| {
gb.set(
"example",
Example {
hello: "world".to_owned(),
answer: 42,
nested: Nested {
foo: "bar".to_owned(),
},
},
)
});
a.eq("example.hello", "\"world\"");
a.eq("dir(example)", "[\"hello\", \"nested\"]");
a.is_true("not hasattr(example, \"answer\")");
a.eq("example.nested.foo", "\"bar\"");
}
#[test]
fn test_eval_function() {
let fun = assert::pass(
r#"
def fun(a, *, y: "string", x = 1) -> "string":
return str((a, y, x))
fun
"#,
);
let env = Module::new();
let mut eval = Evaluator::new(&env);
let hello = env.heap().alloc("hello");
let v = eval
.eval_function(fun.value(), &[Value::new_int(8)], &[("y", hello)])
.unwrap();
assert_eq!(v.unpack_str(), Some("(8, \"hello\", 1)"))
}