/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Test for `def` and `lambda`.

use crate::assert;
use crate::assert::Assert;
use crate::environment::Module;
use crate::eval::Evaluator;

#[test]
fn test_lambda() {
    assert::is_true("(lambda x: x)(1) == 1");
    assert::is_true("(lambda x: (x == 1))(1)");
    assert::is_true(
        "
xs = [lambda x: x + y for y in [1,2,3]]
ys = [lambda x: x + y for y in [4,5,6]]
[xs[1](0),ys[1](0)] == [3,6]",
    );
}

#[test]
fn test_frozen_lambda() {
    let mut a = Assert::new();
    a.module(
        "lam",
        r#"
def my_func(a):
    return lambda b: a + b
add18 = my_func(18)
# This test used to fail if a GC happened, so add one
garbage_collect()
"#,
    );
    a.pass(
        r#"
load("lam", "add18")
assert_eq(add18(24), 42)
"#,
    );
}

#[test]
fn test_nested_def_1() {
    assert::is_true(
        "
def foo(x):
    def bar(y):
        return x+y
    return bar(x)
foo(8) == 16",
    );
}

#[test]
fn test_nested_def_2() {
    assert::is_true(
        "
def squarer():
    x = [0]
    def f():
        x[0] += 1
        return x[0]*x[0]
    return f
sq = squarer()
[sq(), sq(), sq(), sq()] == [1,4,9,16]",
    );
}

#[test]
fn test_nested_def_3() {
    assert::is_true(
        "
def f(x):
    def g(y):
        return lambda z: x + y + z
    return g
f(1)(2)(3) == 6",
    );
}

#[test]
fn test_lambda_capture_from_module() {
    assert::is_true(
        "
f = lambda y: x + y
x = 100
f(42) == 142
",
    );
}

#[test]
fn test_lambda_capture_from_def() {
    assert::is_true(
        "
def inside():
    f = lambda y: x + y
    x = 100
    return f(42) == 142
inside()
",
    );
}

#[test]
fn test_lambda_capture_reassigned_from_def() {
    assert::is_true(
        "
def inside():
    x = 100
    f = lambda y: x + y
    x = 200
    return f(42) == 242
inside()
",
    );
}

#[test]
fn test_def_freeze() {
    let mut a = Assert::new();
    a.module(
        "f.bzl",
        r#"
def f(g):
    g(1)"#,
    );
    a.is_true(
        r#"
load('f.bzl', 'f')
x = []
def g(y):
    x.append(y)
f(g)
x == [1]"#,
    );
}

#[test]
fn test_frozen_lambda_nest() {
    let mut a = Assert::new();
    let m = a.module(
        "a",
        r#"
def outer_function(x):
    return x["test"]

def function(x):
    def inner_function():
        return outer_function(x)
    return inner_function()

value = {"test": "hello"}
"#,
    );
    let f = m.get("function").unwrap();
    let x = m.get("value").unwrap();
    let module = Module::new();
    let mut eval = Evaluator::new(&module);
    let res = eval.eval_function(f.value(), &[x.value()], &[]).unwrap();
    assert_eq!(res.to_str(), "hello");
}

#[test]
fn test_context_captured() {
    let mut a = Assert::new();
    a.module("f.bzl", "x = 17\ndef f(): return x");
    // Import `f` but do not import `x`
    a.is_true("load('f.bzl', 'f')\nf() == 17");
}

#[test]
fn test_lambda_errors() {
    // Test from https://github.com/facebookexperimental/starlark-rust/issues/36
    assert::fail("lambda a,a:a", "duplicated parameter name");
}

#[test]
fn test_double_capture_and_freeze() {
    let mut a = Assert::new();
    a.module(
        "x.bzl",
        r#"
def f(x):
    # `x` is captured by `g` and then frozen.
    def g():
        # When `h` is instantiated, `x` is already captured and frozen.
        def h():
            return noop(x)
        return h

    return g

G = f(1)
     "#,
    );

    a.pass("load('x.bzl', 'G')\nG()");
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::assert;

#[test]
fn test_for_loop_bug_1() {
    assert::pass(
        r#"
def test(x):
    for i in x:
        # This should release mutation lock on `x`.
        return i

l = [1]
test(l)
l.append(1)
"#,
    );
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::values::list::ListRef;
use crate::values::Freeze;
use crate::values::Freezer;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::Value;

struct Test<V> {
    field: V,
}

impl<'v> Freeze for Test<Value<'v>> {
    type Frozen = Test<FrozenValue>;

    fn freeze(self, freezer: &Freezer) -> anyhow::Result<Self::Frozen> {
        let test = Test {
            field: self.field.freeze(freezer)?,
        };
        let members = ListRef::from_value(test.field.to_value()).unwrap();
        assert_eq!(members[0].unpack_num().unwrap().as_int().unwrap(), 1);
        assert_eq!(members[1].unpack_num().unwrap().as_int().unwrap(), 2);
        Ok(test)
    }
}

#[test]
fn test() -> anyhow::Result<()> {
    let heap = Heap::new();
    let list = heap.alloc(vec![1i32, 2i32]);

    let t = Test { field: list };

    let freezer = Freezer::new(FrozenHeap::new());
    list.freeze(&freezer)?;
    t.freeze(&freezer)?;

    Ok(())
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Run Go implementation tests.

use itertools::Itertools;

use crate::assert;
use crate::assert::Assert;

#[test]
fn test_go() {
    macro_rules! test_case {
        ($name:expr) => {
            include_str!(concat!(
                env!("CARGO_MANIFEST_DIR"),
                "/testcases/eval/go/",
                $name,
            ))
        };
    }

    fn ignore_bad_lines(x: &str, bad: &[&str]) -> String {
        x.lines()
            .filter(|x| !bad.iter().any(|b| x.contains(b)))
            .join("\n")
    }

    // The data for these tests was taken from
    // https://github.com/google/starlark-go/blob/e81fc95f7bd5bb1495fe69f27c1a99fcc77caa48/starlark/testdata/

    let mut assert = Assert::new();
    // TODO(nga): fix and enable.
    assert.disable_static_typechecking();
    assert.conformance_except(
        test_case!("assign.star"),
        &[
            "hasfields()", // Not sure what this is, but we don't support
        ],
    );
    // Skip benchmark.star, for benchmarking not testing
    assert.conformance(test_case!("bool.star"));
    assert.conformance(&ignore_bad_lines(
        test_case!("builtin.star"),
        &[
            "[] not in {123: \"\"}", // We disagree, see test_not_in_unhashable
            // Set, unsupported
            "set(",
            "(myset)",
            "(myset,",
            // Has fields, unsupported
            "hasfields()",
            "(hf)",
            "(hf,",
            "(hf,",
            "hf.",
            "(setfield,",
            // test_in_range
            "True in range(3)",
            "\"one\" in range(10)",
            // We added copy, which throws off the assert
            "dir({})[:3]",
            "dir([])[:3]",
            // We do not support range to `i32::MAX` on 32 bit.
            "range(0x7fffffff)",
        ],
    ));
    assert.conformance(test_case!("control.star"));
    assert.conformance_except(
        &ignore_bad_lines(
            test_case!("dict.star"),
            &[
                "unknown binary op: dict \\\\+ dict",   // We support {} + {}
                "cannot insert into frozen hash table", // We don't actually have freeze
                "cannot clear frozen hash table",
                "asserts.eq(a, 1)", // End of the test above
                "asserts.eq(x, {1: 2, 2: 4, 0: 2})",
                "x9a", // Starlark spec does not allow test list in index expression
            ],
        ),
        &[
            "Verify position of an \"unhashable key\"", // FIXME: We should give a better error message
            "Verify position of a \"duplicate key\"",   // FIXME: Give a better line number
            "Verify position of an \"unhashable key\"", // FIXME: we should do better
        ],
    );
    assert.conformance(&ignore_bad_lines(
        test_case!("float.star"),
        &[
            // int's outside our range
            "1229999999999999973",
            "9223372036854775808",
            "1000000000000000000",
            "1230000000000000049",
            "9223372036854775807",
            "p53",
            "maxint64",
            "int too large to convert to float",
            "int(1e100)",
            "1000000 * 1000000 * 1000000",
            "int overflow in starlark-rust",
        ],
    ));
    assert.conformance(&ignore_bad_lines(
        test_case!("function.star"),
        &[
            "eq(str",             // We render function names differently
            "frozen list",        // Our freeze does nothing
            "called recursively", // We allow recursion
            "hf",                 // We don't support hasfield
        ],
    ));
    // Skip int.star, a lot of bit mask stuff, floats and int's outside our range
    // Skip list.star, our strings disagree about whether they are lists of codepoints or lists of 1-char strings
    assert.conformance_except(
        &ignore_bad_lines(
            test_case!("misc.star"),
            &[
                "'<built-in function freeze>'", // Different display of functions
            ],
        ),
        &[
            "cyclic data structures", // We overflow on some of these
        ],
    );
    // Skip module.star, we don't support modules
    // Skip paths.star, a path support library, not tests
    // Skip recursion.star, we don't support `while` loops, which is what this mostly tests
    // Skip set.star, we don't support set
    // Skip string.star, our String's are fundamentally different
    assert.conformance(&ignore_bad_lines(
        test_case!("tuple.star"),
        &[
            "1000000 * 1000000", // Some tests check that you can't create too large tuples, but that's not principled, so we allow it
                                 // But it takes approximately forever, so doing it is a bad idea.
        ],
    ));
}

#[test]
fn test_in_range() {
    // Go Starlark considers this a type error (I think that is a mistake)
    assert::all_true(
        r#"
not (True in range(3))
not ("one" in range(10))
"#,
    );
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::env;
use std::fmt::Write;
use std::fs;

use anyhow::Context;

const REGENERATE_VAR_NAME: &str = "STARLARK_RUST_REGENERATE_GOLDEN_TESTS";

#[allow(clippy::write_literal)] // We mark generated files as generated, but not this file.
fn make_golden(output: &str) -> String {
    let mut golden = String::new();
    writeln!(golden, "# {at}generated", at = "@").unwrap();
    writeln!(golden, "# To regenerate, run:").unwrap();
    writeln!(golden, "# ```").unwrap();
    writeln!(
        golden,
        "# {REGENERATE_VAR_NAME}=1 cargo test -p starlark --lib tests"
    )
    .unwrap();
    writeln!(golden, "# ```").unwrap();
    writeln!(golden).unwrap();
    writeln!(golden, "{}", output.trim_end()).unwrap();
    golden
}

/// Common code for golden tests.
pub(crate) fn golden_test_template(golden_rel_path: &str, output: &str) {
    assert!(golden_rel_path.starts_with("src/"));
    assert!(golden_rel_path.contains(".golden"));

    let manifest_dir =
        env::var("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` variable must be set");

    let golden_file_path = format!("{manifest_dir}/{golden_rel_path}");

    let output_with_prefix = make_golden(output);

    if env::var(REGENERATE_VAR_NAME).is_ok() {
        fs::write(golden_file_path, &output_with_prefix).unwrap();
    } else {
        let expected = fs::read_to_string(&golden_file_path)
            .with_context(|| format!("Reading `{golden_file_path}`"))
            .unwrap();

        let expected = if cfg!(windows) {
            // Git may check out files on Windows with \r\n as line separator.
            // We could configure git, but it's more reliable to handle it in the test.
            expected.replace("\r\n", "\n")
        } else {
            expected
        };
        assert_eq!(expected, output_with_prefix);
    }
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Test starlark-rust embedding.

use std::sync::Arc;
use std::sync::Mutex;

use allocative::Allocative;
use derive_more::Display;
use starlark_derive::starlark_module;
use starlark_derive::starlark_value;
use starlark_derive::Trace;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::assert;
use crate::assert::Assert;
use crate::collections::SmallMap;
use crate::environment::GlobalsBuilder;
use crate::environment::Module;
use crate::eval::Evaluator;
use crate::syntax::AstModule;
use crate::syntax::Dialect;
use crate::values::any::StarlarkAny;
use crate::values::none::NoneType;
use crate::values::types::exported_name::ExportedName;
use crate::values::types::exported_name::MutableExportedName;
use crate::values::Freeze;
use crate::values::NoSerialize;
use crate::values::StarlarkValue;
use crate::values::Value;
use crate::values::ValueLike;

#[test]
fn test_export_as() {
    use std::fmt;
    use std::fmt::Debug;
    use std::fmt::Display;

    use crate as starlark;
    use crate::any::ProvidesStaticType;
    use crate::values::AllocValue;
    use crate::values::Heap;
    use crate::values::StarlarkValue;
    use crate::values::Trace;
    use crate::values::Value;

    #[derive(Debug, Trace, ProvidesStaticType, NoSerialize, Allocative, Freeze)]
    #[allocative(skip)]
    struct Exporter<T: ExportedName> {
        named: T,
        value: i32,
    }

    impl<T: ExportedName> Display for Exporter<T> {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "{}={}", self.named, self.value)
        }
    }

    #[starlark_value(type = "exporter")]
    impl<'v, T: ExportedName> StarlarkValue<'v> for Exporter<T>
    where
        Self: ProvidesStaticType<'v>,
    {
        fn export_as(&self, variable_name: &str, _eval: &mut Evaluator<'v, '_>) {
            self.named.try_export_as(variable_name);
        }
    }

    impl AllocValue<'_> for Exporter<MutableExportedName> {
        fn alloc_value(self, heap: &Heap) -> Value {
            heap.alloc_complex(self)
        }
    }

    #[starlark_module]
    fn exporter(builder: &mut GlobalsBuilder) {
        fn exporter(value: i32) -> anyhow::Result<Exporter<MutableExportedName>> {
            Ok(Exporter {
                named: MutableExportedName::default(),
                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)]",
    );
    // could reasonably be x=1 or y=1 twice, since the order
    // of calls to export_as is not defined
    let opt1 = "(x=1, x=1, longer_name=2, <not_exported>=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]
// Test that we can express something that loads symbols into the exported module
fn test_load_symbols() {
    #[starlark_module]
    fn module(builder: &mut GlobalsBuilder) {
        fn load_symbol<'v>(
            name: &str,
            value: Value<'v>,
            eval: &mut Evaluator<'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()?);
    // Trying to load a symbol transitively should fail.
    a.fail("load('a', 'x')", "Module symbol `x` is not exported");
    Ok(())
}

#[test]
// Test that we can express something that loads symbols into the exported module,
// but not using the very dubious `set_module_variable_at_some_point`.
fn test_load_symbols_extra() -> anyhow::Result<()> {
    #[starlark_module]
    fn module(builder: &mut GlobalsBuilder) {
        fn load_symbol<'v>(
            name: &str,
            value: Value<'v>,
            eval: &mut Evaluator<'v, '_>,
        ) -> anyhow::Result<NoneType> {
            let extra = eval
                .module()
                .extra_value()
                .unwrap()
                .downcast_ref::<Extra<'v>>()
                .unwrap();
            extra.0.lock().unwrap().insert(name.to_owned(), value);
            Ok(NoneType)
        }
    }

    #[derive(
        ProvidesStaticType,
        Default,
        Trace,
        Debug,
        derive_more::Display,
        NoSerialize,
        Allocative
    )]
    #[display(fmt = "{:?}", self)]
    struct Extra<'v>(Arc<Mutex<SmallMap<String, Value<'v>>>>);

    #[starlark_value(type = "Extra")]
    impl<'v> StarlarkValue<'v> for Extra<'v> {}

    let modu = Module::new();
    let globals = GlobalsBuilder::extended().with(module).build();
    {
        let mut eval = Evaluator::new(&modu);
        modu.set_extra_value(eval.heap().alloc_complex_no_freeze(Extra::default()));
        eval.eval_module(
            AstModule::parse("a", "load_symbol('x', 6*7)".to_owned(), &Dialect::Extended)?,
            &globals,
        )?;
    }

    let extra = modu.extra_value().unwrap().downcast_ref::<Extra>().unwrap();
    for (name, value) in extra.0.lock().unwrap().iter() {
        modu.set(name, *value);
    }
    modu.set_extra_value(Value::new_none());
    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(ProvidesStaticType, 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_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::testing_new_int(8)], &[("y", hello)])
        .unwrap();
    assert_eq!(v.unpack_str(), Some("(8, \"hello\", 1)"))
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

mod basic;
mod bc;
mod before_stmt;
mod call;
mod comprehension;
mod def;
mod derive;
mod docs;
mod for_loop;
mod freeze_access_value;
mod go;
pub(crate) mod golden_test_template;
mod interop;
mod opt;
mod runtime;
mod type_annot;
mod uncategorized;


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Test of runtime.

use std::fmt::Write;
use std::mem;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

use derive_more::Display;
use once_cell::sync::Lazy;
use starlark_derive::starlark_module;

use crate as starlark;
use crate::assert;
use crate::assert::Assert;
use crate::environment::GlobalsBuilder;
use crate::eval::Evaluator;
use crate::values::any::StarlarkAny;
use crate::values::FrozenHeap;
use crate::values::Heap;

#[test]
fn test_garbage_collect() {
    assert::pass(
        r#"
x = (100, [{"test": None}], True)
y = str(x)
garbage_collect()
assert_eq(y, str(x))
    "#,
    );
}

#[test]
fn test_deallocation() {
    // Check that we really do deallocate values we create
    static COUNT: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(0));

    #[derive(Default, Debug, Display)]
    struct Dealloc;

    impl Drop for Dealloc {
        fn drop(&mut self) {
            COUNT.fetch_add(1, Ordering::SeqCst);
        }
    }

    #[starlark_module]
    fn globals(builder: &mut GlobalsBuilder) {
        fn mk() -> anyhow::Result<StarlarkAny<Dealloc>> {
            Ok(StarlarkAny::new(Dealloc))
        }
    }

    COUNT.store(0, Ordering::SeqCst);
    let mut a = Assert::new();
    a.disable_gc();
    a.globals_add(globals);
    a.module("test", "x = [mk(), mk()]\ndef y(): return mk()");
    a.pass(
        r#"
load("test", "x", "y")
z = x[1]
q = mk()
r = [y(), mk()]
"#,
    );
    // The three that were run in pass should have gone
    assert_eq!(COUNT.load(Ordering::SeqCst), 3);
    mem::drop(a);
    // Now the frozen ones should have gone too
    assert_eq!(COUNT.load(Ordering::SeqCst), 5);
}

#[test]
fn test_stack_depth() {
    #[starlark_module]
    fn measure_stack(builder: &mut GlobalsBuilder) {
        fn stack_depth() -> anyhow::Result<String> {
            // Put a variable on the stack, and get a reference to it
            // Not entirely documented as to what this does, but hopefully
            // a mut pointer is harder to elide or optimise away
            let mut s = 1i32;
            Ok((&mut s as *mut i32 as usize).to_string())
        }
    }

    let mut a = Assert::new();
    a.globals_add(measure_stack);
    let s = a.pass(
        r#"
for i in range(1001):
    if i == 1:
        v1 = stack_depth()
    if i == 100:
        v100 = stack_depth()
    elif i == 1000:
        v1000 = stack_depth()
v1 + " " + v100 + " " + v1000
"#,
    );
    let s = s.unpack_str().unwrap();
    let words = s
        .split(' ')
        .map(|x| x.parse::<isize>().unwrap())
        .collect::<Vec<_>>();
    let v1 = words[0];
    let v100 = words[1];
    let v1000 = words[2];

    // We want to ensure they don't keep increasing, as that would be very bad
    // so ensure that the increase from v0 to v100 is less than the increase from v100 to v1000
    // with a 1000 for random noise.
    assert!(
        (v1 - v100).abs() + 1000 >= (v1000 - v100).abs(),
        "Stack change exceeded, FAILED {} + 1000 >= {} (relative to v1), 100={}, 1000={}",
        (v1 - v100).abs(),
        (v1000 - v100).abs(),
        v100 - v1,
        v1000 - v1
    );
}

#[test]
fn test_garbage_collect_happens() {
    // GC is meant to be "not observable", but if we break it, we want this test to fail
    #[starlark_module]
    fn helpers(builder: &mut GlobalsBuilder) {
        fn current_usage(heap: &Heap) -> anyhow::Result<i32> {
            Ok(heap.allocated_bytes() as i32)
        }

        fn is_gc_disabled(eval: &mut Evaluator) -> anyhow::Result<bool> {
            Ok(eval.disable_gc)
        }
    }

    let mut a = Assert::new();
    a.globals_add(helpers);

    // Approach is to keep doing something expensive, and we want to see the memory usage decrease.
    let mut code = r#"
globals = []
maximum = [0]
success = [is_gc_disabled()]

def update_maximum():
    maximum[0] = max(current_usage(), maximum[0])

def expensive(n):
    if success[0]:
        return
    now = current_usage()
    if now < maximum[0]:
        print("Success in " + str(n))
        success[0] = True
        return
    update_maximum()
    globals.append(str(n))
    locals = []
    for i in range(10 * n):
        locals.append(str(i))
    update_maximum()
"#
    .to_owned();
    // I expect success in approx 25 times, so do 100 for safety
    for i in 0..100 {
        writeln!(code, "expensive({})", i).unwrap();
    }
    code.push_str("assert_eq(success[0], True)\nis_gc_disabled()");
    // I expect to run with GC disabled some of the time, but not on the last run
    // so make sure at least once GC was enabled
    assert!(!a.pass(&code).unpack_bool().unwrap());
}

#[test]
fn test_callstack() {
    // Make sure that even for native functions that fail, the
    // name of the function is on the call stack.
    let d = assert::fail(
        r#"
def f():
    fail("bad")
f()
"#,
        "bad",
    );
    assert!(d.to_string().contains("fail(\"bad\")"));
}

#[test]
fn test_display_debug() {
    let heap = Heap::new();
    let val = heap.alloc((vec![1, 2], "test", true));
    assert_eq!(format!("{}", val), "([1, 2], \"test\", True)");
    assert_eq!(val.to_repr(), "([1, 2], \"test\", True)");
    assert_eq!(val.to_str(), "([1, 2], \"test\", True)");
    assert_eq!(
        format!("{:?}", val),
        "Value(TupleGen { content: [Value(ListGen(ListData { content: Cell { value: ValueTyped(Value(Array { len: 2, capacity: 4, iter_count: 0, content: [Value(1), Value(2)] })) } })), Value(\"test\"), Value(StarlarkBool(true))] })"
    );
    let v = heap.alloc("test");
    assert_eq!(format!("{}", v), "\"test\"");
    assert_eq!(v.to_repr(), "\"test\"");
    assert_eq!(v.to_str(), "test");
    assert_eq!(format!("{:?}", v), "Value(\"test\")");
    assert_eq!(format!("{:#?}", v), "Value(\n    \"test\",\n)");

    let frozen_heap = FrozenHeap::new();
    let v = frozen_heap.alloc("test");
    assert_eq!(format!("{}", v), "\"test\"");
    assert_eq!(v.to_value().to_repr(), "\"test\"");
    assert_eq!(v.to_value().to_str(), "test");
    assert_eq!(format!("{:?}", v), "FrozenValue(\"test\")");
    assert_eq!(format!("{:#?}", v), "FrozenValue(\n    \"test\",\n)");
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::assert;
use crate::assert::Assert;
use crate::syntax::Dialect;
use crate::syntax::DialectTypes;

const PROGRAM: &str = "\
def f(x: int.type): pass

f(noop('s'))
";

#[test]
fn test_types_disable() {
    let mut dialect = Dialect::Standard;
    dialect.enable_types = DialectTypes::Disable;
    let mut a = Assert::new();
    a.dialect(&dialect);
    a.fail(PROGRAM, "type annotations are not allowed in this dialect");
}

#[test]
fn test_types_parse_only() {
    let mut dialect = Dialect::Standard;
    dialect.enable_types = DialectTypes::ParseOnly;
    let mut a = Assert::new();
    a.dialect(&dialect);
    a.pass(PROGRAM);
}

#[test]
fn test_types_enable() {
    let mut dialect = Dialect::Standard;
    dialect.enable_types = DialectTypes::Enable;
    let mut a = Assert::new();
    a.dialect(&dialect);
    a.fail(PROGRAM, "does not match the type annotation");
}

#[test]
fn test_type_assign_annotation() {
    assert::pass(
        r#"
x : str.type = "test"
xs: "" = [1,2]
xs[0] : int.type = 4
"#,
    );
    assert::fail("a, b : '' = 1, 2", "not allowed on multiple assignments");
    assert::fail("a = 1\na : '' += 1", "not allowed on augmented assignments");
    assert::fail(
        "a : str.type = noop(1)",
        "does not match the type annotation",
    );
}

#[test]
fn test_only_globals_or_bultins_allowed() {
    assert::fail(
        r#"
def f():
    x = "str"
    def g(p: x): pass
"#,
        "Identifiers in type expressions can only refer globals or builtins: `x`",
    );
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::cell::RefCell;
use std::fmt::Write;

use allocative::Allocative;
use anyhow::Context;
use derive_more::Display;
use starlark_derive::starlark_module;
use starlark_derive::starlark_value;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::assert;
use crate::assert::Assert;
use crate::collections::SmallMap;
use crate::environment::Globals;
use crate::environment::GlobalsBuilder;
use crate::environment::Module;
use crate::errors::Diagnostic;
use crate::eval::Evaluator;
use crate::starlark_simple_value;
use crate::syntax::AstModule;
use crate::syntax::Dialect;
use crate::values::none::NoneType;
use crate::values::Freeze;
use crate::values::Freezer;
use crate::values::Heap;
use crate::values::NoSerialize;
use crate::values::StarlarkValue;
use crate::values::Trace;
use crate::values::UnpackValue;
use crate::values::Value;

#[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 a = Assert::new();
    a.fail("def f():\n\tpass", "Parse error");
    a.fail("def f():\n x\t=3", "Parse error");
}

#[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}",
    );
    // This test breaks if we compile constants deep compile the literals
    // and don't deep thaw them
    assert::is_true(
        "
def f():
    return [[]]
y = f()[0]
y.append(1)
y == [1]",
    );
}

#[test]
fn test_frozen_iteration() {
    // nested 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"#,
    );
    // iterate, mutate, iterate
    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"#,
    );
    // iterate and mutate at the same time
    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() {
    // += behaves differently on different types
    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
# TODO(nga): typechecker should accept it.
x = noop(x)
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() {
    // We want select append to always produce a select, much like the
    // Bazel/Buck `select` function.
    #[derive(Debug, Display, Clone, ProvidesStaticType, NoSerialize, Allocative)]
    #[display(fmt = "${:?}", _0)]
    struct Select(Vec<i32>);
    starlark_simple_value!(Select);

    impl<'v> UnpackValue<'v> for Select {
        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
        }
    }

    #[starlark_value(type = "select")]
    impl<'v> StarlarkValue<'v> for 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]
// Tests diagnostics error display.
//
// > EYEBALL=1 cargo test -p starlark diagnostics_display -- --nocapture
fn test_diagnostics_display() {
    fn fail1() -> anyhow::Result<()> {
        Err(anyhow::anyhow!("fail 1"))
    }

    fn fail2() -> anyhow::Result<()> {
        fail1().context("fail 2")
    }

    fn fail3() -> anyhow::Result<()> {
        fail2().context("fail 3")
    }

    #[starlark_module]
    fn module(builder: &mut GlobalsBuilder) {
        fn rust_failure() -> anyhow::Result<NoneType> {
            fail3().context("rust failure")?;
            Ok(NoneType)
        }
    }

    let display = std::env::var("EYEBALL") == Ok("1".to_owned());
    let mut a = Assert::new();
    a.globals_add(module);

    a.module(
        "imported",
        r#"
# blank lines to make line numbers bigger and more obvious
#
#
#
#
x = []
def should_fail():
    rust_failure()"#,
    );

    let err = a.fail(
        r#"
load('imported', 'should_fail')
should_fail()"#,
        "rust failure",
    );

    if display {
        Diagnostic::eprint(&err);
    }

    let diag = err.downcast::<Diagnostic>().unwrap();

    // Checking using `contains()` because `RUST_BACKTRACE` is set to `full` for CI, and we
    // only want to validate that the `Caused by` is printed out here.
    assert!(format!("\n{}", diag).contains(
        r#"
Traceback (most recent call last):
  * assert.bzl:3, in <module>
      should_fail()
  * imported.bzl:9, in should_fail
      rust_failure()
error: rust failure
 --> imported.bzl:9:5
  |
9 |     rust_failure()
  |     ^^^^^^^^^^^^^^
  |

rust failure

Caused by:
    0: fail 3
    1: fail 2
    2: fail 1
"#
    ));
    assert!(format!("\n{:#}", diag).contains(
        r#"
Traceback (most recent call last):
  * assert.bzl:3, in <module>
      should_fail()
  * imported.bzl:9, in should_fail
      rust_failure()
error: rust failure
 --> imported.bzl:9:5
  |
9 |     rust_failure()
  |     ^^^^^^^^^^^^^^
  |

rust failure

Caused by:
    0: fail 3
    1: fail 2
    2: fail 1
"#
    ));
}

#[test]
// Check that errors print out "nicely" - can be used to view it.
// First set `display` to `true` then run:
//
// > EYEBALL=1 cargo test -p starlark eyeball -- --nocapture
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<()> {
    // Make sure that when we use a module in the evaluator, the entering / exiting the
    // module with ScopeData preserves the visibility of symbols.

    let globals = Globals::standard();

    let import = Module::new();
    import.set("a", Value::testing_new_int(1));
    import.set_private(
        import.frozen_heap().alloc_str_intern("b"),
        Value::testing_new_int(2),
    );

    {
        let mut eval = Evaluator::new(&import);
        let ast = AstModule::parse("prelude.bzl", "c = 3".to_owned(), &Dialect::Standard).unwrap();
        // This mutates the original module named `import`
        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(
        "noop(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() {
    // Starlark spec is not clear whether it is allowed or not.
    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() {
    // Nested loops with returns used to cause problems in some cases, add a test
    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() {
    // Test the a.b = c construct.
    // No builtin Starlark types support it, so we have to define a custom type (wapping a dictionary)

    #[derive(Debug, Trace, ProvidesStaticType, Display, NoSerialize, Allocative)]
    #[display(fmt = "{:?}", self)]
    struct Wrapper<'v>(RefCell<SmallMap<String, Value<'v>>>);

    #[starlark_value(type = "wrapper")]
    impl<'v> StarlarkValue<'v> for Wrapper<'v> {
        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, ProvidesStaticType, Display, NoSerialize, Allocative)]
    #[display(fmt = "FrozenWrapper")]
    struct FrozenWrapper;

    #[starlark_value(type = "wrapper")]
    impl<'v> StarlarkValue<'v> for FrozenWrapper {}

    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>(heap: &'v Heap) -> 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() {
    // Check functions that mutate and access self on lists
    let mut a = Assert::new();
    // TODO(nga): fix and enable.
    a.disable_static_typechecking();
    a.is_true(
        r#"
xs = [1, 2, 3]
xs.extend(xs)
xs == [1, 2, 3, 1, 2, 3]
"#,
    );
    a.is_true(
        r#"
xs = [1, 2, 3]
xs += xs
xs == [1, 2, 3, 1, 2, 3]
"#,
    );
    a.fail(
        r#"
xs = [1, 2, 3]
xs.pop(xs)
"#,
        "Type of parameter `index` doesn't match",
    );
    a.fail(
        r#"
xs = [1, 2, 3]
xs.remove(xs)
"#,
        "not found in list",
    );
    a.is_true(
        r#"
xs = [1, 2, 3]
xs.append(xs)
xs.remove(xs)
xs == [1, 2, 3]
"#,
    );
    a.is_true(
        r#"
xs = [1, 2, 3]
xs += xs
xs == [1, 2, 3, 1, 2, 3]
"#,
    );
    a.fail(
        r#"
xs = []
xs[xs]
"#,
        "Type of parameter",
    );
    a.fail(
        r#"
xs = []
xs[xs] = xs
"#,
        "Type of parameter",
    );
}

#[test]
fn test_self_mutate_dict() {
    // Check functions that mutate and access self on dicts
    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() {
    // Based on discussions at https://github.com/facebookexperimental/starlark-rust/issues/22
    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);
}

#[test]
fn test_fuzzer_59102() {
    // From https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=59102
    let src = "\"\u{e0070}";
    let res: Result<AstModule, anyhow::Error> =
        AstModule::parse("hello_world.star", src.to_owned(), &Dialect::Standard);
    // The panic actually only happens when we format the result
    format!("{:?}", res);
}

#[test]
fn test_fuzzer_59371() {
    // From https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=59371
    let src = "\"\u{2009}\\x";
    let res: Result<AstModule, anyhow::Error> =
        AstModule::parse("hello_world.star", src.to_owned(), &Dialect::Standard);
    // The panic actually only happens when we format the result
    format!("{:?}", res);
}

#[test]
fn test_fuzzer_59839() {
    // From https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=59839
    let src = "\"{20000000000000000396}\".format()";
    let ast = AstModule::parse("hello_world.star", src.to_owned(), &Dialect::Standard).unwrap();
    let globals: Globals = Globals::standard();
    let module: Module = Module::new();
    let mut eval: Evaluator = Evaluator::new(&module);
    assert!(eval.eval_module(ast, &globals).is_err());
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::tests::bc::golden::bc_golden_test;

#[test]
fn test_x_and_true() {
    bc_golden_test("and_or_x_and_true", "def test(x): return x and True");
}

#[test]
fn test_x_and_false() {
    bc_golden_test("and_or_x_and_false", "def test(x): return x and False");
}

#[test]
fn test_x_or_true() {
    bc_golden_test("and_or_x_or_true", "def test(x): return x or True");
}

#[test]
fn test_x_or_false() {
    bc_golden_test("and_or_x_or_false", "def test(x): return x or False");
}

#[test]
fn test_true_and_x() {
    bc_golden_test("and_or_true_and_x", "def test(x): return True and x");
}

#[test]
fn test_false_and_x() {
    bc_golden_test("and_or_false_and_x", "def test(x): return False and x");
}

#[test]
fn test_true_or_x() {
    bc_golden_test("and_or_true_or_x", "def test(x): return True or x");
}

#[test]
fn test_false_or_x() {
    bc_golden_test("and_or_false_or_x", "def test(x): return False or x");
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Test compilation of comprehensions.

use crate::tests::bc::golden::bc_golden_test;

#[test]
fn test_no_loop_if_top_collection_is_empty() {
    bc_golden_test(
        "compr_no_loop_if_top_collection_is_empty",
        "def test(): return [x for x in []]",
    );
}

#[test]
fn test_no_loop_if_top_collection_is_empty_on_freeze() {
    // This function is not optimized to return a list on compilation,
    // because `L` is not evaluated yet.
    // But it eliminates the loop on freeze.
    bc_golden_test(
        "compr_no_loop_if_top_collection_is_empty_on_freeze",
        "def test(): return [x for x in D]\nD = {}",
    );
}

#[test]
fn test_if_true_clause() {
    bc_golden_test(
        "compr_if_true_clause",
        "def test(y): return [x for x in y if True]",
    );
}

#[test]
fn test_if_true_clause_on_freeze() {
    bc_golden_test(
        "compr_if_true_clause_on_freeze",
        "def test(y): return [x for x in y if C]\nC = False\nC = True",
    );
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::assert::Assert;
use crate::tests::bc::golden::bc_golden_test;

/// Test for bug reported in D36808160.
#[test]
fn test_definitely_assigned_bug() {
    let mut a = Assert::new();
    a.module(
        "a.star",
        r"
def f(a):
  # The bug was: compilation of `and` expression reassigned `10` to `a`.
  noop(a and 10)
  return a
",
    );
    a.eq("33", "load('a.star', 'f')\nf(33)");
}

#[test]
fn test_definitely_assigned_slot_range_in_list() {
    bc_golden_test(
        "definitely_assigned_slot_range_in_list",
        "def test(x, y): return [x, y]",
    );
}

#[test]
fn test_definitely_assigned_slot_range_in_call() {
    bc_golden_test(
        "definitely_assigned_slot_range_in_call",
        "def test(x, y): noop(x, y)",
    );
}

#[test]
fn test_mov_is_used() {
    // `Mov`, not `LoadLocal` should be used to load `y` and `x`.
    bc_golden_test(
        "definitely_assigned_mov_is_used",
        "def test(x, y): noop(y, x)",
    );
}

#[test]
fn test_no_op_movs() {
    bc_golden_test("definitely_assigned_no_op_movs", "def test(x): x = x");
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::assert;
use crate::tests::bc::golden::bc_golden_test;

#[test]
fn test_type() {
    bc_golden_test("expr_type", "def test(x): return type(x)");
}

#[test]
fn test_percent_s_one() {
    bc_golden_test("expr_percent_s_one", "def test(x): return '((%s))' % x");
}

#[test]
fn test_format_one() {
    bc_golden_test("expr_format_one", "def test(x): return '(({}))'.format(x)");
}

#[test]
fn test_percent_s_one_format_one_eval() {
    assert::pass(
        r#"
load("asserts.star", "asserts")

def test(x):
    return ("<{}>".format(x), "<%s>" % x)

asserts.eq(("<1>", "<1>"), test(1))
# Test format does not accidentally call `PercentSOne`.
asserts.eq(("<(1,)>", "<1>"), test((1,)))
"#,
    );
}

#[test]
fn test_spec_exec_list() {
    // `list` function is const-evaluated and the resulting list is compiled as list instruction.
    bc_golden_test("expr_spec_exec_list", "def test(): return list((10, 20))");
}

#[test]
fn test_call_maybe_known_method() {
    bc_golden_test("expr_call_maybe_known_method", "def test(x): x.append(1)");
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::tests::bc::golden::bc_golden_test;

#[test]
fn test_for() {
    bc_golden_test("for", "def test(x):\n  for i in x:\n    noop(i)");
}

#[test]
fn test_for_break() {
    bc_golden_test(
        "for_break",
        "def test(x):\n  for i in x:\n    if i: break\n    noop(i)",
    );
}

#[test]
fn test_for_continue() {
    bc_golden_test(
        "for_continue",
        "def test(x):\n  for i in x:\n    if i: continue\n    noop(i)",
    );
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::fmt::Write;
use std::mem;

use crate::assert::Assert;
use crate::eval::compiler::def::FrozenDef;
use crate::tests::golden_test_template::golden_test_template;

fn test_function_bytecode(program: &str) -> String {
    let program = program.trim();

    let mut a = Assert::new();
    let def = a
        .module("instrs.star", program)
        .get("test")
        .unwrap()
        .downcast::<FrozenDef>()
        .unwrap();

    let mut golden = String::new();
    writeln!(golden, "{}", program).unwrap();
    writeln!(golden).unwrap();
    writeln!(golden, "# Bytecode:").unwrap();
    writeln!(golden).unwrap();
    writeln!(golden, "{}", def.bc().dump_debug().trim()).unwrap();
    golden
}

pub(crate) fn bc_golden_test(test_name: &str, program: &str) {
    if mem::size_of::<usize>() != mem::size_of::<u64>() {
        // Bytecode addresses are different on 32-bit platforms.
        // TODO(nga): still run evaluation on 32-bit platforms, without comparison.
        return;
    }

    let output = test_function_bytecode(program);

    golden_test_template(&format!("src/tests/bc/golden/{test_name}.golden"), &output);
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::tests::bc::golden::bc_golden_test;

#[test]
fn test_if_x_and_true() {
    bc_golden_test(
        "if_stmt_if_x_and_true",
        "def test(x):\n  if x and True: noop()",
    );
}

#[test]
fn test_if_x_and_false() {
    bc_golden_test(
        "if_stmt_if_x_and_false",
        "def test(x):\n  if x and False: noop()",
    );
}

#[test]
fn test_if_x_or_true() {
    bc_golden_test(
        "if_stmt_if_x_or_true",
        "def test(x):\n  if x or True: noop()",
    );
}

#[test]
fn test_if_x_or_false() {
    bc_golden_test(
        "if_stmt_if_x_or_false",
        "def test(x):\n  if x or False: noop()",
    );
}

#[test]
fn test_if_true_and_x() {
    bc_golden_test(
        "if_stmt_if_true_and_x",
        "def test(x):\n  if True and x: noop()",
    );
}

#[test]
fn test_if_false_and_x() {
    bc_golden_test(
        "if_stmt_if_false_and_x",
        "def test(x):\n  if False and x: noop()",
    );
}

#[test]
fn test_if_true_or_x() {
    bc_golden_test(
        "if_stmt_if_true_or_x",
        "def test(x):\n  if True or x: noop()",
    );
}

#[test]
fn test_if_false_or_x() {
    bc_golden_test(
        "if_stmt_if_false_or_x",
        "def test(x):\n  if False or x: noop()",
    );
}

#[test]
fn test_if_else_x_and_y() {
    bc_golden_test(
        "if_stmt_if_else_x_and_y",
        "def test(x, y):\n  if x and y:\n    return 10\n  else:\n    return 20",
    );
}

#[test]
fn test_if_else_x_or_y() {
    bc_golden_test(
        "if_stmt_if_else_x_or_y",
        "def test(x, y):\n  if x or y:\n    return 10\n  else:\n    return 20",
    );
}

#[test]
fn test_and_stmt() {
    bc_golden_test("if_stmt_and_stmt", "def test(x):\n  x and noop()");
}

#[test]
fn test_or_stmt() {
    bc_golden_test("if_stmt_or_stmt", "def test(x):\n  x or noop()");
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Bytecode generation tests.

mod and_or;
mod compr;
mod definitely_assigned;
mod expr;
mod for_stmt;
pub(crate) mod golden;
mod if_stmt;


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use allocative::Allocative;
use derive_more::Display;
use starlark_derive::starlark_attrs;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::assert::Assert;
use crate::starlark_simple_value;
use crate::values::StarlarkAttrs;
use crate::values::StarlarkValue;

#[test]
fn test_derive_attrs() {
    #[derive(
        Debug,
        StarlarkAttrs,
        Display,
        ProvidesStaticType,
        NoSerialize,
        Allocative
    )]
    #[display(fmt = "{:?}", self)]
    struct Example {
        hello: String,
        #[starlark(skip)]
        answer: i64,
        #[starlark(clone)]
        nested: Nested,
    }
    starlark_simple_value!(Example);

    #[starlark_value(type = "example")]
    impl<'v> StarlarkValue<'v> for Example {
        starlark_attrs!();
    }

    #[derive(
        Debug,
        Clone,
        StarlarkAttrs,
        Display,
        ProvidesStaticType,
        NoSerialize,
        Allocative
    )]
    #[display(fmt = "{}", foo)]
    struct Nested {
        foo: String,
    }
    starlark_simple_value!(Nested);

    #[starlark_value(type = "nested")]
    impl<'v> StarlarkValue<'v> for 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\"");
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use allocative::Allocative;
use derive_more::Display;
use maplit::hashmap;
use serde::Serialize;
use serde::Serializer;
use starlark_derive::starlark_module;
use starlark_derive::starlark_value;
use starlark_derive::Freeze;
use starlark_derive::NoSerialize;
use starlark_derive::StarlarkDocs;
use starlark_derive::Trace;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::coerce::Coerce;
use crate::docs::get_registered_starlark_docs;
use crate::docs::DocItem;
use crate::docs::DocMember;
use crate::docs::DocString;
use crate::docs::DocStringKind;
use crate::environment::Methods;
use crate::environment::MethodsBuilder;
use crate::environment::MethodsStatic;
use crate::starlark_complex_value;
use crate::starlark_simple_value;
use crate::values::StarlarkValue;
use crate::values::ValueLike;
use crate::wasm::is_wasm;

/// Main module docs
#[starlark_module]
fn object_docs_1(_: &mut MethodsBuilder) {
    /// Returns the string "foo"
    #[starlark(attribute)]
    fn foo(this: &TestExample) -> anyhow::Result<String> {
        Ok("foo".to_owned())
    }
}

#[derive(
    Debug,
    Display,
    ProvidesStaticType,
    NoSerialize,
    StarlarkDocs,
    Allocative
)]
struct TestExample {}

starlark_simple_value!(TestExample);

#[starlark_value(type = "TestExample")]
impl<'v> StarlarkValue<'v> for TestExample {
    fn get_methods() -> Option<&'static Methods>
    where
        Self: Sized,
    {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(object_docs_1)
    }
}

#[derive(
    Clone,
    Debug,
    Coerce,
    Display,
    Trace,
    Freeze,
    ProvidesStaticType,
    StarlarkDocs,
    Allocative
)]
#[repr(C)]
struct ComplexTestExampleGen<V>(V);

impl<V> Serialize for ComplexTestExampleGen<V>
where
    V: Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.0.serialize(serializer)
    }
}

starlark_complex_value!(ComplexTestExample);

#[starlark_value(type = "ComplexTestExample")]
impl<'v, T: ValueLike<'v> + 'v + ProvidesStaticType<'v>> StarlarkValue<'v>
    for ComplexTestExampleGen<T>
where
    Self: ProvidesStaticType<'v>,
{
    fn get_methods() -> Option<&'static Methods>
    where
        Self: Sized,
    {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(object_docs_1)
    }
}

#[test]
fn test_derive_docs() {
    if is_wasm() {
        // `inventory` doesn't work on wasm.
        return;
    }

    let docs = get_registered_starlark_docs()
        .into_iter()
        .find(|d| d.id.name == "TestExample")
        .unwrap();
    let obj = match docs.item {
        DocItem::Object(o) => o,
        _ => panic!("Expected object as docitem"),
    };

    assert_eq!(
        DocString::from_docstring(DocStringKind::Rust, "Main module docs"),
        obj.docs
    );
    assert_eq!(
        DocString::from_docstring(DocStringKind::Rust, "Returns the string \"foo\""),
        obj.members
            .iter()
            .find_map(|(name, m)| match m {
                DocMember::Property(p) if name == "foo" => Some(p.docs.clone()),
                _ => None,
            })
            .unwrap()
    );
    assert!(docs.custom_attrs.is_empty());
}

#[test]
fn test_derive_docs_on_complex_values() {
    if is_wasm() {
        // `inventory` doesn't work on wasm.
        return;
    }

    let complex_docs = get_registered_starlark_docs()
        .into_iter()
        .find(|d| d.id.name == "ComplexTestExample")
        .unwrap();
    let complex_obj = match complex_docs.item {
        DocItem::Object(o) => o,
        _ => panic!("Expected object as docitem"),
    };

    assert_eq!(
        DocString::from_docstring(DocStringKind::Rust, "Main module docs"),
        complex_obj.docs
    );
    assert_eq!(
        DocString::from_docstring(DocStringKind::Rust, "Returns the string \"foo\""),
        complex_obj
            .members
            .iter()
            .find_map(|(name, m)| match m {
                DocMember::Property(p) if name == "foo" => Some(p.docs.clone()),
                _ => None,
            })
            .unwrap()
    );
    assert!(complex_docs.custom_attrs.is_empty());
}

/// Main module docs
#[starlark_module]
fn object_docs_2(_: &mut MethodsBuilder) {}

#[derive(
    Debug,
    Display,
    ProvidesStaticType,
    NoSerialize,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(key = "value", key2 = "value2")]
struct TestAttrExample {}

starlark_simple_value!(TestAttrExample);

#[starlark_value(type = "TestAttrExample")]
impl<'v> StarlarkValue<'v> for TestAttrExample {
    fn get_methods() -> Option<&'static Methods>
    where
        Self: Sized,
    {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(object_docs_2)
    }
}

#[test]
fn test_derive_docs_custom_attrs() {
    if is_wasm() {
        // `inventory` doesn't work on wasm.
        return;
    }

    let docs = get_registered_starlark_docs()
        .into_iter()
        .find(|d| d.id.name == "TestAttrExample")
        .unwrap();
    let expected_attrs = hashmap! {
        "key".to_owned()=> "value".to_owned(),
        "key2".to_owned()=> "value2".to_owned(),
    };
    assert_eq!(expected_attrs, docs.custom_attrs);
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

mod attrs;
mod docs;
mod freeze;
mod module;
mod trace;
mod unpack_value_attr;


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#![allow(dead_code)] // Only check it compiles.

use allocative::Allocative;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;
use starlark_derive::ProvidesStaticType;

use crate as starlark;
use crate::values::StarlarkValue;
use crate::values::Value;

#[derive(
    Debug,
    derive_more::Display,
    NoSerialize,
    ProvidesStaticType,
    Allocative
)]
#[display(fmt = "ValueWithLifetimeParam")]
struct ValueWithLifetimeParam<'v>(Value<'v>);
#[derive(
    Debug,
    derive_more::Display,
    NoSerialize,
    ProvidesStaticType,
    Allocative
)]
#[display(fmt = "ValueWithoutParam")]
struct ValueWithoutParam(String);

#[starlark_value(type = "ValueWithLifetimeParam", StarlarkTypeRepr, UnpackValue)]
impl<'v> StarlarkValue<'v> for ValueWithLifetimeParam<'v> {}

#[starlark_value(type = "ValueWithoutParam", StarlarkTypeRepr, UnpackValue)]
impl<'v> StarlarkValue<'v> for ValueWithoutParam {}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate as starlark;
use crate::values::Freeze;
use crate::values::Freezer;
use crate::values::FrozenHeap;

#[derive(Freeze)]
struct TestStruct {
    s: String,
    #[freeze(identity)]
    s2: String,
}

#[derive(Freeze)]
struct TestAnonStruct(String, #[freeze(identity)] String);

#[test]
fn test_struct() -> anyhow::Result<()> {
    let t = TestStruct {
        s: "test".to_owned(),
        s2: "test2".to_owned(),
    };
    let freezer = Freezer::new(FrozenHeap::new());
    t.freeze(&freezer)?;
    Ok(())
}

#[test]
fn test_anon_struct() -> anyhow::Result<()> {
    let t = TestAnonStruct("test".to_owned(), "test2".to_owned());
    let freezer = Freezer::new(FrozenHeap::new());
    t.freeze(&freezer)?;
    Ok(())
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate as starlark;
use crate::values::Freeze;

trait Bound<'x> {}

#[derive(Freeze)]
#[freeze(validator = check_type, bounds = "<V as Freeze>::Frozen: Bound<'freeze>")]
struct Test<V> {
    field: V,
}

fn check_type<'x, V>(_: &Test<V>) -> anyhow::Result<()>
where
    V: Bound<'x>,
{
    Ok(())
}

#[test]
fn assert_impl() {
    #[derive(Freeze)]
    struct Impl {}
    impl<'x> Bound<'x> for Impl {}
    fn check(_: impl Freeze) {}
    check(Test { field: Impl {} });
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use starlark_derive::Freeze;

use crate as starlark;

#[allow(dead_code)] // If it compiles, it works.
#[derive(Freeze)]
enum TestFreezeEnum<V> {
    A,
    B(),
    C(V),
    D(V, V),
    E {},
    F { a: V },
    G { a: V, b: V },
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

mod basic;
mod bounds;
mod enums;
mod validator;
mod validator_order;


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate as starlark;
use crate::values::Freeze;
use crate::values::Freezer;
use crate::values::FrozenHeap;

#[derive(Freeze)]
#[freeze(validator = check_true)]
struct Test {
    field: bool,
}

fn check_true(test: &Test) -> anyhow::Result<()> {
    if !test.field {
        return Err(anyhow::anyhow!("Err"));
    }

    Ok(())
}

#[test]
fn test_ok() -> anyhow::Result<()> {
    let t = Test { field: true };
    let freezer = Freezer::new(FrozenHeap::new());
    t.freeze(&freezer)?;
    Ok(())
}

#[test]
fn test_fail() -> anyhow::Result<()> {
    let t = Test { field: false };
    let freezer = Freezer::new(FrozenHeap::new());
    assert!(t.freeze(&freezer).is_err());
    Ok(())
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate as starlark;
use crate::values::Freeze;
use crate::values::Freezer;
use crate::values::FrozenHeap;

struct FreezeSentinel {
    frozen: bool,
}

impl Freeze for FreezeSentinel {
    type Frozen = Self;

    fn freeze(self, _: &Freezer) -> anyhow::Result<Self> {
        assert!(!self.frozen);
        Ok(Self { frozen: true })
    }
}

#[derive(Freeze)]
#[freeze(validator = check_froze_before_validating)]
struct Test {
    sentinel: FreezeSentinel,
}

fn check_froze_before_validating(test: &Test) -> anyhow::Result<()> {
    // Accessing fields on a Starlark value before we call freeze() on it may fail (because we
    // read the forward not what it points to), so we check that validators receive frozen data.
    assert!(test.sentinel.frozen);
    Ok(())
}

#[test]
fn test() -> anyhow::Result<()> {
    let t = Test {
        sentinel: FreezeSentinel { frozen: false },
    };
    let freezer = Freezer::new(FrozenHeap::new());
    t.freeze(&freezer)?;
    Ok(())
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use starlark_derive::starlark_module;

use crate as starlark;
use crate::assert::Assert;
use crate::environment::GlobalsBuilder;
use crate::environment::MethodsBuilder;
use crate::values::Heap;
use crate::values::StringValue;
use crate::values::Value;

// The examples from the starlark_module documentation.
#[test]
fn test_starlark_module() {
    #[starlark_module]
    fn global(builder: &mut GlobalsBuilder) {
        fn cc_binary(name: &str, srcs: Vec<&str>) -> anyhow::Result<String> {
            // real implementation may write it to a global variable
            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"]"#
    );
}

#[test]
fn test_starlark_methods() {
    #[starlark_module]
    fn methods(builder: &mut MethodsBuilder) {
        fn r#enum<'v>(
            this: Value<'v>,
            #[starlark(require = named, default = 3)] index: i32,
            heap: &'v Heap,
        ) -> anyhow::Result<StringValue<'v>> {
            Ok(heap.alloc_str(&format!("{this} {index}")))
        }
    }

    MethodsBuilder::new().with(methods).build();
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use starlark_derive::starlark_module;

use crate as starlark;
use crate::assert::Assert;
use crate::environment::GlobalsBuilder;

#[starlark_module]
fn default_value_functions(globals: &mut GlobalsBuilder) {
    fn foo(#[starlark(default = 75)] x: i32) -> anyhow::Result<i32> {
        Ok(x)
    }
}

#[test]
fn test_default_value() {
    let mut a = Assert::new();
    a.globals_add(default_value_functions);
    a.eq("74", "foo(74)");
    a.eq("75", "foo()");
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use allocative::Allocative;
use starlark_derive::starlark_module;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::assert::Assert;
use crate::environment::Methods;
use crate::environment::MethodsBuilder;
use crate::environment::MethodsStatic;
use crate::values::AllocFrozenValue;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::StarlarkValue;
use crate::values::Value;
use crate::values::ValueLike;

#[derive(
    Debug,
    derive_more::Display,
    ProvidesStaticType,
    NoSerialize,
    Allocative
)]
#[display(fmt = "{:?}", self)]
struct Applaud {
    value: i32,
}

#[starlark_module]
fn methods(builder: &mut MethodsBuilder) {
    fn test_method(#[starlark(this)] receiver: Value, this: i32) -> anyhow::Result<i32> {
        let applauld = receiver.downcast_ref::<Applaud>().unwrap();
        Ok(applauld.value + this)
    }
}

#[starlark_value(type = "applaud")]
impl<'v> StarlarkValue<'v> for Applaud {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(methods)
    }
}

impl AllocFrozenValue for Applaud {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc_simple(self)
    }
}

#[test]
fn test_receiver_can_be_named_anything() {
    let mut a = Assert::new();
    a.globals_add(|g| g.set("x", g.alloc(Applaud { value: 10 })));
    a.eq("13", "x.test_method(this=3)");
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

mod basic;
mod default_value;
mod methods;
mod named_positional;
mod return_impl;
mod special_params;
mod type_annotation;
mod unpack_value;


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use starlark_derive::starlark_module;

use crate as starlark;
use crate::assert::Assert;
use crate::environment::GlobalsBuilder;

#[starlark_module]
fn named_positional_functions(globals: &mut GlobalsBuilder) {
    fn positional(#[starlark(require = pos)] x: i32) -> anyhow::Result<i32> {
        Ok(x)
    }

    fn named(x: i32) -> anyhow::Result<i32> {
        Ok(x)
    }

    fn named_only(#[starlark(require = named)] x: i32) -> anyhow::Result<i32> {
        Ok(x)
    }

    fn named_after_args(#[starlark(args)] star_args: Vec<i32>, x: i32) -> anyhow::Result<i32> {
        Ok(x + star_args.iter().sum::<i32>())
    }

    // Same as above, but with explicit redundant annotation.
    fn named_after_args_explicitly_marked(
        #[starlark(args)] args: Vec<i32>,
        #[starlark(require = named)] x: i32,
    ) -> anyhow::Result<i32> {
        Ok(x + args.iter().sum::<i32>())
    }
}

#[test]
fn test_positional_only() {
    let mut a = Assert::new();
    a.globals_add(named_positional_functions);
    a.eq("17", "positional(17)");
    a.fail("noop(positional)(x=19)", "extra named parameter");
}

#[test]
fn test_named_can_be_called_as_both_named_and_positional() {
    let mut a = Assert::new();
    a.globals_add(named_positional_functions);
    a.eq("23", "named(x=23)");
    a.eq("29", "named(29)");
}

#[test]
fn test_named_only() {
    let mut a = Assert::new();
    a.globals_add(named_positional_functions);
    a.eq("31", "named_only(x=31)");
    a.fail("noop(named_only)(37)", "Missing parameter");
}

#[test]
fn test_named_after_args() {
    let mut a = Assert::new();
    a.globals_add(named_positional_functions);
    a.eq("13", "named_after_args(1, 2, x=10)");
    a.fail("noop(named_after_args)(1, 2, 3)", "Missing parameter");
}

#[test]
fn test_named_after_args_explicitly_marked() {
    let mut a = Assert::new();
    a.globals_add(named_positional_functions);
    a.eq("13", "named_after_args_explicitly_marked(1, 2, x=10)");
    a.fail(
        "noop(named_after_args_explicitly_marked)(1, 2, 3)",
        "Missing parameter",
    );
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use starlark_derive::starlark_module;

use crate as starlark;
use crate::environment::GlobalsBuilder;
use crate::values::AllocValue;
use crate::values::Value;

#[starlark_module]
fn _test_return_impl_alloc_value(globals: &mut GlobalsBuilder) {
    fn func<'v>(v: Value<'v>) -> anyhow::Result<impl AllocValue<'v>> {
        Ok(v)
    }
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use starlark_derive::starlark_module;

use crate as starlark;
use crate::assert::Assert;
use crate::environment::GlobalsBuilder;
use crate::values::Heap;
use crate::values::StringValue;

#[starlark_module]
fn functions(builder: &mut GlobalsBuilder) {
    fn non_standard_heap_name<'v>(
        heap: &str,
        starlark_heap: &'v Heap,
    ) -> anyhow::Result<StringValue<'v>> {
        Ok(starlark_heap.alloc_str_concat(heap, "!"))
    }
}

#[test]
fn test_non_standard_param_names() {
    let mut a = Assert::new();
    a.globals_add(functions);
    a.eq("'x!'", "non_standard_heap_name('x')");
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use allocative::Allocative;
use starlark_derive::starlark_module;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;
use starlark_derive::ProvidesStaticType;

use crate as starlark;
use crate::assert::Assert;
use crate::environment::GlobalsBuilder;
use crate::values::StarlarkValue;

#[derive(
    Debug,
    derive_more::Display,
    ProvidesStaticType,
    NoSerialize,
    Allocative
)]
#[display(fmt = "foo")]
struct Foo;

#[starlark_value(type = "Foo")]
impl<'v> StarlarkValue<'v> for Foo {}

#[starlark_module]
fn type_annotation_functions(globals: &mut GlobalsBuilder) {
    #[starlark(as_type = Foo)]
    fn foo(x: i32) -> anyhow::Result<i32> {
        Ok(x)
    }
}

#[test]
fn test_type_annotation() {
    let mut a = Assert::new();
    a.globals_add(type_annotation_functions);
    a.eq("'Foo'", "foo.type");
}