/*
 * 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.
 */

/// Reduce boilerplate when making types instances of [`ComplexValue`](crate::values::ComplexValue)
/// - see the [`ComplexValue`](crate::values::ComplexValue) docs for an example.
#[macro_export]
macro_rules! starlark_complex_value {
    // Common part of macro variants.
    (impl $x:ident) => {
        $crate::__macro_refs::item! {
            impl<'v> $crate::values::AllocValue<'v> for $x<'v> {
                #[inline]
                fn alloc_value(self, heap: &'v $crate::values::Heap) -> $crate::values::Value<'v> {
                    heap.alloc_complex(self)
                }
            }

            impl $crate::values::AllocFrozenValue for [< Frozen $x >] {
                #[inline]
                fn alloc_frozen_value(self, heap: &$crate::values::FrozenHeap) -> $crate::values::FrozenValue {
                    heap.alloc_simple(self)
                }
            }

            impl<'v> $x<'v> {
                /// Downcast the value.
                #[inline]
                pub fn from_value(x: $crate::values::Value<'v>) -> Option<&'v Self> {
                    if let Some(x) = x.unpack_frozen() {
                        $crate::values::ValueLike::downcast_ref::< [< Frozen $x >] >(x).map($crate::__macro_refs::coerce)
                    } else {
                        $crate::values::ValueLike::downcast_ref::< $x<'v> >(x)
                    }
                }
            }

            impl<'v> $crate::values::type_repr::StarlarkTypeRepr for &'v $x<'v> {
                #[inline]
                fn starlark_type_repr() -> $crate::typing::Ty {
                    <$x as $crate::values::StarlarkValue>::get_type_starlark_repr()
                }
            }

            impl<'v> $crate::values::UnpackValue<'v> for &'v $x<'v> {
                #[inline]
                fn unpack_value(x: $crate::values::Value<'v>) -> Option<&'v $x<'v>> {
                    $x::from_value(x)
                }
            }
        }
    };
    ($v:vis $x:ident) => {
        $crate::__macro_refs::item! {
            /// Type of value.
            $v type $x<'v> = [< $x Gen >]<$crate::values::Value<'v>>;
            /// Type of frozen value.
            $v type [< Frozen $x >] = [< $x Gen >]<$crate::values::FrozenValue>;

            starlark_complex_value!(impl $x);
        }
    };
    ($v:vis $x:ident <'v>) => {
        $crate::__macro_refs::item! {
            /// Type of unfrozen value.
            $v type $x<'v> = [< $x Gen >]<'v, $crate::values::Value<'v>>;
            /// Type of frozen value.
            $v type [< Frozen $x >] = [< $x Gen >]<'static, $crate::values::FrozenValue>;

            starlark_complex_value!(impl $x);
        }
    };
}

/// Reduce boilerplate when making types instances of [`ComplexValue`](crate::values::ComplexValue)
/// - see the [`ComplexValue`](crate::values::ComplexValue) docs for an example.
#[macro_export]
macro_rules! starlark_complex_values {
    ($x:ident) => {
        $crate::__macro_refs::item! {
            impl<'v> $crate::values::AllocValue<'v> for $x<'v> {
                #[inline]
                fn alloc_value(self, heap: &'v $crate::values::Heap) -> $crate::values::Value<'v> {
                    heap.alloc_complex(self)
                }
            }

            impl $crate::values::AllocFrozenValue for [< Frozen $x >] {
                #[inline]
                fn alloc_frozen_value(self, heap: &$crate::values::FrozenHeap) -> $crate::values::FrozenValue {
                    heap.alloc_simple(self)
                }
            }

            impl<'v> $x<'v> {
                #[allow(dead_code)]
                #[inline]
                pub(crate) fn from_value(
                    x: $crate::values::Value<'v>,
                ) -> Option<$crate::__macro_refs::Either<&'v Self, &'v [< Frozen $x >]>> {
                    if let Some(x) = x.unpack_frozen() {
                        $crate::values::ValueLike::downcast_ref(x).map($crate::__macro_refs::Either::Right)
                    } else {
                        $crate::values::ValueLike::downcast_ref(x).map($crate::__macro_refs::Either::Left)
                    }
                }
            }
        }
    };
}

/// A macro reducing boilerplace defining Starlark values which are simple - they
/// aren't mutable and can't contain references to other Starlark values.
///
/// Let's define a simple object, where `+x` makes the string uppercase:
///
/// ```
/// use starlark::values::{Heap, StarlarkValue, Value, ProvidesStaticType, NoSerialize};
/// use starlark::{starlark_simple_value};
/// use derive_more::Display;
/// use allocative::Allocative;
/// use starlark_derive::starlark_value;
///
/// #[derive(Debug, Display, ProvidesStaticType, NoSerialize, Allocative)]
/// struct MyObject(String);
/// starlark_simple_value!(MyObject);
///
/// #[starlark_value(type = "my_object")]
/// impl<'v> StarlarkValue<'v> for MyObject {
///     // We can choose to implement whichever methods we want.
///     // All other operations will result in runtime errors.
///     fn plus(&self, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
///         Ok(heap.alloc(MyObject(self.0.to_uppercase())))
///     }
/// }
/// ```
///
/// The `starlark_simple_value!` macro defines instances of
/// [`ProvidesStaticType`](crate::values::ProvidesStaticType),
/// [`AllocValue`](crate::values::AllocValue),
/// [`AllocFrozenValue`](crate::values::AllocFrozenValue) and
/// [`UnpackValue`](crate::values::UnpackValue). It also defines a method:
///
/// ```
/// # use crate::starlark::values::*;
/// # struct MyObject;
/// impl MyObject {
///     pub fn from_value<'v>(x: Value<'v>) -> Option<&'v MyObject> {
/// # unimplemented!(
/// # r#"
///         ...
/// # "#);
///     }
/// }
/// ```
#[macro_export]
macro_rules! starlark_simple_value {
    ($x:ident) => {
        $crate::__macro_refs::item! {
            impl<'v> $crate::values::AllocValue<'v> for $x {
                #[inline]
                fn alloc_value(self, heap: &'v $crate::values::Heap) -> $crate::values::Value<'v> {
                    heap.alloc_simple(self)
                }
            }

            impl $crate::values::AllocFrozenValue for $x {
                #[inline]
                fn alloc_frozen_value(self, heap: &$crate::values::FrozenHeap) -> $crate::values::FrozenValue {
                    heap.alloc_simple(self)
                }
            }

            impl $x {
                /// Downcast a value to self type.
                #[inline]
                pub fn from_value<'v>(x: $crate::values::Value<'v>) -> Option<&'v Self> {
                    $crate::values::ValueLike::downcast_ref::< $x >(x)
                }
            }

            impl<'v> $crate::values::type_repr::StarlarkTypeRepr for &'v $x {
                fn starlark_type_repr() -> $crate::typing::Ty {
                    <$x as $crate::values::StarlarkValue>::get_type_starlark_repr()
                }
            }

            impl<'v> $crate::values::UnpackValue<'v> for &'v $x {
                fn expected() -> String {
                    <$x as $crate::values::StarlarkValue>::get_type_value_static().as_str().to_owned()
                }

                #[inline]
                fn unpack_value(x: $crate::values::Value<'v>) -> Option<&'v $x> {
                    $x::from_value(x)
                }
            }
        }
    };
}


/*
 * 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 std::sync::Mutex;

use itertools::Itertools;
use once_cell::sync::Lazy;
use starlark_derive::starlark_module;
use thiserror::Error;

use crate as starlark;
use crate::environment::GlobalsBuilder;
use crate::eval::Evaluator;
use crate::read_line::ReadLine;
use crate::syntax::AstModule;
use crate::syntax::Dialect;
use crate::values::none::NoneType;

// A breakpoint takes over the console UI, so having two going at once confuses everything.
// Have a global mutex to ensure one at a time.
static BREAKPOINT_MUTEX: Lazy<Mutex<State>> = Lazy::new(|| Mutex::new(State::Allow));

/// `breakpoint` function uses this trait to perform console IO.
pub(crate) trait BreakpointConsole {
    /// Return `None` on EOF.
    fn read_line(&mut self) -> anyhow::Result<Option<String>>;
    fn println(&mut self, line: &str);
}

/// Breakpoint handler implemented with `rustyline`.
pub(crate) struct RealBreakpointConsole {
    read_line: ReadLine,
}

impl BreakpointConsole for RealBreakpointConsole {
    fn read_line(&mut self) -> anyhow::Result<Option<String>> {
        self.read_line.read_line("$> ")
    }

    fn println(&mut self, line: &str) {
        eprintln!("{}", line);
    }
}

impl RealBreakpointConsole {
    pub(crate) fn factory() -> Box<dyn Fn() -> anyhow::Result<Box<dyn BreakpointConsole>>> {
        Box::new(|| {
            Ok(Box::new(RealBreakpointConsole {
                read_line: ReadLine::new("STARLARK_RUST_DEBUGGER_HISTFILE")?,
            }))
        })
    }
}

/// Is debugging allowed or not? After the user hits Ctrl-C they probably
/// just want to stop hard, so don't keep dropping them into breakpoints.
#[derive(PartialEq, Eq)]
enum State {
    Allow, // More breakpoints are fine
    Stop,  // No more breakpoints
}

/// We've run a breakpoint command, what should we do.
enum Next {
    Again,  // Accept another breakpoint command
    Resume, // Continue running
    Fail,   // Stop running
}

fn cmd_help(_eval: &mut Evaluator, rl: &mut dyn BreakpointConsole) -> anyhow::Result<Next> {
    for (name, msg, _) in COMMANDS {
        rl.println(&format!("* :{}, {}", name[0], msg))
    }
    Ok(Next::Again)
}

fn cmd_variables(eval: &mut Evaluator, rl: &mut dyn BreakpointConsole) -> anyhow::Result<Next> {
    fn truncate(mut s: String, n: usize) -> String {
        if s.len() > n {
            s.truncate(n);
            s.push_str("...");
        }
        s
    }

    for (name, value) in eval.local_variables() {
        rl.println(&format!("* {} = {}", name, truncate(value.to_string(), 80)))
    }
    Ok(Next::Again)
}

fn cmd_stack(eval: &mut Evaluator, rl: &mut dyn BreakpointConsole) -> anyhow::Result<Next> {
    for line in eval.call_stack().to_string().lines() {
        rl.println(line)
    }
    Ok(Next::Again)
}

fn cmd_resume(_eval: &mut Evaluator, _rl: &mut dyn BreakpointConsole) -> anyhow::Result<Next> {
    Ok(Next::Resume)
}

fn cmd_fail(_eval: &mut Evaluator, _rl: &mut dyn BreakpointConsole) -> anyhow::Result<Next> {
    Ok(Next::Fail)
}

const COMMANDS: &[(
    &[&str], // Possible names
    &str,    // Help text
    fn(eval: &mut Evaluator, &mut dyn BreakpointConsole) -> anyhow::Result<Next>,
)] = &[
    (&["help", "?"], "Show this help message", cmd_help),
    (&["vars"], "Show all local variables", cmd_variables),
    (&["stack"], "Show the stack trace", cmd_stack),
    (&["resume", "quit", "exit"], "Resume execution", cmd_resume),
    (&["fail"], "Abort with a failure message", cmd_fail),
];

fn pick_command(
    x: &str,
    rl: &mut dyn BreakpointConsole,
) -> Option<fn(eval: &mut Evaluator, &mut dyn BreakpointConsole) -> anyhow::Result<Next>> {
    // If we can find a command that matches perfectly, do that
    // Otherwise return the longest match, but if they are multiple, show a warning
    let mut poss = Vec::new();
    for (names, _, cmd) in COMMANDS {
        for n in *names {
            if *n == x {
                return Some(*cmd);
            }
            if n.starts_with(x) {
                poss.push((n, cmd));
                break;
            }
        }
    }
    match poss.as_slice() {
        [] => rl.println("Unrecognised command, type :help for all commands"),
        [x] => return Some(*x.1),
        xs => rl.println(&format!(
            "Ambiguous command, could have been any of: {}",
            xs.iter().map(|x| x.0).join(" ")
        )),
    }
    None
}

fn breakpoint_loop(
    eval: &mut Evaluator,
    mut rl: Box<dyn BreakpointConsole>,
) -> anyhow::Result<State> {
    loop {
        let readline = rl.read_line()?;
        match readline {
            Some(line) => {
                if let Some(line) = line.strip_prefix(':') {
                    if let Some(cmd) = pick_command(line.trim_end(), &mut *rl) {
                        match cmd(eval, &mut *rl)? {
                            Next::Again => {}
                            Next::Resume => return Ok(State::Allow),
                            Next::Fail => {
                                return Err(anyhow::anyhow!("Selected :fail at breakpoint()"));
                            }
                        }
                    }
                } else {
                    let ast = AstModule::parse("interactive", line, &Dialect::Extended);
                    let res = ast.and_then(|ast| eval.eval_statements(ast));
                    match res {
                        Err(e) => {
                            rl.println(&format!("{:#}", e));
                        }
                        Ok(v) => {
                            if !v.is_none() {
                                rl.println(&v.to_string())
                            }
                        }
                    }
                }
            }
            None => return Ok(State::Stop),
        }
    }
}

#[derive(Error, Debug)]
enum BreakpointError {
    #[error("Breakpoint handler is not enabled for current Evaluator")]
    NoHandler,
}

const BREAKPOINT_HIT_MESSAGE: &str = "BREAKPOINT HIT! :resume to continue, :help for all options";

#[starlark_module]
pub fn global(builder: &mut GlobalsBuilder) {
    /// When a debugger is available, breaks into the debugger.
    fn breakpoint(eval: &mut Evaluator) -> anyhow::Result<NoneType> {
        {
            let mut guard = BREAKPOINT_MUTEX.lock().unwrap();
            if *guard == State::Allow {
                let mut rl = match &mut eval.breakpoint_handler {
                    Some(rl) => rl()?,
                    None => return Err(BreakpointError::NoHandler.into()),
                };
                rl.println(BREAKPOINT_HIT_MESSAGE);
                *guard = breakpoint_loop(eval, rl)?;
            }
        }
        Ok(NoneType)
    }
}

#[cfg(test)]
mod tests {
    use std::cell::RefCell;
    use std::env;
    use std::rc::Rc;

    use dupe::Dupe;

    use super::*;
    use crate::assert::Assert;

    // Breakpoint tests should not be executed concurrently
    // to avoid interfering with `BREAKPOINT_MUTEX`.
    static TEST_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));

    fn reset_global_state() {
        // `breakpoint()` function modifies the global state.
        *BREAKPOINT_MUTEX.lock().unwrap() = State::Allow;
    }

    #[test]
    // Test with: BREAKPOINT=1 cargo test -p starlark breakpoint -- --nocapture
    fn test_breakpoint_real() {
        let _g = TEST_MUTEX.lock();
        reset_global_state();

        if env::var("BREAKPOINT") != Ok("1".to_owned()) {
            return;
        }

        let mut a = Assert::new();
        a.setup_eval(|e| e.enable_terminal_breakpoint_console());
        a.globals_add(global);
        a.pass("x = [1,2,3]; breakpoint(); print(x)");
    }

    #[test]
    fn test_breakpoint_mock() {
        let _g = TEST_MUTEX.lock();
        reset_global_state();

        let printed_lines = Rc::new(RefCell::new(Vec::new()));
        let printed_lines_copy = printed_lines.dupe();

        let mut a = Assert::new();
        a.globals_add(global);
        a.setup_eval(move |eval| {
            let printed_lines = printed_lines.dupe();
            eval.breakpoint_handler = Some(Box::new(move || {
                // `Assert` runs tests several times, take only lines from the last iteration.
                printed_lines.borrow_mut().clear();

                struct Handler {
                    printed_lines: Rc<RefCell<Vec<String>>>,
                    called: bool,
                }

                impl BreakpointConsole for Handler {
                    fn read_line(&mut self) -> anyhow::Result<Option<String>> {
                        let called = self.called;
                        self.called = true;
                        if !called {
                            Ok(Some("x".to_owned()))
                        } else {
                            Ok(None)
                        }
                    }

                    fn println(&mut self, line: &str) {
                        self.printed_lines.borrow_mut().push(line.to_owned());
                    }
                }

                Ok(Box::new(Handler {
                    printed_lines: printed_lines.dupe(),
                    called: false,
                }))
            }));
        });
        a.pass("x = [1,2,3]; breakpoint()");

        assert_eq!(
            vec![BREAKPOINT_HIT_MESSAGE, "[1, 2, 3]"],
            *printed_lines_copy.borrow()
        );
    }

    #[test]
    fn test_breakpoint_disabled() {
        let _g = TEST_MUTEX.lock();
        reset_global_state();

        let mut a = Assert::new();
        a.globals_add(global);
        a.fail(
            "x = [1,2,3]; breakpoint()",
            "Breakpoint handler is not enabled",
        );
    }
}


/*
 * 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.
 */

//! Methods for the `dict` type.

use std::mem;

use starlark_derive::starlark_module;

use crate as starlark;
use crate::environment::MethodsBuilder;
use crate::hint::unlikely;
use crate::values::dict::DictMut;
use crate::values::dict::DictRef;
use crate::values::list::AllocList;
use crate::values::list::ListOf;
use crate::values::list::ListRef;
use crate::values::none::NoneType;
use crate::values::Heap;
use crate::values::Value;
use crate::values::ValueOfUnchecked;

#[starlark_module]
pub(crate) fn dict_methods(registry: &mut MethodsBuilder) {
    /// [dict.clear](
    /// https://github.com/google/skylark/blob/3705afa472e466b8b061cce44b47c9ddc6db696d/doc/spec.md#dictÂ·clear
    /// ): clear a dictionary
    ///
    /// `D.clear()` removes all the entries of dictionary D and returns `None`.
    /// It fails if the dictionary is frozen or if there are active iterators.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// x = {"one": 1, "two": 2}
    /// x.clear()
    /// x == {}
    /// # "#);
    /// ```
    fn clear(this: Value) -> anyhow::Result<NoneType> {
        let mut this = DictMut::from_value(this)?;
        this.clear();
        Ok(NoneType)
    }

    /// [dict.get](
    /// https://github.com/google/skylark/blob/3705afa472e466b8b061cce44b47c9ddc6db696d/doc/spec.md#dictÂ·get
    /// ): return an element from the dictionary.
    ///
    /// `D.get(key[, default])` returns the dictionary value corresponding to
    /// the given key. If the dictionary contains no such value, `get`
    /// returns `None`, or the value of the optional `default` parameter if
    /// present.
    ///
    /// `get` fails if `key` is unhashable.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// x = {"one": 1, "two": 2}
    /// # (
    /// x.get("one") == 1
    /// # and
    /// x.get("three") == None
    /// # and
    /// x.get("three", 0) == 0
    /// # )"#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn get<'v>(
        this: DictRef<'v>,
        #[starlark(require = pos)] key: Value<'v>,
        #[starlark(require = pos)] default: Option<Value<'v>>,
    ) -> anyhow::Result<Value<'v>> {
        match this.get(key)? {
            None => Ok(default.unwrap_or_else(Value::new_none)),
            Some(x) => Ok(x),
        }
    }

    /// [dict.items](
    /// https://github.com/google/skylark/blob/3705afa472e466b8b061cce44b47c9ddc6db696d/doc/spec.md#dictÂ·items
    /// ): get list of (key, value) pairs.
    ///
    /// `D.items()` returns a new list of key/value pairs, one per element in
    /// dictionary D, in the same order as they would be returned by a `for`
    /// loop.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// x = {"one": 1, "two": 2}
    /// x.items() == [("one", 1), ("two", 2)]
    /// # "#);
    /// ```
    fn items<'v>(
        this: DictRef<'v>,
        heap: &'v Heap,
    ) -> anyhow::Result<ValueOfUnchecked<'v, ListOf<'v, (Value<'v>, Value<'v>)>>> {
        Ok(ValueOfUnchecked::new(heap.alloc_list_iter(
            this.iter().map(|(k, v)| heap.alloc((k, v))),
        )))
    }

    /// [dict.keys](
    /// https://github.com/google/skylark/blob/3705afa472e466b8b061cce44b47c9ddc6db696d/doc/spec.md#dictÂ·keys
    /// ): get the list of keys of the dictionary.
    ///
    /// `D.keys()` returns a new list containing the keys of dictionary D, in
    /// the same order as they would be returned by a `for` loop.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// x = {"one": 1, "two": 2}
    /// x.keys() == ["one", "two"]
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn keys<'v>(
        this: DictRef<'v>,
        heap: &'v Heap,
    ) -> anyhow::Result<ValueOfUnchecked<'v, &'v ListRef<'v>>> {
        Ok(ValueOfUnchecked::new(heap.alloc(AllocList(this.keys()))))
    }

    /// [dict.pop](
    /// https://github.com/google/skylark/blob/3705afa472e466b8b061cce44b47c9ddc6db696d/doc/spec.md#dictÂ·pop
    /// ): return an element and remove it from a dictionary.
    ///
    /// `D.pop(key[, default])` returns the value corresponding to the specified
    /// key, and removes it from the dictionary.  If the dictionary contains no
    /// such value, and the optional `default` parameter is present, `pop`
    /// returns that value; otherwise, it fails.
    ///
    /// `pop` fails if `key` is unhashable, or the dictionary is frozen or has
    /// active iterators.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// x = {"one": 1, "two": 2}
    /// # (
    /// x.pop("one") == 1
    /// # and
    /// x == {"two": 2}
    /// # and
    /// x.pop("three", 0) == 0
    /// # and
    /// x.pop("three", None) == None
    /// # )"#);
    /// ```
    ///
    /// Failure:
    ///
    /// ```
    /// # starlark::assert::fail(r#"
    /// {'one': 1}.pop('four')   # error: not found
    /// # "#, "not found");
    /// ```
    fn pop<'v>(
        this: Value<'v>,
        #[starlark(require = pos)] key: Value<'v>,
        #[starlark(require = pos)] default: Option<Value<'v>>,
    ) -> anyhow::Result<Value<'v>> {
        let mut me = DictMut::from_value(this)?;
        match me.remove_hashed(key.get_hashed()?) {
            Some(x) => Ok(x),
            None => match default {
                Some(v) => Ok(v),
                None => {
                    mem::drop(me);
                    Err(anyhow::anyhow!(
                        "Key `{}` not found in dictionary `{}`",
                        key.to_repr(),
                        this.to_repr()
                    ))
                }
            },
        }
    }

    /// [dict.popitem](
    /// https://github.com/google/skylark/blob/3705afa472e466b8b061cce44b47c9ddc6db696d/doc/spec.md#dictÂ·popitem
    /// ): returns and removes the first key/value pair of a dictionary.
    ///
    /// `D.popitem()` returns the first key/value pair, removing it from the
    /// dictionary.
    ///
    /// `popitem` fails if the dictionary is empty, frozen, or has active
    /// iterators.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// x = {"one": 1, "two": 2}
    /// # (
    /// x.popitem() == ("one", 1)
    /// # and
    /// x.popitem() == ("two", 2)
    /// # and
    /// x == {}
    /// # )"#);
    /// ```
    ///
    /// Failure:
    ///
    /// ```
    /// # starlark::assert::fail(r#"
    /// {}.popitem()   # error: empty dict
    /// # "#, "empty dict");
    /// ```
    fn popitem<'v>(this: Value<'v>) -> anyhow::Result<(Value<'v>, Value<'v>)> {
        let mut this = DictMut::from_value(this)?;

        let key = this.iter_hashed().next().map(|(k, _)| k);
        match key {
            Some(k) => Ok((*k.key(), this.remove_hashed(k).unwrap())),
            None => Err(anyhow::anyhow!("Cannot .popitem() on an empty dictionary")),
        }
    }

    /// [dict.setdefault](
    /// https://github.com/google/skylark/blob/3705afa472e466b8b061cce44b47c9ddc6db696d/doc/spec.md#dictÂ·setdefault
    /// ): get a value from a dictionary, setting it to a new value if not
    /// present.
    ///
    /// `D.setdefault(key[, default])` returns the dictionary value
    /// corresponding to the given key. If the dictionary contains no such
    /// value, `setdefault`, like `get`, returns `None` or the value of the
    /// optional `default` parameter if present; `setdefault` additionally
    /// inserts the new key/value entry into the dictionary.
    ///
    /// `setdefault` fails if the key is unhashable or if the dictionary is
    /// frozen.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// x = {"one": 1, "two": 2}
    /// # (
    /// x.setdefault("one") == 1
    /// # and
    /// x.setdefault("three", 0) == 0
    /// # and
    /// x == {"one": 1, "two": 2, "three": 0}
    /// # and
    /// x.setdefault("four") == None
    /// # and
    /// x == {"one": 1, "two": 2, "three": 0, "four": None}
    /// # )"#)
    /// ```
    fn setdefault<'v>(
        this: Value<'v>,
        #[starlark(require = pos)] key: Value<'v>,
        #[starlark(require = pos)] default: Option<Value<'v>>,
    ) -> anyhow::Result<Value<'v>> {
        let mut this = DictMut::from_value(this)?;
        let key = key.get_hashed()?;
        if let Some(r) = this.get_hashed(key) {
            return Ok(r);
        }
        let def = default.unwrap_or_else(Value::new_none);
        this.insert_hashed(key, def);
        Ok(def)
    }

    /// [dict.update](
    /// https://github.com/google/skylark/blob/3705afa472e466b8b061cce44b47c9ddc6db696d/doc/spec.md#dictÂ·update
    /// ): update values in the dictionary.
    ///
    /// `D.update([pairs][, name=value[, ...])` makes a sequence of key/value
    /// insertions into dictionary D, then returns `None.`
    ///
    /// If the positional argument `pairs` is present, it must be `None`,
    /// another `dict`, or some other iterable.
    /// If it is another `dict`, then its key/value pairs are inserted into D.
    /// If it is an iterable, it must provide a sequence of pairs (or other
    /// iterables of length 2), each of which is treated as a key/value pair
    /// to be inserted into D.
    ///
    /// For each `name=value` argument present, the name is converted to a
    /// string and used as the key for an insertion into D, with its
    /// corresponding value being `value`.
    ///
    /// `update` fails if the dictionary is frozen.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// x = {}
    /// x.update([("a", 1), ("b", 2)], c=3)
    /// x.update({"d": 4})
    /// x.update(e=5)
    /// x == {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
    /// # "#);
    /// ```
    fn update<'v>(
        this: Value<'v>,
        #[starlark(require = pos)] pairs: Option<Value<'v>>,
        #[starlark(kwargs)] kwargs: DictRef<'v>,
        heap: &'v Heap,
    ) -> anyhow::Result<NoneType> {
        let pairs = if pairs.map(|x| x.ptr_eq(this)) == Some(true) {
            // someone has done `x.update(x)` - that isn't illegal, but we will have issues
            // with trying to iterate over x while holding x for mutation, and it doesn't do
            // anything useful, so just change pairs back to None
            None
        } else {
            pairs
        };

        let mut this = DictMut::from_value(this)?;
        if let Some(pairs) = pairs {
            if let Some(dict) = DictRef::from_value(pairs) {
                for (k, v) in dict.iter_hashed() {
                    this.insert_hashed(k, v);
                }
            } else {
                for v in pairs.iterate(heap)? {
                    let mut it = v.iterate(heap)?;
                    let k = it.next();
                    let v = if k.is_some() { it.next() } else { None };
                    if unlikely(v.is_none() || it.next().is_some()) {
                        return Err(anyhow::anyhow!(
                            "dict.update expect a list of pairs or a dictionary as first argument, got a list of non-pairs.",
                        ));
                    };
                    this.insert_hashed(k.unwrap().get_hashed()?, v.unwrap());
                }
            }
        }

        for (k, v) in kwargs.iter_hashed() {
            this.insert_hashed(k, v);
        }
        Ok(NoneType)
    }

    /// [dict.values](
    /// https://github.com/google/skylark/blob/3705afa472e466b8b061cce44b47c9ddc6db696d/doc/spec.md#dictÂ·values
    /// ): get the list of values of the dictionary.
    ///
    /// `D.values()` returns a new list containing the dictionary's values, in
    /// the same order as they would be returned by a `for` loop over the
    /// dictionary.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// x = {"one": 1, "two": 2}
    /// x.values() == [1, 2]
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn values<'v>(
        this: DictRef<'v>,
        heap: &'v Heap,
    ) -> anyhow::Result<ValueOfUnchecked<'v, &'v ListRef<'v>>> {
        Ok(ValueOfUnchecked::new(heap.alloc_list_iter(this.values())))
    }
}

#[cfg(test)]
mod tests {
    use crate::assert;

    #[test]
    fn test_error_codes() {
        assert::fail(r#"x = {"one": 1}; x.pop("four")"#, "not found");
        assert::fail("x = {}; x.popitem()", "empty");
    }

    #[test]
    fn test_dict_add() {
        assert::fail("{1: 2} + {3: 4}", "not supported");
    }

    #[test]
    fn test_dict_with_duplicates() {
        // In Starlark spec this is a runtime error. In Python it's fine.
        // We make it a runtime error, plus have a lint that checks for it statically.
        assert::fails("{40+2: 2, 6*7: 3}", &["key repeated", "42"]);
        // Also check we fail if the entire dictionary is static (a different code path).
        assert::fails("{42: 2, 42: 3}", &["key repeated", "42"]);
    }
}


/*
 * 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.
 */

//! Implementation of `enum` function.
use starlark_derive::starlark_module;

use crate as starlark;
use crate::environment::GlobalsBuilder;
use crate::values::enumeration::EnumType;
use crate::values::Heap;
use crate::values::Value;

#[starlark_module]
pub fn global(builder: &mut GlobalsBuilder) {
    /// The `enum` type represents one value picked from a set of values.
    ///
    /// For example:
    ///
    /// ```python
    /// MyEnum = enum("option1", "option2", True)
    /// ```
    ///
    /// This statement defines an enumeration `MyEnum` that consists of the three values `"option1"`, `"option2"` and `True`.
    ///
    /// Now `MyEnum` is defined, it's possible to do the following:
    ///
    /// * Create values of this type with `MyEnum("option2")`. It is a runtime error if the argument is not one of the predeclared values of the enumeration.
    /// * Get the type of the enum suitable for a type annotation with `MyEnum.type`.
    /// * Given a value of the enum (for example, `v = MyEnum("option2")`), get the underlying value `v.value == "option2"` or the index in the enumeration `v.index = 1`.
    /// * Get a list of the values that make up the array with `MyEnum.values() == ["option1", "option2", True]`.
    /// * Treat `MyEnum` a bit like an array, with `len(MyEnum) == 3`, `MyEnum[1] == MyEnum("option2")` and iteration over enums `[x.value for x in MyEnum] == ["option1", "option2", True]`.
    ///
    /// Enumeration types store each value once, which are then efficiently referenced by enumeration values.
    fn r#enum<'v>(
        #[starlark(args)] args: Vec<Value<'v>>,
        heap: &'v Heap,
    ) -> anyhow::Result<Value<'v>> {
        // Every Value must either be a field or a value (the type)
        EnumType::new(args, heap)
    }
}

#[cfg(test)]
mod tests {
    use crate::assert;
    use crate::assert::Assert;

    #[test]
    fn test_enum() {
        assert::pass(
            r#"
enum_type = enum("option1", "option2", True)
x = enum_type("option1")
assert_eq(x.value, "option1")
assert_eq(enum_type(True).value, True)
assert_eq(enum_type.type, "enum_type")
"#,
        );
        assert::fails(
            r#"
enum_type = enum("option1", "option2", True)
enum_type(False)"#,
            &["Unknown enum element", "`False`", "option1"],
        );
        assert::fails(
            r#"
enum_type = enum("option1", "option2", True)
enum_type("option3")"#,
            &["Unknown enum element", "`option3`"],
        );
        assert::fails(
            r#"
enum_type = enum("option1", "option1")
enum_type("option3")"#,
            &["distinct", "option1"],
        );
        assert::pass(
            r#"
enum_type = enum("option1","option2")
def foo(x: enum_type.type) -> "enum_type":
    return x
foo(enum_type("option1"))"#,
        );
        assert::pass(
            r#"
v = [enum("option1","option2")]
v_0 = v[0]
def foo(y: v_0.type) -> "enum":
    # TODO(nga): fails at compile time.
    return noop(y)
foo(v[0]("option1"))"#,
        );
        assert::pass(
            r#"
enum_type = enum("option1","option2")
assert_eq(enum_type.values(), ["option1","option2"])
assert_eq([enum_type[i].value for i in range(len(enum_type))], ["option1","option2"])
assert_eq(enum_type("option2").index, 1)
assert_eq([x.value for x in enum_type], ["option1","option2"])"#,
        );
        assert::pass(
            r#"
enum_type = enum("option1","option2")
x = enum_type("option1")
assert_eq(str(enum_type), "enum(\"option1\", \"option2\")")
assert_eq(str(x), "\"option1\"")
"#,
        );
        assert::pass(
            r#"
enum_type = enum("option1","option2")
repr(enum_type) # Check it is finite
"#,
        );
    }

    #[test]
    fn test_enum_equality() {
        assert::pass(
            r#"
enum_type = enum("option1", "option2", True)
assert_eq(enum_type("option1"), enum_type("option1"))
assert_eq(enum_type(True), enum_type(True))
assert_ne(enum_type("option1"), enum_type(True))
"#,
        );

        let mut a = Assert::new();
        a.module(
            "m",
            r#"
enum_type = enum("option1", "option2", True)
enum_val = enum_type("option1")
"#,
        );
        a.pass(
            r#"
load('m', 'enum_type', 'enum_val')
assert_eq(enum_val, enum_type("option1"))
assert_ne(enum_val, enum_type(True))
"#,
        );

        a = Assert::new();
        a.module(
            "m1",
            r#"
rt = enum(1)
"#,
        );
        a.module(
            "m2",
            r#"
rt = enum(1, 2)
"#,
        );
        a.pass(
            r#"
load('m1', r1='rt')
load('m2', r2='rt')
rt = enum(1)
diff = enum(1)
assert_eq(r1(1), rt(1))
assert_ne(rt(1), r2(1))
assert_ne(rt(1), diff(1))
"#,
        );
    }
}


/*
 * 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 std::fmt;

use itertools::Itertools;
use starlark_derive::starlark_module;

use crate as starlark;
use crate::environment::GlobalsBuilder;
use crate::eval::Evaluator;
use crate::values::function::StarlarkFunction;
use crate::values::iter_type::StarlarkIter;
use crate::values::none::NoneOr;
use crate::values::none::NoneType;
use crate::values::regex::StarlarkRegex;
use crate::values::Value;
use crate::values::ValueOfUnchecked;

#[starlark_module]
pub fn filter(builder: &mut GlobalsBuilder) {
    /// Apply a predicate to each element of the iterable, returning those that match.
    /// As a special case if the function is `None` then removes all the `None` values.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// filter(bool, [0, 1, False, True]) == [1, True]
    /// filter(lambda x: x > 2, [1, 2, 3, 4]) == [3, 4]
    /// filter(None, [True, None, False]) == [True, False]
    /// # "#);
    /// ```
    fn filter<'v>(
        #[starlark(require = pos)] func: NoneOr<ValueOfUnchecked<'v, StarlarkFunction>>,
        #[starlark(require = pos)] seq: ValueOfUnchecked<'v, StarlarkIter<Value<'v>>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Vec<Value<'v>>> {
        let mut res = Vec::new();

        for v in seq.get().iterate(eval.heap())? {
            match func {
                NoneOr::None => {
                    if !v.is_none() {
                        res.push(v);
                    }
                }
                NoneOr::Other(func) => {
                    if func.get().invoke_pos(&[v], eval)?.to_bool() {
                        res.push(v);
                    }
                }
            }
        }
        Ok(res)
    }
}

#[starlark_module]
pub fn map(builder: &mut GlobalsBuilder) {
    /// Apply a function to each element of the iterable, returning the results.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// map(abs, [7, -5, -6]) == [7, 5, 6]
    /// map(lambda x: x * 2, [1, 2, 3, 4]) == [2, 4, 6, 8]
    /// # "#);
    /// ```
    fn map<'v>(
        #[starlark(require = pos)] func: ValueOfUnchecked<'v, StarlarkFunction>,
        #[starlark(require = pos)] seq: ValueOfUnchecked<'v, StarlarkIter<Value<'v>>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Vec<Value<'v>>> {
        let it = seq.get().iterate(eval.heap())?;
        let mut res = Vec::with_capacity(it.size_hint().0);
        for v in it {
            res.push(func.get().invoke_pos(&[v], eval)?);
        }
        Ok(res)
    }
}

#[starlark_module]
pub fn debug(builder: &mut GlobalsBuilder) {
    /// Print the value with full debug formatting. The result may not be stable over time.
    /// Intended for debugging purposes and guaranteed to produce verbose output not suitable for user display.
    fn debug(#[starlark(require = pos)] val: Value) -> anyhow::Result<String> {
        Ok(format!("{:?}", val))
    }
}

#[starlark_module]
pub fn regex(builder: &mut GlobalsBuilder) {
    /// Creates a regex which can be used for matching.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// experimental_regex("^[a-z]*$").match("test") == True
    /// experimental_regex("^[a-z]*$").match("1234") == False
    /// # "#);
    /// ```
    fn experimental_regex<'v>(
        #[starlark(require = pos)] regex: &str,
    ) -> anyhow::Result<StarlarkRegex> {
        StarlarkRegex::new(regex)
    }
}

struct PrintWrapper<'a, 'b>(&'a Vec<Value<'b>>);
impl fmt::Display for PrintWrapper<'_, '_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (i, v) in self.0.iter().enumerate() {
            if i != 0 {
                f.write_str(" ")?;
            }
            fmt::Display::fmt(v, f)?;
        }
        Ok(())
    }
}

/// Invoked from `print` or `pprint` to print a value.
pub trait PrintHandler {
    /// If this function returns error, evaluation fails with this error.
    fn println(&self, text: &str) -> anyhow::Result<()>;
}

pub(crate) struct StderrPrintHandler;

impl PrintHandler for StderrPrintHandler {
    fn println(&self, text: &str) -> anyhow::Result<()> {
        eprintln!("{}", text);
        Ok(())
    }
}

#[starlark_module]
pub fn print(builder: &mut GlobalsBuilder) {
    /// Print some values to the output.
    fn print(#[starlark(args)] args: Vec<Value>, eval: &mut Evaluator) -> anyhow::Result<NoneType> {
        // In practice most users should want to put the print somewhere else, but this does for now
        // Unfortunately, we can't use PrintWrapper because strings to_str() and Display are different.
        eval.print_handler
            .println(&args.iter().map(|x| x.to_str()).join(" "))?;
        Ok(NoneType)
    }
}

#[starlark_module]
pub fn pprint(builder: &mut GlobalsBuilder) {
    fn pprint(
        #[starlark(args)] args: Vec<Value>,
        eval: &mut Evaluator,
    ) -> anyhow::Result<NoneType> {
        // In practice most users may want to put the print somewhere else, but this does for now
        eval.print_handler
            .println(&format!("{:#}", PrintWrapper(&args)))?;
        Ok(NoneType)
    }
}

#[starlark_module]
pub fn abs(builder: &mut GlobalsBuilder) {
    /// Take the absolute value of an int.
    //
    /// ```
    /// # starlark::assert::all_true(r#"
    /// abs(0)   == 0
    /// abs(-10) == 10
    /// abs(10)  == 10
    /// # "#);
    /// ```
    fn abs(#[starlark(require = pos)] x: i32) -> anyhow::Result<i32> {
        Ok(x.abs())
    }
}

#[cfg(test)]
mod tests {
    use std::cell::RefCell;
    use std::rc::Rc;

    use dupe::Dupe;

    use crate::assert;
    use crate::assert::Assert;
    use crate::stdlib::PrintHandler;

    #[test]
    fn test_filter() {
        let mut a = Assert::new();
        // TODO(nga): fix and enable.
        a.disable_static_typechecking();
        a.pass(
            r#"
def contains_hello(s):
    if "hello" in s:
        return True
    return False

def positive(i):
    return i > 0

assert_eq([], filter(positive, []))
assert_eq([1, 2, 3], filter(positive, [1, 2, 3]))
assert_eq([], filter(positive, [-1, -2, -3]))
assert_eq([1, 2, 3], filter(positive, [-1, 1, 2, -2, -3, 3]))
assert_eq(["hello world!"], filter(contains_hello, ["hello world!", "goodbye"]))
"#,
        );
    }

    #[test]
    fn test_map() {
        let mut a = Assert::new();
        // TODO: fix and enable.
        a.disable_static_typechecking();
        a.pass(
            r#"
def double(x):
    return x + x

assert_eq([], map(int, []))
assert_eq([1,2,3], map(int, ["1","2","3"]))
assert_eq(["0","1","2"], map(str, range(3)))
assert_eq(["11",8], map(double, ["1",4]))
"#,
        );
    }

    #[test]
    fn test_debug() {
        assert::pass(
            r#"assert_eq(
                debug([1,2]),
                "Value(ListGen(ListData { content: Cell { value: ValueTyped(Value(Array { len: 2, capacity: 2, iter_count: 0, content: [Value(1), Value(2)] })) } }))"
                )"#,
        );
    }

    #[test]
    fn test_print() {
        let s = Rc::new(RefCell::new(String::new()));
        let s_copy = s.dupe();
        struct PrintHandlerImpl {
            s: Rc<RefCell<String>>,
        }
        impl PrintHandler for PrintHandlerImpl {
            fn println(&self, s: &str) -> anyhow::Result<()> {
                *self.s.borrow_mut() = s.to_owned();
                Ok(())
            }
        }
        let print_handler = PrintHandlerImpl { s: s.dupe() };
        let mut a = Assert::new();
        a.set_print_handler(&print_handler);
        a.pass("print('hw')");
        assert_eq!("hw", s_copy.borrow().as_str());
    }
}


/*
 * 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.
 */

//! A module with the standard function and constants that are by default in all
//! dialect of Starlark

use std::char;
use std::cmp::Ordering;
use std::num::NonZeroI32;

use allocative::Allocative;
use either::Either;
use starlark_derive::starlark_module;

use crate as starlark;
use crate::codemap::Span;
use crate::codemap::Spanned;
use crate::collections::SmallMap;
use crate::environment::GlobalsBuilder;
use crate::eval::Arguments;
use crate::eval::Evaluator;
use crate::typing::error::TypingError;
use crate::typing::function::Arg;
use crate::typing::function::TyCustomFunctionImpl;
use crate::typing::oracle::ctx::TypingOracleCtx;
use crate::typing::Ty;
use crate::typing::TypingAttr;
use crate::typing::TypingOracle;
use crate::values::bool::StarlarkBool;
use crate::values::dict::value::FrozenDict;
use crate::values::dict::Dict;
use crate::values::dict::DictRef;
use crate::values::float::StarlarkFloat;
use crate::values::function::SpecialBuiltinFunction;
use crate::values::int::PointerI32;
use crate::values::list::value::FrozenList;
use crate::values::list::AllocList;
use crate::values::list::ListRef;
use crate::values::never::StarlarkNever;
use crate::values::none::NoneType;
use crate::values::num::NumRef;
use crate::values::range::Range;
use crate::values::string::repr::string_repr;
use crate::values::string::StarlarkStr;
use crate::values::tuple::value::FrozenTuple;
use crate::values::tuple::AllocTuple;
use crate::values::tuple::TupleRef;
use crate::values::types::int_or_big::StarlarkInt;
use crate::values::value_of_unchecked::ValueOfUnchecked;
use crate::values::AllocValue;
use crate::values::FrozenStringValue;
use crate::values::Heap;
use crate::values::StarlarkIter;
use crate::values::StringValue;
use crate::values::Value;
use crate::values::ValueError;
use crate::values::ValueLike;
use crate::values::ValueOf;

fn unpack_pair<'v>(pair: Value<'v>, heap: &'v Heap) -> anyhow::Result<(Value<'v>, Value<'v>)> {
    let mut it = pair.iterate(heap)?;
    if let Some(first) = it.next() {
        if let Some(second) = it.next() {
            if it.next().is_none() {
                return Ok((first, second));
            }
        }
    }
    Err(anyhow::anyhow!(
        "Found a non-pair element in the positional argument of dict(): {}",
        pair.to_repr(),
    ))
}

fn min_max_iter<'v>(
    mut it: impl Iterator<Item = Value<'v>>,
    key: Option<Value<'v>>,
    eval: &mut Evaluator<'v, '_>,
    // Select min on true, max on false.
    min: bool,
) -> anyhow::Result<Value<'v>> {
    let mut max = match it.next() {
        Some(x) => x,
        None => {
            return Err(anyhow::anyhow!(
                "Argument is an empty iterable, max() expect a non empty iterable"
            ));
        }
    };
    let update_max_ordering = if min {
        Ordering::Greater
    } else {
        Ordering::Less
    };
    match key {
        None => {
            for i in it {
                if max.compare(i)? == update_max_ordering {
                    max = i;
                }
            }
        }
        Some(key) => {
            let mut cached = key.invoke_pos(&[max], eval)?;
            for i in it {
                let keyi = key.invoke_pos(&[i], eval)?;
                if cached.compare(keyi)? == update_max_ordering {
                    max = i;
                    cached = keyi;
                }
            }
        }
    };
    Ok(max)
}

/// Common implementation of `min` and `max`.
fn min_max<'v>(
    mut args: Vec<Value<'v>>,
    key: Option<Value<'v>>,
    eval: &mut Evaluator<'v, '_>,
    // Select min on true, max on false.
    min: bool,
) -> anyhow::Result<Value<'v>> {
    if args.len() == 1 {
        let it = args.swap_remove(0).iterate(eval.heap())?;
        min_max_iter(it, key, eval, min)
    } else {
        min_max_iter(args.into_iter(), key, eval, min)
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Allocative)]
struct ZipType;

impl TyCustomFunctionImpl for ZipType {
    fn validate_call(
        &self,
        _span: Span,
        args: &[Spanned<Arg>],
        oracle: TypingOracleCtx,
    ) -> Result<Ty, TypingError> {
        let mut iter_item_types: Vec<Ty> = Vec::new();
        let mut seen_star_args = false;
        for arg in args {
            match &arg.node {
                Arg::Pos(pos) => match oracle.attribute(pos, TypingAttr::Iter) {
                    Some(Err(_)) => {
                        return Err(oracle.msg_error(arg.span, "Argument does not allow iteration"));
                    }
                    Some(Ok(t)) => iter_item_types.push(t),
                    None => iter_item_types.push(Ty::Any),
                },
                Arg::Name(_, _) => {
                    return Err(
                        oracle.msg_error(arg.span, "zip() does not accept keyword arguments")
                    );
                }
                Arg::Args(_) => {
                    seen_star_args = true;
                }
                Arg::Kwargs(_) => {
                    // `zip()` does not accept keyword args,
                    // but if `**kwargs` is empty, the call is valid.
                }
            }
        }
        if seen_star_args {
            Ok(Ty::list(Ty::Any))
        } else {
            Ok(Ty::list(Ty::Tuple(iter_item_types)))
        }
    }
}

#[starlark_module]
pub(crate) fn global_functions(builder: &mut GlobalsBuilder) {
    /// The `None` value, used to represent nothing.
    /// Implicitly returned from functions that don't have an explicit return.
    const None: NoneType = NoneType;

    /// A boolean representing true.
    const True: bool = true;

    /// A boolean representing false.
    const False: bool = false;

    /// fail: fail the execution
    ///
    /// ```
    /// # starlark::assert::fail(r#"
    /// fail("this is an error")  # fail: this is an error
    /// # "#, "this is an error");
    /// # starlark::assert::fail(r#"
    /// fail("oops", 1, False)  # fail: oops 1 False
    /// # "#, "oops 1 False");
    /// ```
    fn fail(#[starlark(args)] args: Vec<Value>) -> anyhow::Result<StarlarkNever> {
        let mut s = String::new();
        for x in args {
            s.push(' ');
            match x.unpack_str() {
                Some(x) => s.push_str(x),
                None => x.collect_repr(&mut s),
            }
        }
        Err(anyhow::anyhow!("fail:{}", s))
    }

    /// [any](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#any
    /// ): returns true if any value in the iterable object have a truth value
    /// of true.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// any([0, True]) == True
    /// any([0, 1]) == True
    /// any([0, 1, True]) == True
    /// any([0, 0]) == False
    /// any([0, False]) == False
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn any<'v>(
        #[starlark(require = pos)] x: ValueOfUnchecked<'v, StarlarkIter<Value<'v>>>,
        heap: &'v Heap,
    ) -> anyhow::Result<bool> {
        for i in x.get().iterate(heap)? {
            if i.to_bool() {
                return Ok(true);
            }
        }
        Ok(false)
    }

    /// [all](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#all
    /// ): returns true if all values in the iterable object have a truth value
    /// of true.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// all([1, True]) == True
    /// all([1, 1]) == True
    /// all([0, 1, True]) == False
    /// all([True, 1, True]) == True
    /// all([0, 0]) == False
    /// all([0, False]) == False
    /// all([True, 0]) == False
    /// all([1, False]) == False
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn all<'v>(
        #[starlark(require = pos)] x: ValueOfUnchecked<'v, StarlarkIter<Value<'v>>>,
        heap: &'v Heap,
    ) -> anyhow::Result<bool> {
        for i in x.get().iterate(heap)? {
            if !i.to_bool() {
                return Ok(false);
            }
        }
        Ok(true)
    }

    /// [bool](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#bool
    /// ): returns the truth value of any starlark value.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// bool() == False
    /// bool([]) == False
    /// bool([1]) == True
    /// bool(True) == True
    /// bool(False) == False
    /// bool(None) == False
    /// bool(bool) == True
    /// bool(1) == True
    /// bool(0) == False
    /// bool({}) == False
    /// bool({1:2}) == True
    /// bool(()) == False
    /// bool((1,)) == True
    /// bool("") == False
    /// bool("1") == True
    /// # "#);
    /// ```
    #[starlark(as_type = StarlarkBool, speculative_exec_safe)]
    fn bool(#[starlark(require = pos)] x: Option<Value>) -> anyhow::Result<bool> {
        match x {
            None => Ok(false),
            Some(x) => Ok(x.to_bool()),
        }
    }

    /// [chr](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#bool
    /// ): returns a string encoding a codepoint.
    ///
    /// `chr(i)` returns a returns a string that encodes the single Unicode code
    /// point whose value is specified by the integer `i`. `chr` fails
    /// unless `0 â‰¤ i â‰¤ 0x10FFFF`.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// chr(65) == 'A'
    /// chr(1049) == 'Ð™'
    /// chr(0x1F63F) == 'ðŸ˜¿'
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn chr(#[starlark(require = pos)] i: i32) -> anyhow::Result<char> {
        let cp = u32::try_from(i)
            .map_err(|_| anyhow::anyhow!("chr() parameter value negative integer {i}"))?;
        match char::from_u32(cp) {
            Some(x) => Ok(x),
            None => Err(anyhow::anyhow!(
                "chr() parameter value is 0x{:x} which is not a valid UTF-8 codepoint",
                cp
            )),
        }
    }

    /// [dict](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#dict
    /// ): creates a dictionary.
    ///
    /// `dict` creates a dictionary. It accepts up to one positional argument,
    /// which is interpreted as an iterable of two-element sequences
    /// (pairs), each specifying a key/value pair in the
    /// resulting dictionary.
    ///
    /// `dict` also accepts any number of keyword arguments, each of which
    /// specifies a key/value pair in the resulting dictionary; each keyword
    /// is treated as a string.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// dict() == {}
    /// dict(**{'a': 1}) == {'a': 1}
    /// dict({'a': 1}) == {'a': 1}
    /// dict([(1, 2), (3, 4)]) == {1: 2, 3: 4}
    /// dict([(1, 2), ['a', 'b']]) == {1: 2, 'a': 'b'}
    /// dict(one=1, two=2) == {'one': 1, 'two': 2}
    /// dict([(1, 2)], x=3) == {1: 2, 'x': 3}
    /// dict([('x', 2)], x=3) == {'x': 3}
    /// # "#);
    /// # starlark::assert::is_true(r#"
    /// x = {'a': 1}
    /// y = dict([('x', 2)], **x)
    /// x == {'a': 1} and y == {'x': 2, 'a': 1}
    /// # "#);
    /// ```
    #[starlark(
        as_type = FrozenDict,
        speculative_exec_safe,
        special_builtin_function = SpecialBuiltinFunction::Dict,
    )]
    fn dict<'v>(args: &Arguments<'v, '_>, heap: &'v Heap) -> anyhow::Result<Dict<'v>> {
        // Dict is super hot, and has a slightly odd signature, so we can do a bunch of special cases on it.
        // In particular, we don't generate the kwargs if there are no positional arguments.
        // Therefore we make it take the raw Arguments.
        // It might have one positional argument, which could be a dict or an array of pairs.
        // It might have named/kwargs arguments, which we copy over (afterwards).

        let pos = args.optional1(heap)?;
        let kwargs = args.names()?;

        match pos {
            None => Ok(kwargs),
            Some(pos) => {
                let mut result = match DictRef::from_value(pos) {
                    Some(pos) => {
                        let mut result = pos.clone();
                        result.reserve(kwargs.len());
                        result
                    }
                    None => {
                        let it = pos.iterate(heap)?;
                        let mut result = SmallMap::with_capacity(it.size_hint().0 + kwargs.len());
                        for el in it {
                            let (k, v) = unpack_pair(el, heap)?;
                            let k = k.get_hashed()?;
                            result.insert_hashed(k, v);
                        }
                        Dict::new(result)
                    }
                };
                for (k, v) in kwargs.iter_hashed() {
                    result.insert_hashed(k, v);
                }
                Ok(result)
            }
        }
    }

    /// [dir](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#dir
    /// ): list attributes of a value.
    ///
    /// `dir(x)` returns a list of the names of the attributes (fields and
    /// methods) of its operand. The attributes of a value `x` are the names
    /// `f` such that `x.f` is a valid expression.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// "capitalize" in dir("abc")
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn dir(#[starlark(require = pos)] x: Value) -> anyhow::Result<Vec<String>> {
        Ok(x.dir_attr())
    }

    /// [enumerate](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#enumerate
    /// ): return a list of (index, element) from an iterable.
    ///
    /// `enumerate(x)` returns a list of `(index, value)` pairs, each containing
    /// successive values of the iterable sequence and the index of the
    /// value within the sequence.
    ///
    /// The optional second parameter, `start`, specifies an integer value to
    /// add to each index.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// enumerate(["zero", "one", "two"]) == [(0, "zero"), (1, "one"), (2, "two")]
    /// enumerate(["one", "two"], 1) == [(1, "one"), (2, "two")]
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn enumerate<'v>(
        #[starlark(require = pos)] it: ValueOfUnchecked<'v, StarlarkIter<Value<'v>>>,
        #[starlark(default = 0)] start: i32,
        heap: &'v Heap,
    ) -> anyhow::Result<impl AllocValue<'v>> {
        let v = it
            .get()
            .iterate(heap)?
            .enumerate()
            .map(move |(k, v)| (k as i32 + start, v));
        Ok(AllocList(v))
    }

    /// [float](
    /// https://github.com/google/skylark/blob/a5f7082aabed29c0e429c722292c66ec8ecf9591/doc/spec.md#float
    /// ): interprets its argument as a floating-point number.
    ///
    /// If x is a `float`, the result is x.
    /// if x is an `int`, the result is the nearest floating point value to x.
    /// If x is a string, the string is interpreted as a floating-point literal.
    /// With no arguments, `float()` returns `0.0`.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// float() == 0.0
    /// float(1) == 1.0
    /// float('1') == 1.0
    /// float('1.0') == 1.0
    /// float('.25') == 0.25
    /// float('1e2') == 100.0
    /// float(False) == 0.0
    /// float(True) == 1.0
    /// # "#);
    /// # starlark::assert::fail(r#"
    /// float("hello")   # error: not a valid number
    /// # "#, "not a valid number");
    /// # starlark::assert::fail(r#"
    /// float([])   # error
    /// # "#, "Expected type");
    /// ```
    #[starlark(as_type = StarlarkFloat, speculative_exec_safe)]
    fn float(
        #[starlark(require = pos)] a: Option<Either<Either<NumRef, bool>, &str>>,
    ) -> anyhow::Result<f64> {
        if a.is_none() {
            return Ok(0.0);
        }
        let a = a.unwrap();
        match a {
            Either::Left(Either::Left(f)) => Ok(f.as_float()),
            Either::Left(Either::Right(b)) => Ok(if b { 1.0 } else { 0.0 }),
            Either::Right(s) => {
                match s.parse::<f64>() {
                    Ok(f) => {
                        if f.is_infinite() && !s.to_lowercase().contains("inf") {
                            // if a resulting float is infinite but the parsed string is not explicitly infinity then we should fail with an error
                            Err(anyhow::anyhow!(
                                "float() floating-point number too large: {}",
                                s
                            ))
                        } else {
                            Ok(f)
                        }
                    }
                    Err(x) => {
                        let mut repr = String::new();
                        string_repr(s, &mut repr);
                        Err(anyhow::anyhow!("{} is not a valid number: {}", repr, x,))
                    }
                }
            }
        }
    }

    /// [getattr](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#getattr
    /// ): returns the value of an attribute
    ///
    /// `getattr(x, name)` returns the value of the attribute (field or method)
    /// of x named `name`. It is a dynamic error if x has no such attribute.
    ///
    /// `getattr(x, "f")` is equivalent to `x.f`.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// getattr("banana", "split")("a") == ["b", "n", "n", ""] # equivalent to "banana".split("a")
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn getattr<'v>(
        #[starlark(require = pos)] a: Value<'v>,
        #[starlark(require = pos)] attr: &str,
        #[starlark(require = pos)] default: Option<Value<'v>>,
        heap: &'v Heap,
    ) -> anyhow::Result<Value<'v>> {
        // TODO(nga): this doesn't cache string hash, so it is suboptimal.
        match a.get_attr(attr, heap)? {
            Some(v) => Ok(v),
            None => match default {
                Some(x) => Ok(x),
                None => ValueError::unsupported_owned(a.get_type(), &format!(".{}", attr), None),
            },
        }
    }

    /// [hasattr](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#hasattr
    /// ): test if an object has an attribute
    ///
    /// `hasattr(x, name)` reports whether x has an attribute (field or method)
    /// named `name`.
    #[starlark(speculative_exec_safe)]
    fn hasattr<'v>(
        #[starlark(require = pos)] a: Value<'v>,
        #[starlark(require = pos)] attr: &str,
        heap: &'v Heap,
    ) -> anyhow::Result<bool> {
        Ok(a.has_attr(attr, heap))
    }

    /// [hash](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#hash
    /// ): returns the hash number of a value.
    ///
    /// `hash(x)`` returns an integer hash value for x such that `x == y`
    /// implies `hash(x) == hash(y)``.
    ///
    /// `hash` fails if x, or any value upon which its hash depends, is
    /// unhashable.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// hash("hello") != hash("world")
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn hash(#[starlark(require = pos)] a: &str) -> anyhow::Result<i32> {
        // From the starlark spec:
        // > the hash function for strings is the same as that implemented by java.lang.String.hashCode,
        // > a simple polynomial accumulator over the UTF-16 transcoding of the string:
        // > `s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]`
        // As per spec the function should only support string and bytes types.
        // We don't have support for bytes, so parameter is forced to be a string.

        // Most strings are ASCII strings, try them first.
        #[allow(clippy::never_loop)]
        'ascii: loop {
            let mut hash = 0i32;
            for &b in a.as_bytes() {
                if b > 0x7f {
                    break 'ascii;
                }
                hash = hash.wrapping_mul(31i32).wrapping_add(b as i32);
            }
            return Ok(hash);
        }

        Ok(a.encode_utf16().fold(0i32, |hash: i32, c: u16| {
            31i32.wrapping_mul(hash).wrapping_add(c as i32)
        }))
    }

    /// [int](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#int
    /// ): convert a value to integer.
    ///
    /// `int(x[, base])` interprets its argument as an integer.
    ///
    /// If x is an `int`, the result is x.
    /// If x is a `float`, the result is the integer value nearest to x,
    /// truncating towards zero; it is an error if x is not finite (`NaN`,
    /// `+Inf`, `-Inf`).
    /// If x is a `bool`, the result is 0 for `False` or 1 for `True`.
    ///
    /// If x is a string, it is interpreted like a string literal;
    /// an optional base prefix (`0`, `0b`, `0B`, `0x`, `0X`) determines which
    /// base to use. The string may specify an arbitrarily large integer,
    /// whereas true integer literals are restricted to 64 bits.
    /// If a non-zero `base` argument is provided, the string is interpreted
    /// in that base and no base prefix is permitted; the base argument may
    /// specified by name.
    ///
    /// `int()` with no arguments returns 0.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// int() == 0
    /// int(1) == 1
    /// int(False) == 0
    /// int(True) == 1
    /// int('1') == 1
    /// int('16') == 16
    /// int('16', 10) == 16
    /// int('16', 8) == 14
    /// int('16', 16) == 22
    /// int(0.0) == 0
    /// int(3.14) == 3
    /// int(-12345.6789) == -12345
    /// int(2e9) == 2000000000
    /// # "#);
    /// # starlark::assert::fail(r#"
    /// int("hello")   # error: Cannot parse
    /// # "#, "Cannot parse");
    /// # starlark::assert::fail(r#"
    /// int(float("nan"))   # error: cannot be represented as exact integer
    /// # "#, "cannot be represented as exact integer");
    /// # starlark::assert::fail(r#"
    /// int(float("inf"))   # error: cannot be represented as exact integer
    /// # "#, "cannot be represented as exact integer");
    /// ```
    #[starlark(as_type = PointerI32, speculative_exec_safe)]
    fn int<'v>(
        #[starlark(require = pos)] a: Option<
            ValueOf<'v, Either<Either<NumRef<'v>, bool>, &'v str>>,
        >,
        base: Option<i32>,
        heap: &'v Heap,
    ) -> anyhow::Result<ValueOfUnchecked<'v, StarlarkInt>> {
        let Some(a) = a else {
            return Ok(ValueOfUnchecked::new(heap.alloc(0)));
        };
        let num_or_bool = match a.typed {
            Either::Left(num_or_bool) => num_or_bool,
            Either::Right(s) => {
                let base = base.unwrap_or(0);
                if base == 1 || base < 0 || base > 36 {
                    return Err(anyhow::anyhow!(
                        "{} is not a valid base, int() base must be >= 2 and <= 36",
                        base
                    ));
                }
                let (negate, s) = {
                    match s.chars().next() {
                        Some('+') => (false, s.get(1..).unwrap()),
                        Some('-') => (true, s.get(1..).unwrap()),
                        _ => (false, s),
                    }
                };
                let base = if base == 0 {
                    match s.get(0..2) {
                        Some("0b") | Some("0B") => 2,
                        Some("0o") | Some("0O") => 8,
                        Some("0x") | Some("0X") => 16,
                        _ => 10,
                    }
                } else {
                    base as u32
                };
                let s = match base {
                    16 => {
                        if s.starts_with("0x") || s.starts_with("0X") {
                            s.get(2..).unwrap()
                        } else {
                            s
                        }
                    }
                    8 => {
                        if s.starts_with("0o") || s.starts_with("0O") {
                            s.get(2..).unwrap()
                        } else {
                            s
                        }
                    }
                    2 => {
                        if s.starts_with("0b") || s.starts_with("0B") {
                            s.get(2..).unwrap()
                        } else {
                            s
                        }
                    }
                    _ => s,
                };
                // We already handled the sign above, so we are not trying to parse another sign.
                if s.starts_with('-') || s.starts_with('+') {
                    return Err(anyhow::anyhow!("Cannot parse `{}` as an integer", s,));
                }

                let x = StarlarkInt::from_str_radix(s, base)?;
                let x = if negate { -x } else { x };
                return Ok(ValueOfUnchecked::new(heap.alloc(x)));
            }
        };

        if let Some(base) = base {
            return Err(anyhow::anyhow!(
                "int() cannot convert non-string with explicit base '{}'",
                base
            ));
        }

        match num_or_bool {
            Either::Left(NumRef::Int(_)) => Ok(ValueOfUnchecked::new(a.value)),
            Either::Left(NumRef::Float(f)) => Ok(ValueOfUnchecked::new(
                heap.alloc(StarlarkInt::from_f64_exact(f.trunc())?),
            )),
            Either::Right(b) => Ok(ValueOfUnchecked::new(heap.alloc(b as i32))),
        }
    }

    /// [len](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#len
    /// ): get the length of a sequence
    ///
    /// `len(x)` returns the number of elements in its argument.
    ///
    /// It is a dynamic error if its argument is not a sequence.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// len(()) == 0
    /// len({}) == 0
    /// len([]) == 0
    /// len([1]) == 1
    /// len([1,2]) == 2
    /// len({'16': 10}) == 1
    /// # "#);
    /// # starlark::assert::fail(r#"
    /// len(True)    # error: not supported
    /// # "#, "not supported");
    /// ```
    #[starlark(speculative_exec_safe)]
    fn len(#[starlark(require = pos)] a: Value) -> anyhow::Result<i32> {
        a.length()
    }

    /// [list](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#list
    /// ): construct a list.
    ///
    /// `list(x)` returns a new list containing the elements of the
    /// iterable sequence x.
    ///
    /// With no argument, `list()` returns a new empty list.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// list()        == []
    /// list((1,2,3)) == [1, 2, 3]
    /// # "#);
    /// # starlark::assert::fail(r#"
    /// list("strings are not iterable") # error: not supported
    /// # "#, r#"Expected type `iter("")` but got `str.type`"#);
    /// ```
    #[starlark(
        as_type = FrozenList,
        speculative_exec_safe,
        special_builtin_function = SpecialBuiltinFunction::List,
    )]
    fn list<'v>(
        #[starlark(require = pos)] a: Option<ValueOfUnchecked<'v, StarlarkIter<Value<'v>>>>,
        heap: &'v Heap,
    ) -> anyhow::Result<ValueOfUnchecked<'v, &'v ListRef<'v>>> {
        Ok(ValueOfUnchecked::new(if let Some(a) = a {
            if let Some(xs) = ListRef::from_value(a.get()) {
                heap.alloc_list(xs.content())
            } else {
                let it = a.get().iterate(heap)?;
                heap.alloc(AllocList(it))
            }
        } else {
            heap.alloc(AllocList::EMPTY)
        }))
    }

    /// [max](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#max
    /// ): returns the maximum of a sequence.
    ///
    /// `max(x)` returns the greatest element in the iterable sequence x.
    ///
    /// It is an error if any element does not support ordered comparison,
    /// or if the sequence is empty.
    ///
    /// The optional named parameter `key` specifies a function to be applied
    /// to each element prior to comparison.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// max([3, 1, 4, 1, 5, 9])               == 9
    /// max("two", "three", "four")           == "two"    # the lexicographically greatest
    /// max("two", "three", "four", key=len)  == "three"  # the longest
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn max<'v>(
        #[starlark(args)] mut args: Vec<Value<'v>>,
        key: Option<Value<'v>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        min_max(args, key, eval, false)
    }

    /// [min](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#min
    /// ): returns the minimum of a sequence.
    ///
    /// `min(x)` returns the least element in the iterable sequence x.
    ///
    /// It is an error if any element does not support ordered comparison,
    /// or if the sequence is empty.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// min([3, 1, 4, 1, 5, 9])                 == 1
    /// min("two", "three", "four")             == "four"  # the lexicographically least
    /// min("two", "three", "four", key=len)    == "two"   # the shortest
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn min<'v>(
        #[starlark(args)] mut args: Vec<Value<'v>>,
        key: Option<Value<'v>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        min_max(args, key, eval, true)
    }

    /// [ord](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.mdord
    /// ): returns the codepoint of a character
    ///
    /// `ord(s)` returns the integer value of the sole Unicode code point
    /// encoded by the string `s`.
    ///
    /// If `s` does not encode exactly one Unicode code point, `ord` fails.
    /// Each invalid code within the string is treated as if it encodes the
    /// Unicode replacement character, U+FFFD.
    ///
    /// Example:
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// ord("A")                                == 65
    /// ord("Ð™")                                == 1049
    /// ord("ðŸ˜¿")                               == 0x1F63F
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn ord<'v>(#[starlark(require = pos)] a: StringValue<'v>) -> anyhow::Result<i32> {
        let mut chars = a.as_str().chars();
        if let Some(c) = chars.next() {
            if chars.next().is_none() {
                return Ok(u32::from(c) as i32);
            }
        }
        Err(anyhow::anyhow!(
            "ord(): {} is not a single character string",
            a.to_value().to_repr()
        ))
    }

    /// [range](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#range
    /// ): return a range of integers
    ///
    /// `range` returns a tuple of integers defined by the specified interval
    /// and stride.
    ///
    /// ```python
    /// range(stop)                             # equivalent to range(0, stop)
    /// range(start, stop)                      # equivalent to range(start, stop, 1)
    /// range(start, stop, step)
    /// ```
    ///
    /// `range` requires between one and three integer arguments.
    /// With one argument, `range(stop)` returns the ascending sequence of
    /// non-negative integers less than `stop`.
    /// With two arguments, `range(start, stop)` returns only integers not less
    /// than `start`.
    ///
    /// With three arguments, `range(start, stop, step)` returns integers
    /// formed by successively adding `step` to `start` until the value meets or
    /// passes `stop`. A call to `range` fails if the value of `step` is
    /// zero.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// list(range(10))                         == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    /// list(range(3, 10))                      == [3, 4, 5, 6, 7, 8, 9]
    /// list(range(3, 10, 2))                   == [3, 5, 7, 9]
    /// list(range(10, 3, -2))                  == [10, 8, 6, 4]
    /// # "#);
    /// ```
    #[starlark(as_type = Range, speculative_exec_safe)]
    fn range(
        #[starlark(require = pos)] a1: i32,
        #[starlark(require = pos)] a2: Option<i32>,
        #[starlark(require = pos, default = 1)] step: i32,
    ) -> anyhow::Result<Range> {
        let start = match a2 {
            None => 0,
            Some(_) => a1,
        };
        let stop = a2.unwrap_or(a1);
        let step = match NonZeroI32::new(step) {
            Some(step) => step,
            None => {
                return Err(anyhow::anyhow!(
                    "Third argument of range (step) cannot be zero"
                ));
            }
        };
        Ok(Range::new(start, stop, step))
    }

    /// [repr](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#repr
    /// ): formats its argument as a string.
    ///
    /// All strings in the result are double-quoted.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// repr(1)                 == '1'
    /// repr("x")               == "\"x\""
    /// repr([1, "x"])          == "[1, \"x\"]"
    /// repr("test \"'")        == "\"test \\\"'\""
    /// repr("x\"yðŸ˜¿ \\'")      == "\"x\\\"y\\U0001f63f \\\\'\""
    /// "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn repr<'v>(
        #[starlark(require = pos)] a: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StringValue<'v>> {
        let mut s = eval.string_pool.alloc();
        a.collect_repr(&mut s);
        let r = eval.heap().alloc_str(&s);
        eval.string_pool.release(s);
        Ok(r)
    }

    /// [reversed](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#reversed
    /// ): reverse a sequence
    ///
    /// `reversed(x)` returns a new list containing the elements of the iterable
    /// sequence x in reverse order.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// reversed(['a', 'b', 'c'])              == ['c', 'b', 'a']
    /// reversed(range(5))                     == [4, 3, 2, 1, 0]
    /// reversed("stressed".elems())           == ["d", "e", "s", "s", "e", "r", "t", "s"]
    /// reversed({"one": 1, "two": 2}.keys())  == ["two", "one"]
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn reversed<'v>(
        #[starlark(require = pos)] a: ValueOfUnchecked<'v, StarlarkIter<Value<'v>>>,
        heap: &'v Heap,
    ) -> anyhow::Result<Vec<Value<'v>>> {
        let mut v: Vec<Value> = a.get().iterate(heap)?.collect();
        v.reverse();
        Ok(v)
    }

    /// [sorted](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#sorted
    /// ): sort a sequence
    ///
    /// `sorted(x)` returns a new list containing the elements of the iterable
    /// sequence x, in sorted order.  The sort algorithm is stable.
    ///
    /// The optional named parameter `reverse`, if true, causes `sorted` to
    /// return results in reverse sorted order.
    ///
    /// The optional named parameter `key` specifies a function of one
    /// argument to apply to obtain the value's sort key.
    /// The default behavior is the identity function.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// sorted([3, 1, 4, 1, 5, 9])                               == [1, 1, 3, 4, 5, 9]
    /// sorted([3, 1, 4, 1, 5, 9], reverse=True)                 == [9, 5, 4, 3, 1, 1]
    /// sorted(["two", "three", "four"], key=len)                == ["two", "four", "three"] # shortest to longest
    /// sorted(["two", "three", "four"], key=len, reverse=True)  == ["three", "four", "two"] # longest to shortest
    /// # "#);
    /// ```
    // This function is not spec-safe, because it may call `key` function
    // which might be not spec-safe.
    fn sorted<'v>(
        #[starlark(require = pos)] x: ValueOfUnchecked<'v, ValueOfUnchecked<Value<'v>>>,
        #[starlark(require = named)] key: Option<Value<'v>>,
        #[starlark(require = named, default = false)] reverse: bool,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<AllocList<impl IntoIterator<Item = Value<'v>>>> {
        let it = x.get().iterate(eval.heap())?;
        let mut it = match key {
            None => it.map(|x| (x, x)).collect(),
            Some(key) => {
                let mut v = Vec::new();
                for el in it {
                    v.push((el, key.invoke_pos(&[el], eval)?));
                }
                v
            }
        };

        let mut compare_ok = Ok(());

        it.sort_by(|x: &(Value, Value), y: &(Value, Value)| {
            let ord_or_err = if reverse {
                x.1.compare(y.1).map(Ordering::reverse)
            } else {
                x.1.compare(y.1)
            };
            match ord_or_err {
                Ok(r) => r,
                Err(e) => {
                    compare_ok = Err(e);
                    Ordering::Equal // does not matter
                }
            }
        });

        compare_ok?;

        Ok(AllocList(it.into_iter().map(|x| x.0)))
    }

    /// [str](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#str
    /// ): formats its argument as a string.
    ///
    /// If x is a string, the result is x (without quotation).
    /// All other strings, such as elements of a list of strings, are
    /// double-quoted.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// str(1)                          == '1'
    /// str("x")                        == 'x'
    /// str([1, "x"])                   == "[1, \"x\"]"
    /// # "#);
    /// ```
    #[starlark(as_type = StarlarkStr, speculative_exec_safe)]
    fn str<'v>(
        #[starlark(require = pos)] a: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StringValue<'v>> {
        if let Some(a) = StringValue::new(a) {
            // Special case that can avoid reallocating, but is equivalent.
            Ok(a)
        } else {
            let mut s = eval.string_pool.alloc();
            a.collect_repr(&mut s);
            let r = eval.heap().alloc_str(&s);
            eval.string_pool.release(s);
            Ok(r)
        }
    }

    /// [tuple](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#tuple
    /// ): returns a tuple containing the elements of the iterable x.
    ///
    /// With no arguments, `tuple()` returns the empty tuple.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// tuple() == ()
    /// tuple([1,2,3]) == (1, 2, 3)
    /// # "#);
    /// ```
    #[starlark(as_type = FrozenTuple, speculative_exec_safe)]
    fn tuple<'v>(
        #[starlark(require = pos)] a: Option<ValueOfUnchecked<'v, StarlarkIter<Value<'v>>>>,
        heap: &'v Heap,
    ) -> anyhow::Result<ValueOfUnchecked<'v, &'v TupleRef<'v>>> {
        if let Some(a) = a {
            if TupleRef::from_value(a.get()).is_some() {
                return Ok(ValueOfUnchecked::new(a.get()));
            }

            let it = a.get().iterate(heap)?;
            Ok(ValueOfUnchecked::new(heap.alloc_tuple_iter(it)))
        } else {
            Ok(ValueOfUnchecked::new(heap.alloc(AllocTuple::EMPTY)))
        }
    }

    /// [type](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#type
    /// ): returns a string describing the type of its operand.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// type(None)              == "NoneType"
    /// type(0)                 == "int"
    /// type(1)                 == "int"
    /// type(())                == "tuple"
    /// type("hello")           == "string"
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe)]
    fn r#type<'v>(#[starlark(require = pos)] a: Value) -> anyhow::Result<FrozenStringValue> {
        Ok(a.get_type_value())
    }

    /// [zip](
    /// https://github.com/google/skylark/blob/a0e5de7e63b47e716cca7226662a4c95d47bf873/doc/spec.md#zip
    /// ): zip several iterables together
    ///
    /// `zip()` returns a new list of n-tuples formed from corresponding
    /// elements of each of the n iterable sequences provided as arguments to
    /// `zip`.  That is, the first tuple contains the first element of each of
    /// the sequences, the second element contains the second element of each
    /// of the sequences, and so on.  The result list is only as long as the
    /// shortest of the input sequences.
    ///
    /// ```
    /// # starlark::assert::all_true(r#"
    /// zip()                           == []
    /// zip(range(5))                   == [(0,), (1,), (2,), (3,), (4,)]
    /// zip(range(5), "abc".elems())    == [(0, "a"), (1, "b"), (2, "c")]
    /// # "#);
    /// ```
    #[starlark(speculative_exec_safe, ty_custom_function = ZipType)]
    fn zip<'v>(
        #[starlark(args)] args: Vec<Value<'v>>,
        heap: &'v Heap,
    ) -> anyhow::Result<Vec<Value<'v>>> {
        let mut v = Vec::new();
        let mut first = true;
        for arg in args {
            let mut idx = 0;
            for e in arg.iterate(heap)? {
                if first {
                    v.push(heap.alloc((e,)));
                    idx += 1;
                } else if idx < v.len() {
                    v[idx] = v[idx].add(heap.alloc((e,)), heap)?;
                    idx += 1;
                }
            }
            v.truncate(idx);
            first = false;
        }
        Ok(v)
    }
}

#[cfg(test)]
mod tests {
    use crate::assert;
    use crate::assert::Assert;

    #[test]
    fn test_constants() {
        assert::is_true("not None");
        assert::is_true("not False");
        assert::is_true("True");
    }

    #[test]
    fn test_chr() {
        assert::fail("chr(0x110000)", "not a valid UTF-8");
        assert::fail("chr(-1)", "negative");
    }

    #[test]
    fn test_hash() {
        assert::eq("0", "hash('')");
        assert::eq("97", "hash('a')");
        assert::eq("3105", "hash('ab')");
        assert::eq("96354", "hash('abc')");
        assert::eq("2987074", "hash('abcd')");
        assert::eq("92599395", "hash('abcde')");
        assert::eq("-1424385949", "hash('abcdef')");
        assert::all_true(
            r#"
hash("te") == hash("te")
hash("te") != hash("st")
x = "test"; y = "te" + "st"; hash(y) == hash(y)
"#,
        );
        assert::fail("noop(hash)(None)", "doesn't match");
        assert::fail("noop(hash)(True)", "doesn't match");
        assert::fail("noop(hash)(1)", "doesn't match");
        assert::fail("noop(hash)([])", "doesn't match");
        assert::fail("noop(hash)({})", "doesn't match");
        assert::fail("noop(hash)(range(1))", "doesn't match");
        assert::fail("noop(hash)((1, 2))", "doesn't match");
        assert::fail(
            r#"
def foo():
    pass
noop(hash)(foo)
"#,
            "doesn't match",
        );
    }

    #[test]
    fn test_int() {
        assert::eq("2147483647", "int('2147483647')");
        assert::eq("-2147483647 - 1", "int('-2147483648')");
        assert::eq("0", "int('0')");
        assert::eq("0", "int('-0')");
        assert::eq(
            "999999999999999945322333868247445125709646570021247924665841614848",
            "int(1e66)",
        );
        assert::eq("2147483648", "int('2147483648')");
        assert::eq("-2147483649", "int('-2147483649')");
    }

    #[test]
    fn test_tuple() {
        let mut a = Assert::new();
        // TODO(nga): fix and enable.
        a.disable_static_typechecking();
        a.eq("(1, 2)", "tuple((1, 2))");
        a.eq("(1, 2)", "tuple([1, 2])");
    }
}


/*
 * 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::str::FromStr;

use either::Either;
use num_bigint::BigInt;
use starlark_derive::starlark_module;

use crate as starlark;
use crate::collections::SmallMap;
use crate::environment::GlobalsBuilder;
use crate::typing::Ty;
use crate::values::dict::AllocDict;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::types::int_or_big::StarlarkInt;
use crate::values::AllocFrozenValue;
use crate::values::AllocValue;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::Value;

impl StarlarkTypeRepr for serde_json::Number {
    fn starlark_type_repr() -> Ty {
        Either::<i32, f64>::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for serde_json::Number {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        if let Some(x) = self.as_u64() {
            heap.alloc(x)
        } else if let Some(x) = self.as_i64() {
            heap.alloc(x)
        } else if let Some(x) = self.as_f64() {
            heap.alloc(x)
        } else if let Ok(x) = BigInt::from_str(&self.to_string()) {
            heap.alloc(StarlarkInt::from(x))
        } else {
            panic!("Unrepresentable number: {:?}", self)
        }
    }
}

impl AllocFrozenValue for serde_json::Number {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        if let Some(x) = self.as_u64() {
            heap.alloc(x)
        } else if let Some(x) = self.as_i64() {
            heap.alloc(x)
        } else if let Some(x) = self.as_f64() {
            heap.alloc(x)
        } else if let Ok(x) = BigInt::from_str(&self.to_string()) {
            heap.alloc(StarlarkInt::from(x))
        } else {
            panic!("Unrepresentable number: {:?}", self)
        }
    }
}

impl<K: StarlarkTypeRepr, V: StarlarkTypeRepr> StarlarkTypeRepr for serde_json::Map<K, V> {
    fn starlark_type_repr() -> Ty {
        AllocDict::<SmallMap<K, V>>::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for serde_json::Map<String, serde_json::Value> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        // For some reason `IntoIter` is implemented only for `String, Value` parameters,
        // so this implementation is limited.
        heap.alloc(AllocDict(self))
    }
}

impl AllocFrozenValue for serde_json::Map<String, serde_json::Value> {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc(AllocDict(self))
    }
}

impl StarlarkTypeRepr for serde_json::Value {
    fn starlark_type_repr() -> Ty {
        // Any.
        Value::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for serde_json::Value {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        match self {
            serde_json::Value::Null => Value::new_none(),
            serde_json::Value::Bool(x) => Value::new_bool(x),
            serde_json::Value::Number(x) => heap.alloc(x),
            serde_json::Value::String(x) => heap.alloc(x),
            serde_json::Value::Array(x) => heap.alloc(x),
            serde_json::Value::Object(x) => heap.alloc(x),
        }
    }
}

impl AllocFrozenValue for serde_json::Value {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        match self {
            serde_json::Value::Null => FrozenValue::new_none(),
            serde_json::Value::Bool(x) => FrozenValue::new_bool(x),
            serde_json::Value::Number(x) => heap.alloc(x),
            serde_json::Value::String(x) => heap.alloc(x),
            serde_json::Value::Array(x) => heap.alloc(x),
            serde_json::Value::Object(x) => heap.alloc(x),
        }
    }
}

pub(crate) fn json(globals: &mut GlobalsBuilder) {
    #[starlark_module]
    fn json_members(globals: &mut GlobalsBuilder) {
        fn encode(#[starlark(require = pos)] x: Value) -> anyhow::Result<String> {
            x.to_json()
        }

        fn decode<'v>(
            #[starlark(require = pos)] x: &str,
            heap: &'v Heap,
        ) -> anyhow::Result<Value<'v>> {
            Ok(heap.alloc(serde_json::from_str::<serde_json::Value>(x)?))
        }
    }

    // Copying Bazel's json module: https://bazel.build/rules/lib/json
    // or starlark-go json module:
    // https://github.com/google/starlark-go/blob/d1966c6b9fcd6631f48f5155f47afcd7adcc78c2/lib/json/json.go#L28
    globals.struct_("json", json_members);
}

#[cfg(test)]
mod tests {
    use crate::assert::Assert;

    #[test]
    fn test_json_encode() {
        let a = Assert::new();
        a.eq("'[10]'", "json.encode([10])");
    }

    #[test]
    fn test_json_decode() {
        let a = Assert::new();
        a.eq(
            "[10, None, False, {'k': 'v'}]",
            "json.decode('[10, null, false, {\"k\": \"v\"}]')",
        );

        a.eq("3.142", "json.decode('3.142')");
        a.eq(
            "123456789123456789123456789",
            "json.decode('123456789123456789123456789')",
        );
    }
}