use std::{cell::RefCell, fmt::Write};
use derive_more::Display;
use gazebo::any::AnyLifetime;
use crate::{
self as starlark,
assert::{self, Assert},
collections::SmallMap,
environment::{Globals, GlobalsBuilder, Module},
errors::Diagnostic,
eval::Evaluator,
syntax::{AstModule, Dialect},
values::{Freeze, Freezer, Heap, NoSerialize, StarlarkValue, Trace, UnpackValue, Value},
};
mod basic;
mod bc;
mod before_stmt;
mod call;
mod comprehension;
mod def;
mod docstring;
mod go;
mod interop;
mod opt;
mod runtime;
mod type_is;
#[test]
fn alias_test() {
assert::is_true(
r#"
a = [1, 2, 3]
b = a
a[2] = 0
a == [1, 2, 0] and b == [1, 2, 0]
"#,
)
}
#[test]
fn test_bad_break() {
assert::fails("break", &["break", "outside of", "loop"]);
assert::fails(
"def foo(x):\n if 1:\n break",
&["break", "outside of", "loop"],
);
assert::fails(
"def foo(x):\n if 1:\n continue",
&["continue", "outside of", "loop"],
);
assert::fail(
"
def foo(x):
for y in x:
def bar(y):
continue",
"outside of",
);
assert::fail("return 1", "outside of a `def`");
assert::fail("for x in []:\n return 1", "outside of a `def`");
}
#[test]
fn test_tabs_fail() {
let mut a = Assert::new();
a.dialect_set(|d| d.enable_tabs = false);
a.fail("def f():\n\tpass", "Parse error");
a.fail("def f():\n x\t=3", "Parse error");
}
#[test]
fn test_tabs_pass() {
assert::pass("def f():\n '\t'");
let mut a = Assert::new();
a.dialect(&Dialect::Standard);
a.pass("def f():\n\tpass");
a.pass("def f():\n x\t=3");
}
#[test]
fn test_top_level_statements() {
assert::pass(
r#"
j = 0
for i in range(10):
if i % 2 == 0:
j += i
assert_eq(j, 20)
"#,
);
}
#[test]
fn test_compiled_literals() {
assert::is_true(
"
def f():
return [[]]
y = f()
y.append(1)
y == [[],1]",
);
assert::is_true(
"
def f():
return {1:2}
y = f()
y[3] = 4
y == {1:2, 3:4}",
);
assert::is_true(
"
def f():
return [[]]
y = f()[0]
y.append(1)
y == [1]",
);
}
#[test]
fn test_frozen_iteration() {
assert::is_true(
r#"
def loop():
xs = [1, 2, 3]
z = 0
for x in xs:
for y in xs:
z += x + y
return z
loop() == 36"#,
);
assert::is_true(
r#"
def loop():
y = 0
xs = [1, 2, 3]
for x in xs:
y += x
xs.append(4)
for x in xs:
y += x
return y
loop() == 16"#,
);
assert::fail(
r#"
def loop():
xs = [1, 2, 3]
for x in xs:
if len(xs) == 3:
xs.append(4)
loop()"#,
"mutate an iterable",
);
}
#[test]
fn test_lvalue_once() {
assert::is_true(
r#"
ys = [1]
xs = [1,2,3,4]
def f():
return ys[0]
def g():
ys[0] = 2;
return 10
xs[f()] += g()
# f must be evaluated first, and only once
xs == [1,12,3,4]
"#,
);
assert::is_true(
r#"
ys = [1]
xs = [1,2,3,4]
def f():
return ys[0]
def g():
ys[0] = 2;
return 10
xs[f()] = g()
xs == [1, 2, 10, 4]
"#,
);
}
#[test]
fn test_add_assign() {
assert::pass(
r#"
x = 1
x += 8
assert_eq(x, 9)"#,
);
assert::pass(
r#"
orig = [1, 2]
x = orig
x += [3]
assert_eq(x, [1, 2, 3])
assert_eq(orig, [1, 2, 3])
"#,
);
assert::pass(
r#"
orig = (1, 2)
x = orig
x += (3,)
assert_eq(x, (1, 2, 3))
assert_eq(orig, (1, 2))
"#,
);
assert::fail(
r#"
x = {1: 2}
x += {3: 4}
"#,
"not supported",
);
assert::pass(
r#"
x = [1, 2]
x[0] += 5
assert_eq(x, [6, 2])
"#,
);
assert::pass(
r#"
x = {1: 2}
x[1] += 5
assert_eq(x, {1: 7})
"#,
);
assert::fail(
r#"
def foo():
xs = [1, 2]
for x in xs:
xs += [1]
break
foo()
"#,
"mutate an iterable",
);
assert::fail(
r#"
xs = (1, 2)
xs[1] += 1
"#,
"Immutable",
);
}
#[test]
fn test_radd() {
#[derive(Debug, Display, Clone, AnyLifetime, NoSerialize)]
#[display(fmt = "${:?}", _0)]
struct Select(Vec<i32>);
starlark_simple_value!(Select);
impl<'v> UnpackValue<'v> for Select {
fn expected() -> String {
Select::get_type_value_static().as_str().to_owned()
}
fn unpack_value(value: Value<'v>) -> Option<Self> {
match Select::from_value(value) {
Some(x) => Some(x.clone()),
None => Some(Select(UnpackValue::unpack_value(value)?)),
}
}
}
impl Select {
fn add(mut self, x: &Select) -> Self {
self.0.extend(x.0.iter().copied());
self
}
}
impl<'v> StarlarkValue<'v> for Select {
starlark_type!("select");
fn radd(&self, lhs: Value<'v>, heap: &'v Heap) -> Option<anyhow::Result<Value<'v>>> {
let lhs: Select = UnpackValue::unpack_value(lhs).unwrap();
Some(Ok(heap.alloc(lhs.add(self))))
}
fn add(&self, rhs: Value<'v>, heap: &'v Heap) -> Option<anyhow::Result<Value<'v>>> {
let rhs: Select = UnpackValue::unpack_value(rhs).unwrap();
Some(Ok(heap.alloc(self.clone().add(&rhs))))
}
fn collect_repr(&self, collector: &mut String) {
write!(collector, "{}", self).unwrap()
}
}
#[starlark_module]
fn module(build: &mut GlobalsBuilder) {
fn select(xs: Vec<i32>) -> anyhow::Result<Select> {
Ok(Select(xs))
}
}
let mut a = Assert::new();
a.globals_add(module);
a.pass(
r#"
s1 = select([1])
s2 = select([2])
assert_eq(repr(s1), "$[1]")
assert_eq(repr(s1 + [3]), "$[1, 3]")
assert_eq(repr([3] + s1), "$[3, 1]")
assert_eq(repr(s1 + s2), "$[1, 2]")
s1 += [3]
v = [4]
v += s2
s2 += s1
assert_eq(repr(s1), "$[1, 3]")
assert_eq(repr(v), "$[4, 2]")
assert_eq(repr(s2), "$[2, 1, 3]")
"#,
);
}
#[test]
fn test_compound_assignment() {
assert::pass(
r#"
x = 1
x <<= 8
assert_eq(x, 256)"#,
);
assert::pass(
r#"
x = 1
x ^= 8
assert_eq(x, 9)"#,
);
}
#[test]
fn test_static_name_checks() {
let a = Assert::new();
a.fail(
r#"
def f():
no_name()
True"#,
"no_name",
);
}
#[test]
fn test_function_to_name() {
let mut a = Assert::new();
a.module(
"x",
r#"
def mine():
pass
names = {repr: "repr", str: "str", mine: "mine"}
assert_eq(names[repr], "repr")
assert_eq(names[mine], "mine")
assert_eq(names[str], "str")
"#,
);
a.pass(
r#"
load("x", "mine", "names")
assert_eq(names[repr], "repr")
assert_eq(names[mine], "mine")
assert_eq(names[str], "str")
"#,
);
}
#[test]
fn test_eyeball() {
let display = std::env::var("EYEBALL") == Ok("1".to_owned());
let mut a = Assert::new();
a.module(
"imported",
r#"
# blank lines to make line numbers bigger and more obvious
#
#
#
#
x = []
def add2(z):
add(z)
def add(z):
x.append(z)"#,
);
let diag = a.fail(
r#"
load('imported', 'add2')
def add3(z):
add2(z)
add3(8)"#,
"Immutable",
);
if display {
Diagnostic::eprint(&diag)
}
assert_eq!(
&format!("\n{}", diag),
r#"
Traceback (most recent call last):
* assert.bzl:5, in <module>
add3(8)
* assert.bzl:4, in add3
add2(z)
* imported.bzl:9, in add2
add(z)
* imported.bzl:11, in add
x.append(z)
error: Immutable
--> imported.bzl:11:3
|
11 | x.append(z)
| ^^^^^^^^^^^
|
"#
);
assert_eq!(
&format!("\n{:#}", diag),
r#"
Traceback (most recent call last):
* assert.bzl:5, in <module>
add3(8)
* assert.bzl:4, in add3
add2(z)
* imported.bzl:9, in add2
add(z)
* imported.bzl:11, in add
x.append(z)
error: Immutable
--> imported.bzl:11:3
|
11 | x.append(z)
| ^^^^^^^^^^^
|
"#
);
}
#[test]
fn test_load_reexport() {
let mut a = Assert::new();
a.dialect_set(|d| d.enable_load_reexport = true);
a.module("a", "x = 1");
a.module("b", "load('a', 'x')");
a.pass("load('b', 'x')\nassert_eq(x, 1)");
let mut a = Assert::new();
a.dialect_set(|d| d.enable_load_reexport = false);
a.module("a", "x = 1");
a.module("b", "load('a', 'x')");
a.fail(
"load('b', 'x')\nassert_eq(x, 1)",
"Module symbol `x` is not exported",
);
}
#[test]
fn test_module_visibility_preserved_by_evaluator() -> anyhow::Result<()> {
let globals = Globals::standard();
let import = Module::new();
import.set("a", Value::new_int(1));
import.set_private("b", Value::new_int(2));
let mut eval = Evaluator::new(&import);
let ast = AstModule::parse("prelude.bzl", "c = 3".to_owned(), &Dialect::Standard).unwrap();
let _: Value = eval.eval_module(ast, &globals)?;
let frozen_import = import.freeze()?;
let m_uses_public = Module::new();
m_uses_public.import_public_symbols(&frozen_import);
let mut eval = Evaluator::new(&m_uses_public);
let ast = AstModule::parse("code.bzl", "d = a".to_owned(), &Dialect::Standard).unwrap();
let _: Value = eval.eval_module(ast, &globals)?;
let m_uses_private = Module::new();
m_uses_private.import_public_symbols(&frozen_import);
let mut eval = Evaluator::new(&m_uses_private);
let ast = AstModule::parse("code.bzl", "d = b".to_owned(), &Dialect::Standard).unwrap();
let err = eval
.eval_module(ast, &globals)
.expect_err("Evaluation should have failed using a private symbol");
let msg = err.to_string();
let expected_msg = "Variable `b` not found";
assert!(
msg.contains(expected_msg),
"Expected `{}` to be in error message `{}`",
expected_msg,
msg
);
Ok(())
}
#[test]
fn test_load_did_you_mean() {
let mut a = Assert::new();
a.module("categories", "colour = 1");
a.fail(
"load('categories', 'color')",
"Module has no symbol `color`, did you mean `colour`?",
);
}
#[test]
fn test_getattr_did_you_mean_builtin() {
assert::fail(
"[].appen",
"Object of type `list` has no attribute `appen`, did you mean `append`?",
);
}
#[test]
fn test_getattr_did_you_mean_custom() {
assert::fail(
"struct(grey=1).gray",
"Object of type `struct` has no attribute `gray`, did you mean `grey`?",
);
assert::fail(
"record(grey=int.type)(grey=1).gray",
"Object of type `record` has no attribute `gray`, did you mean `grey`?",
);
}
#[test]
fn test_globals_did_you_mean() {
assert::fail("true", "Variable `true` not found, did you mean `True`?");
}
#[test]
fn test_module_level_did_you_mean() {
assert::fail(
"_x = 1; print(x)",
"Variable `x` not found, did you mean `_x`?",
);
}
#[test]
fn test_module_level_from_def_did_you_mean() {
assert::fail(
"def _func(): return func",
"Variable `func` not found, did you mean `_func`?",
);
}
#[test]
fn test_local_from_def_did_you_mean() {
assert::fail(
"def f(discreet): return discrete",
"Variable `discrete` not found, did you mean `discreet`?",
);
}
#[test]
fn test_compr_did_you_mean() {
assert::fail(
"[val for value in []]",
"Variable `val` not found, did you mean `value`?",
);
}
#[test]
fn test_unassigned() {
assert::fails("y = x; x = 1", &["referenced before assignment", "`x`"]);
assert::fails(
"def f():\n y = x; x = 1\nf()",
&["referenced before assignment", "`x`"],
);
assert::fails(
"
def f():
y = x
x = 1
def g(q = 1):
f()
g()",
&["referenced before assignment", "`x`"],
);
assert::fails(
"[1 for _ in [1] for y in y]",
&["referenced before assignment", "`y`"],
);
assert::fails(
"def f():\n [1 for _ in [1] for y in y]\nf()",
&["referenced before assignment", "`y`"],
);
}
#[test]
fn test_self_assign() {
assert::pass("x = [1,2]\na, x[0] = x");
assert::pass("x = {0:0,1:1}\na, x[0] = x");
}
#[test]
fn test_nested_loops() {
assert::pass(
r#"
def foo(y):
for x in [1,2,3,4]:
if x == 3:
return y
def bar(xs):
res = []
for x in xs:
if type(x) == type(1):
fail("Type confusion")
res.append(foo(x))
assert_eq(xs, res)
bar(["a","b","c"])
"#,
);
}
#[test]
fn test_label_assign() {
#[derive(Debug, Trace, AnyLifetime, Display, NoSerialize)]
#[display(fmt = "{:?}", self)]
struct Wrapper<'v>(RefCell<SmallMap<String, Value<'v>>>);
impl<'v> StarlarkValue<'v> for Wrapper<'v>
where
Self: AnyLifetime<'v>,
{
starlark_type!("wrapper");
fn get_attr(&self, attribute: &str, _heap: &'v Heap) -> Option<Value<'v>> {
Some(*self.0.borrow().get(attribute).unwrap())
}
fn set_attr(&self, attribute: &str, new_value: Value<'v>) -> anyhow::Result<()> {
self.0.borrow_mut().insert(attribute.to_owned(), new_value);
Ok(())
}
}
#[derive(Debug, AnyLifetime, Display, NoSerialize)]
#[display(fmt = "FrozenWrapper")]
struct FrozenWrapper;
impl<'v> StarlarkValue<'v> for FrozenWrapper {
starlark_type!("wrapper");
}
impl<'v> Freeze for Wrapper<'v> {
type Frozen = FrozenWrapper;
fn freeze(self, _freezer: &Freezer) -> anyhow::Result<Self::Frozen> {
Ok(FrozenWrapper)
}
}
#[starlark_module]
fn module<'v>(builder: &mut GlobalsBuilder) {
fn wrapper<'v>() -> anyhow::Result<Value<'v>> {
Ok(heap.alloc_complex(Wrapper(RefCell::new(SmallMap::new()))))
}
}
let mut a = Assert::new();
a.globals_add(module);
a.pass(
r#"
a = wrapper()
b = wrapper()
a.foo = 100
a.bar = 93
b.foo = 7
assert_eq(a.bar + b.foo, a.foo)
a.foo += 8
assert_eq(a.foo, 108)
count = []
def mk_wrapper():
count.append(1)
res = wrapper()
res.x = 9
return res
mk_wrapper().x += 5
assert_eq(len(count), 1)
"#,
);
}
#[test]
fn test_self_mutate_list() {
assert::is_true(
r#"
xs = [1, 2, 3]
xs.extend(xs)
xs == [1, 2, 3, 1, 2, 3]
"#,
);
assert::is_true(
r#"
xs = [1, 2, 3]
xs += xs
xs == [1, 2, 3, 1, 2, 3]
"#,
);
assert::fail(
r#"
xs = [1, 2, 3]
xs.pop(xs)
"#,
"not supported",
);
assert::fail(
r#"
xs = [1, 2, 3]
xs.remove(xs)
"#,
"not found in list",
);
assert::is_true(
r#"
xs = [1, 2, 3]
xs.append(xs)
xs.remove(xs)
xs == [1, 2, 3]
"#,
);
assert::is_true(
r#"
xs = [1, 2, 3]
xs += xs
xs == [1, 2, 3, 1, 2, 3]
"#,
);
assert::fail(
r#"
xs = []
xs[xs]
"#,
"Type of parameter",
);
assert::fail(
r#"
xs = []
xs[xs] = xs
"#,
"Type of parameter",
);
}
#[test]
fn test_self_mutate_dict() {
assert::is_true(
r#"
xs = {1: 2}
xs |= xs
xs == {1: 2}
"#,
);
assert::fail(
r#"
xs = {}
xs[xs]
"#,
"not hashable",
);
assert::fail(
r#"
xs = {}
xs[xs] = 1
"#,
"not hashable",
);
assert::is_true(
r#"
xs = {}
xs[1] = xs
len(xs[1]) == 1
"#,
);
assert::is_true(
r#"
xs = {}
xs.update(xs)
len(xs) == 0
"#,
);
}
#[test]
fn test_dict_union() {
assert::is_true(
r#"
xs = {1: 2, 3: 4}
xs |= {5: 6}
xs |= {1: 7}
xs.items() == [(1, 7), (3, 4), (5, 6)]
"#,
);
}
#[test]
fn test_dict_with_frozen_list_key_inlined() {
let mut a = Assert::new();
a.module(
"m.star",
"\
li = []
def f():
# This should fail at runtime.
return {li: 1}
",
);
a.fail(
"\
load('m.star', 'f')
f()
",
"Value of type `list` is not hashable",
);
}
#[test]
fn test_joe() {
let code = r#"
def animal(id):
return {
"kind": "giraffe",
"name": "giraffe-%s" % id,
"feeding": [
{
"name": "feeder",
"image": "photos-%s" % id,
"commands": [
"lift",
"roll-over",
],
},
],
}
animal("Joe")
"#;
let m = Module::new();
let globals = Globals::standard();
let mut eval = Evaluator::new(&m);
let ast = AstModule::parse("code.bzl", code.to_owned(), &Dialect::Standard).unwrap();
let res: Value = eval.eval_module(ast, &globals).unwrap();
let animal = SmallMap::<String, Value>::unpack_value(res).unwrap();
println!("animal = {:?}", animal);
}