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

//! Function types, including native functions and `object.member` functions.

use std::collections::HashMap;

use allocative::Allocative;
use derivative::Derivative;
use derive_more::Display;
use dupe::Dupe;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::coerce::Coerce;
use crate::docs::DocFunction;
use crate::docs::DocItem;
use crate::docs::DocProperty;
use crate::docs::DocString;
use crate::docs::DocStringKind;
use crate::docs::DocType;
use crate::eval::Arguments;
use crate::eval::Evaluator;
use crate::eval::ParametersParser;
use crate::eval::ParametersSpec;
use crate::private::Private;
use crate::starlark_complex_value;
use crate::starlark_simple_value;
use crate::typing::Ty;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::typing::TypeCompiled;
use crate::values::AllocFrozenValue;
use crate::values::AllocValue;
use crate::values::Freeze;
use crate::values::FrozenHeap;
use crate::values::FrozenStringValue;
use crate::values::FrozenValue;
use crate::values::FrozenValueTyped;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::Trace;
use crate::values::Value;
use crate::values::ValueError;
use crate::values::ValueLike;

/// Return value of `type(any function)`.
pub const FUNCTION_TYPE: &str = "function";

/// Marker trait for function types.
pub(crate) enum StarlarkFunction {}

impl StarlarkTypeRepr for StarlarkFunction {
    fn starlark_type_repr() -> Ty {
        Ty::name(FUNCTION_TYPE)
    }
}

#[derive(Debug, Allocative, Clone, Copy, Dupe)]
#[doc(hidden)]
pub enum SpecialBuiltinFunction {
    List,
    Dict,
}

/// A native function that can be evaluated.
///
/// This trait is implemented by generated code and rarely needed to be implemented manually.
pub trait NativeFunc: Send + Sync + 'static {
    /// Invoke the function.
    fn invoke<'v>(
        &self,
        eval: &mut Evaluator<'v, '_>,
        args: &Arguments<'v, '_>,
    ) -> anyhow::Result<Value<'v>>;
}

impl<T> NativeFunc for T
where
    T: for<'v> Fn(&mut Evaluator<'v, '_>, &Arguments<'v, '_>) -> anyhow::Result<Value<'v>>
        + Send
        + Sync
        + 'static,
{
    fn invoke<'v>(
        &self,
        eval: &mut Evaluator<'v, '_>,
        args: &Arguments<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        (*self)(eval, args)
    }
}

/// Native method implementation.
///
/// This trait is implemented by generated code and rarely needed to be implemented manually.
pub trait NativeMeth: Send + Sync + 'static {
    /// Invoke the method.
    fn invoke<'v>(
        &self,
        eval: &mut Evaluator<'v, '_>,
        this: Value<'v>,
        args: &Arguments<'v, '_>,
    ) -> anyhow::Result<Value<'v>>;
}

impl<T> NativeMeth for T
where
    T: for<'v> Fn(
            &mut Evaluator<'v, '_>,
            Value<'v>,
            &Arguments<'v, '_>,
        ) -> anyhow::Result<Value<'v>>
        + Send
        + Sync
        + 'static,
{
    fn invoke<'v>(
        &self,
        eval: &mut Evaluator<'v, '_>,
        this: Value<'v>,
        args: &Arguments<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        (*self)(eval, this, args)
    }
}

/// A native function that can be evaluated.
pub trait NativeAttr:
    for<'v> Fn(Value<'v>, &'v Heap) -> anyhow::Result<Value<'v>> + Send + Sync + 'static
{
}

impl<T> NativeAttr for T where
    T: for<'v> Fn(Value<'v>, &'v Heap) -> anyhow::Result<Value<'v>> + Send + Sync + 'static
{
}

/// Enough details to get the documentation for a callable ([`NativeFunction`] or [`NativeMethod`])
#[doc(hidden)]
#[derive(Allocative)]
pub struct NativeCallableRawDocs {
    pub rust_docstring: Option<&'static str>,
    pub signature: ParametersSpec<FrozenValue>,
    pub parameter_types: HashMap<usize, DocType>,
    pub return_type: Option<DocType>,
    pub dot_type: Option<&'static str>,
}

#[doc(hidden)]
impl NativeCallableRawDocs {
    pub fn documentation(&self) -> DocFunction {
        DocFunction::from_docstring(
            DocStringKind::Rust,
            self.signature
                .documentation(self.parameter_types.clone(), HashMap::new()),
            self.return_type.clone(),
            self.rust_docstring,
            self.dot_type.map(ToOwned::to_owned),
        )
    }
}

/// Starlark representation of native (Rust) functions.
///
/// Almost always created with [`#[starlark_module]`](macro@crate::starlark_module).
#[derive(Derivative, ProvidesStaticType, Display, NoSerialize, Allocative)]
#[derivative(Debug)]
#[display(fmt = "{}", name)]
pub struct NativeFunction {
    #[derivative(Debug = "ignore")]
    #[allocative(skip)]
    pub(crate) function: Box<dyn NativeFunc>,
    pub(crate) name: String,
    /// `.type` attribute.
    pub(crate) typ: Option<FrozenStringValue>,
    pub(crate) ty: Option<Ty>,
    /// Safe to evaluate speculatively.
    pub(crate) speculative_exec_safe: bool,
    #[derivative(Debug = "ignore")]
    pub(crate) raw_docs: Option<NativeCallableRawDocs>,
    pub(crate) special_builtin_function: Option<SpecialBuiltinFunction>,
}

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

impl NativeFunction {
    /// Create a new [`NativeFunction`] from the Rust function which works directly on the parameters.
    /// The called function is responsible for validating the parameters are correct.
    pub fn new_direct<F>(function: F, name: String) -> Self
    where
        // If I switch this to the trait alias then it fails to resolve the usages
        F: for<'v> Fn(&mut Evaluator<'v, '_>, &Arguments<'v, '_>) -> anyhow::Result<Value<'v>>
            + Send
            + Sync
            + 'static,
    {
        NativeFunction {
            function: Box::new(function),
            name,
            typ: None,
            ty: None,
            speculative_exec_safe: false,
            raw_docs: None,
            special_builtin_function: None,
        }
    }

    /// Create a new [`NativeFunction`] from the Rust function, plus the parameter specification.
    pub fn new<F>(function: F, name: String, parameters: ParametersSpec<FrozenValue>) -> Self
    where
        F: for<'v> Fn(
                &mut Evaluator<'v, '_>,
                ParametersParser<'v, '_>,
            ) -> anyhow::Result<Value<'v>>
            + Send
            + Sync
            + 'static,
    {
        Self::new_direct(
            move |eval, params| {
                parameters.parser(params, eval, |parser, eval| function(eval, parser))
            },
            name,
        )
    }
}

impl<'v> AllocValue<'v> for NativeFunction {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_simple(self)
    }
}

/// Define the function type
#[starlark_value(type = FUNCTION_TYPE)]
impl<'v> StarlarkValue<'v> for NativeFunction {
    fn invoke(
        &self,
        _me: Value<'v>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        self.function.invoke(eval, args)
    }

    fn get_attr(&self, attribute: &str, _heap: &'v Heap) -> Option<Value<'v>> {
        if let Some(s) = &self.typ {
            if attribute == "type" {
                return Some(s.to_value());
            }
        }
        None
    }

    #[allow(clippy::manual_map)]
    fn eval_type(&self, _private: Private) -> Option<Ty> {
        if let Some(s) = &self.typ {
            Some(Ty::name(s.as_str()))
        } else {
            None
        }
    }

    fn has_attr(&self, _attribute: &str, _heap: &'v Heap) -> bool {
        // TODO(nga): implement properly.
        false
    }

    fn dir_attr(&self) -> Vec<String> {
        if self.typ.is_some() {
            vec!["type".to_owned()]
        } else {
            Vec::new()
        }
    }

    fn documentation(&self) -> Option<DocItem> {
        self.raw_docs
            .as_ref()
            .map(|raw_docs| DocItem::Function(raw_docs.documentation()))
    }

    fn typechecker_ty(&self, _private: Private) -> Option<Ty> {
        self.ty.clone()
    }

    fn at(&self, index: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match &self.special_builtin_function {
            Some(SpecialBuiltinFunction::List) => {
                let index = TypeCompiled::new(index, heap)?;
                Ok(TypeCompiled::type_list_of(index, heap).to_inner())
            }
            _ => ValueError::unsupported(self, "[]"),
        }
    }

    fn at2(
        &self,
        index0: Value<'v>,
        index1: Value<'v>,
        heap: &'v Heap,
        _private: Private,
    ) -> anyhow::Result<Value<'v>> {
        match &self.special_builtin_function {
            Some(SpecialBuiltinFunction::Dict) => {
                let index0 = TypeCompiled::new(index0, heap)?;
                let index1 = TypeCompiled::new(index1, heap)?;
                Ok(TypeCompiled::type_dict_of(index0, index1, heap).to_inner())
            }
            _ => ValueError::unsupported(self, "[,]"),
        }
    }
}

#[derive(Derivative, Display, NoSerialize, ProvidesStaticType, Allocative)]
#[derivative(Debug)]
#[display(fmt = "{}", name)]
pub(crate) struct NativeMethod {
    #[derivative(Debug = "ignore")]
    #[allocative(skip)]
    pub(crate) function: Box<dyn NativeMeth>,
    pub(crate) name: String,
    pub(crate) typ: Option<FrozenValue>,
    /// Safe to evaluate speculatively.
    pub(crate) speculative_exec_safe: bool,
    #[derivative(Debug = "ignore")]
    pub(crate) raw_docs: NativeCallableRawDocs,
}

starlark_simple_value!(NativeMethod);

#[starlark_value(type = "native_method")]
impl<'v> StarlarkValue<'v> for NativeMethod {
    fn invoke_method(
        &self,
        _me: Value<'v>,
        this: Value<'v>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
        _: Private,
    ) -> anyhow::Result<Value<'v>> {
        self.function.invoke(eval, this, args)
    }

    fn documentation(&self) -> Option<DocItem> {
        Some(DocItem::Function(self.raw_docs.documentation()))
    }
}

/// Used by the `#[starlark(attribute)]` tag of [`#[starlark_module]`](macro@starlark_module)
/// to define a function that pretends to be an attribute.
#[derive(Derivative, Display, NoSerialize, ProvidesStaticType, Allocative)]
#[display(fmt = "Attribute")]
#[derivative(Debug)]
pub(crate) struct NativeAttribute {
    #[derivative(Debug = "ignore")]
    #[allocative(skip)]
    pub(crate) function: Box<dyn NativeAttr>,
    /// Safe to evaluate speculatively.
    pub(crate) speculative_exec_safe: bool,
    pub(crate) docstring: Option<String>,
    pub(crate) typ: Ty,
}

starlark_simple_value!(NativeAttribute);

impl NativeAttribute {
    pub(crate) fn call<'v>(&self, value: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        (self.function)(value, heap)
    }
}

#[starlark_value(type = "attribute")]
impl<'v> StarlarkValue<'v> for NativeAttribute {
    fn invoke_method(
        &self,
        _me: Value<'v>,
        this: Value<'v>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
        _: Private,
    ) -> anyhow::Result<Value<'v>> {
        let method = self.call(this, eval.heap())?;
        method.invoke(args, eval)
    }

    fn documentation(&self) -> Option<DocItem> {
        let ds = self
            .docstring
            .as_ref()
            .and_then(|ds| DocString::from_docstring(DocStringKind::Rust, ds));
        let typ = Some(DocType {
            raw_type: self.typ.clone(),
        });
        Some(DocItem::Property(DocProperty { docs: ds, typ }))
    }
}

/// A wrapper for a method with a self object already bound.
#[derive(
    Clone,
    Debug,
    Trace,
    Coerce,
    Display,
    Freeze,
    NoSerialize,
    ProvidesStaticType,
    Allocative
)]
#[repr(C)]
#[display(fmt = "{}", method)]
pub(crate) struct BoundMethodGen<V> {
    pub(crate) method: FrozenValueTyped<'static, NativeMethod>,
    pub(crate) this: V,
}

starlark_complex_value!(pub(crate) BoundMethod);

impl<'v, V: ValueLike<'v>> BoundMethodGen<V> {
    /// Create a new [`BoundMethod`]. Given the expression `object.function`,
    /// the first argument would be `object`, and the second would be `getattr(object, "function")`.
    pub(crate) fn new(this: V, method: FrozenValueTyped<'static, NativeMethod>) -> Self {
        BoundMethodGen { method, this }
    }
}

#[starlark_value(type = FUNCTION_TYPE)]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for BoundMethodGen<V>
where
    Self: ProvidesStaticType<'v>,
{
    fn invoke(
        &self,
        _me: Value<'v>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        self.method.invoke_method(
            self.method.to_value(),
            self.this.to_value(),
            args,
            eval,
            Private,
        )
    }

    fn documentation(&self) -> Option<DocItem> {
        self.method.documentation()
    }
}


/*
 * 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::cmp::Ordering;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::ops::BitAnd;
use std::ops::BitOr;
use std::ops::BitXor;
use std::ops::Not;
use std::ops::Rem;

use anyhow::Context;
use dupe::Dupe;
use num_bigint::BigInt;
use serde::Serialize;

use crate::hint;
use crate::typing::Ty;
use crate::values::int::PointerI32;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::AllocFrozenValue;
use crate::values::AllocValue;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::UnpackValue;
use crate::values::Value;

/// Integer which is stored inline in `RawPointer`.
#[derive(
    Clone,
    Copy,
    Dupe,
    derive_more::Display,
    Eq,
    PartialEq,
    Hash,
    Ord,
    PartialOrd,
    Serialize
)]
#[serde(transparent)]
pub(crate) struct InlineInt(i32);

impl Debug for InlineInt {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(&self.0, f)
    }
}

impl InlineInt {
    const fn min_max_for_bits(bits: usize) -> (i32, i32) {
        let max = ((1u64 << (bits - 1)) - 1) as i32;
        let min = -max - 1;
        (min, max)
    }

    /// Number of bits in the integer.
    #[cfg(target_pointer_width = "64")]
    pub(crate) const BITS: usize = 32;
    #[cfg(target_pointer_width = "32")]
    pub(crate) const BITS: usize = 29;

    pub(crate) const ZERO: InlineInt = InlineInt(0);
    pub(crate) const MINUS_ONE: InlineInt = InlineInt(-1);
    pub(crate) const MIN: InlineInt = InlineInt(Self::min_max_for_bits(Self::BITS).0);
    pub(crate) const MAX: InlineInt = InlineInt(Self::min_max_for_bits(Self::BITS).1);

    /// This type does not contain full range of `i32`.
    pub(crate) fn smaller_than_i32() -> bool {
        Self::BITS < 32
    }

    #[inline]
    pub(crate) fn new_unchecked(i: i32) -> InlineInt {
        InlineInt(i)
    }

    #[cfg(test)]
    pub(crate) fn testing_new(i: i32) -> InlineInt {
        InlineInt::try_from(i).ok().unwrap()
    }

    #[inline]
    pub(crate) fn to_i32(self) -> i32 {
        self.0
    }

    #[inline]
    pub(crate) fn to_u64(self) -> Option<u64> {
        u64::try_from(self.0).ok()
    }

    #[inline]
    pub(crate) fn to_u32(self) -> Option<u32> {
        u32::try_from(self.0).ok()
    }

    #[inline]
    pub(crate) fn to_f64(self) -> f64 {
        self.0 as f64
    }

    #[inline]
    pub(crate) fn signum(self) -> i32 {
        self.0.signum()
    }

    #[inline]
    pub(crate) fn checked_add(self, rhs: InlineInt) -> Option<InlineInt> {
        self.0
            .checked_add(rhs.0)
            .and_then(|i| InlineInt::try_from(i).ok())
    }

    #[inline]
    pub(crate) fn checked_sub(self, rhs: InlineInt) -> Option<InlineInt> {
        self.checked_sub_i32(rhs.0)
    }

    #[inline]
    pub(crate) fn checked_sub_i32(self, rhs: i32) -> Option<InlineInt> {
        self.0
            .checked_sub(rhs)
            .and_then(|i| InlineInt::try_from(i).ok())
    }

    #[inline]
    pub(crate) fn checked_neg(self) -> Option<InlineInt> {
        self.0
            .checked_neg()
            .and_then(|i| InlineInt::try_from(i).ok())
    }

    #[inline]
    pub(crate) fn checked_div(self, rhs: InlineInt) -> Option<InlineInt> {
        self.0
            .checked_div(rhs.0)
            .and_then(|i| InlineInt::try_from(i).ok())
    }

    #[inline]
    pub(crate) fn checked_mul_i32(self, rhs: i32) -> Option<InlineInt> {
        self.0
            .checked_mul(rhs)
            .and_then(|i| InlineInt::try_from(i).ok())
    }

    #[inline]
    pub(crate) fn checked_shr(self, rhs: u32) -> Option<InlineInt> {
        self.0
            .checked_shr(rhs)
            .and_then(|i| InlineInt::try_from(i).ok())
    }

    #[inline]
    pub(crate) fn checked_shl(self, rhs: u32) -> Option<InlineInt> {
        self.0
            .checked_shl(rhs)
            .and_then(|i| InlineInt::try_from(i).ok())
    }

    pub(crate) fn to_bigint(self) -> BigInt {
        BigInt::from(self.0)
    }

    pub(crate) fn from_str_radix(s: &str, base: u32) -> anyhow::Result<InlineInt> {
        let i = i32::from_str_radix(s, base)?;
        InlineInt::try_from(i).ok().context("parse int overflow")
    }

    #[inline]
    fn try_from_impl<I>(i: I) -> Result<InlineInt, InlineIntOverflow>
    where
        i32: TryFrom<I>,
    {
        let i = i32::try_from(i).ok().ok_or(InlineIntOverflow)?;
        if hint::likely(i >= Self::MIN.0 && i <= Self::MAX.0) {
            Ok(InlineInt(i))
        } else {
            Err(InlineIntOverflow)
        }
    }
}

pub(crate) struct InlineIntOverflow;

impl TryFrom<u32> for InlineInt {
    type Error = InlineIntOverflow;

    #[inline]
    fn try_from(value: u32) -> Result<Self, Self::Error> {
        i32::try_from(value)
            .ok()
            .and_then(|i| InlineInt::try_from(i).ok())
            .ok_or(InlineIntOverflow)
    }
}

impl TryFrom<i32> for InlineInt {
    type Error = InlineIntOverflow;

    #[inline]
    fn try_from(value: i32) -> Result<Self, Self::Error> {
        InlineInt::try_from_impl(value)
    }
}

impl TryFrom<u64> for InlineInt {
    type Error = InlineIntOverflow;

    #[inline]
    fn try_from(value: u64) -> Result<Self, Self::Error> {
        InlineInt::try_from_impl(value)
    }
}

impl TryFrom<i64> for InlineInt {
    type Error = InlineIntOverflow;

    #[inline]
    fn try_from(value: i64) -> Result<Self, Self::Error> {
        InlineInt::try_from_impl(value)
    }
}

impl TryFrom<usize> for InlineInt {
    type Error = InlineIntOverflow;

    #[inline]
    fn try_from(value: usize) -> Result<Self, Self::Error> {
        InlineInt::try_from_impl(value)
    }
}

impl TryFrom<isize> for InlineInt {
    type Error = InlineIntOverflow;

    fn try_from(value: isize) -> Result<Self, Self::Error> {
        InlineInt::try_from_impl(value)
    }
}

impl<'v> TryFrom<&'v BigInt> for InlineInt {
    type Error = InlineIntOverflow;

    fn try_from(value: &'v BigInt) -> Result<Self, Self::Error> {
        InlineInt::try_from_impl(value)
    }
}

impl BitAnd for InlineInt {
    type Output = InlineInt;

    fn bitand(self, rhs: Self) -> InlineInt {
        InlineInt(self.0 & rhs.0)
    }
}

impl BitOr for InlineInt {
    type Output = InlineInt;

    fn bitor(self, rhs: Self) -> InlineInt {
        InlineInt(self.0 | rhs.0)
    }
}

impl BitXor for InlineInt {
    type Output = InlineInt;

    fn bitxor(self, rhs: Self) -> InlineInt {
        InlineInt(self.0 ^ rhs.0)
    }
}

impl Not for InlineInt {
    type Output = InlineInt;

    fn not(self) -> InlineInt {
        InlineInt(!self.0)
    }
}

impl PartialEq<i32> for InlineInt {
    #[inline]
    fn eq(&self, other: &i32) -> bool {
        self.0 == *other
    }
}

impl PartialOrd<i32> for InlineInt {
    #[inline]
    fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
        self.0.partial_cmp(other)
    }
}

impl PartialEq<InlineInt> for i32 {
    #[inline]
    fn eq(&self, other: &InlineInt) -> bool {
        *self == other.0
    }
}

impl PartialOrd<InlineInt> for i32 {
    #[inline]
    fn partial_cmp(&self, other: &InlineInt) -> Option<Ordering> {
        self.partial_cmp(&other.0)
    }
}

impl Rem for InlineInt {
    type Output = InlineInt;

    fn rem(self, rhs: Self) -> InlineInt {
        InlineInt(self.0 % rhs.0)
    }
}

impl StarlarkTypeRepr for InlineInt {
    fn starlark_type_repr() -> Ty {
        PointerI32::starlark_type_repr()
    }
}

impl<'v> UnpackValue<'v> for InlineInt {
    fn unpack_value(value: Value<'v>) -> Option<Self> {
        value.0.unpack_int()
    }
}

impl<'v> AllocValue<'v> for InlineInt {
    fn alloc_value(self, _heap: &'v Heap) -> Value<'v> {
        Value::new_int(self)
    }
}

impl AllocFrozenValue for InlineInt {
    fn alloc_frozen_value(self, _heap: &FrozenHeap) -> FrozenValue {
        FrozenValue::new_int(self)
    }
}

#[cfg(test)]
mod tests {
    use crate::values::types::inline_int::InlineInt;

    #[test]
    fn test_min_max_for_bits() {
        assert_eq!((-1, 0), InlineInt::min_max_for_bits(1));
        assert_eq!((-2, 1), InlineInt::min_max_for_bits(2));
        assert_eq!((-4, 3), InlineInt::min_max_for_bits(3));
        assert_eq!((i32::MIN, i32::MAX), InlineInt::min_max_for_bits(32));
    }
}


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

//! The integer type. Currently limited to 32 bit.
//!
//! Unlike most Starlark values, these aren't actually represented on the [`Heap`], but as special values.
//! At some point in the future we plan to support arbitrary sized integers (as required by the
//! [Starlark spec](https://github.com/bazelbuild/starlark/blob/master/spec.md#integers)), and those larger
//! integer values will be stored on the heap.

use std::cmp::Ordering;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::hash::Hasher;
use std::mem;
use std::ptr;

use allocative::Allocative;
use num_bigint::BigInt;
use serde::Serialize;
use serde::Serializer;
use starlark_derive::starlark_value;
use starlark_derive::StarlarkDocs;

use crate as starlark;
use crate::any::AnyLifetime;
use crate::any::ProvidesStaticType;
use crate::cast;
use crate::collections::StarlarkHashValue;
use crate::collections::StarlarkHasher;
use crate::private::Private;
use crate::typing::Ty;
use crate::values::error::ValueError;
use crate::values::layout::avalue::AValueImpl;
use crate::values::layout::avalue::Basic;
use crate::values::layout::pointer::RawPointer;
use crate::values::layout::vtable::AValueDyn;
use crate::values::layout::vtable::AValueVTable;
use crate::values::layout::vtable::StarlarkValueRawPtr;
use crate::values::num::NumRef;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::types::inline_int::InlineInt;
use crate::values::types::int_or_big::StarlarkInt;
use crate::values::types::int_or_big::StarlarkIntRef;
use crate::values::AllocFrozenValue;
use crate::values::AllocValue;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::UnpackValue;
use crate::values::Value;

/// The result of calling `type()` on integers.
pub const INT_TYPE: &str = "int";

impl<'v> AllocValue<'v> for i32 {
    #[inline]
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc(StarlarkInt::from(self))
    }
}
impl AllocFrozenValue for i32 {
    #[inline]
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl StarlarkTypeRepr for i32 {
    fn starlark_type_repr() -> Ty {
        PointerI32::starlark_type_repr()
    }
}

impl UnpackValue<'_> for i32 {
    fn unpack_value(value: Value) -> Option<Self> {
        if InlineInt::smaller_than_i32() {
            StarlarkIntRef::unpack_value(value)?.to_i32()
        } else {
            Some(InlineInt::unpack_value(value)?.to_i32())
        }
    }
}

// WARNING: This type isn't a real type, a pointer to this is secretly an i32.
// Therefore, don't derive stuff on it, since it will be wrong.
// However, `ProvidesStaticType` promises not to peek at its value, so that's fine.
#[derive(ProvidesStaticType, StarlarkDocs, Allocative)]
#[starlark_docs(builtin = "standard")]
#[repr(C)]
pub(crate) struct PointerI32 {
    _private: (),
}

impl PartialEq for PointerI32 {
    fn eq(&self, other: &Self) -> bool {
        ptr::eq(self, other)
    }
}

impl Eq for PointerI32 {}

impl Debug for PointerI32 {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Debug::fmt(&self.get(), f)
    }
}

impl PointerI32 {
    const _ASSERTIONS: () = {
        assert!(mem::align_of::<Self>() == 1);
    };

    #[inline]
    pub(crate) fn new(x: InlineInt) -> &'static Self {
        // UB if the pointer isn't aligned, or it is zero
        // Alignment is 1, so that's not an issue.
        // And the pointer is not zero because it has `TAG_INT` bit set.
        unsafe { Self::from_raw_pointer_unchecked(FrozenValue::new_int(x).ptr_value()) }
    }

    #[inline]
    pub(crate) unsafe fn from_raw_pointer_unchecked(
        raw_pointer: RawPointer,
    ) -> &'static PointerI32 {
        debug_assert!(raw_pointer.is_int());
        cast::usize_to_ptr(raw_pointer.ptr_value())
    }

    #[inline]
    pub(crate) fn get(&self) -> InlineInt {
        unsafe { RawPointer::new_unchecked(self as *const Self as usize).unpack_int_unchecked() }
    }

    #[inline]
    pub(crate) fn as_avalue_dyn(&'static self) -> AValueDyn<'static> {
        unsafe {
            AValueDyn::new(
                StarlarkValueRawPtr::new_pointer_i32(self),
                AValueVTable::new::<AValueImpl<Basic, PointerI32>>(),
            )
        }
    }

    /// This operation is expensive, use only if you have to.
    fn to_bigint(&self) -> BigInt {
        self.get().to_bigint()
    }

    pub(crate) fn type_is_pointer_i32<'v, T: StarlarkValue<'v>>() -> bool {
        T::static_type_id() == PointerI32::static_type_id()
    }
}

impl Display for PointerI32 {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.get())
    }
}

/// Define the int type
#[starlark_value(type = INT_TYPE)]
impl<'v> StarlarkValue<'v> for PointerI32 {
    fn is_special(_: Private) -> bool
    where
        Self: Sized,
    {
        true
    }

    fn equals(&self, other: Value) -> anyhow::Result<bool> {
        Ok(Some(NumRef::Int(StarlarkIntRef::Small(self.get()))) == other.unpack_num())
    }

    fn to_bool(&self) -> bool {
        self.get().to_i32() != 0
    }
    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        hasher.write_u64(NumRef::Int(StarlarkIntRef::Small(self.get())).get_hash_64());
        Ok(())
    }

    fn get_hash(&self, _private: Private) -> anyhow::Result<StarlarkHashValue> {
        Ok(NumRef::Int(StarlarkIntRef::Small(self.get())).get_hash())
    }

    fn plus(&self, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(Value::new_int(self.get()))
    }
    fn minus(&self, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(heap.alloc(-StarlarkIntRef::Small(self.get())))
    }
    fn add(&self, other: Value<'v>, heap: &'v Heap) -> Option<anyhow::Result<Value<'v>>> {
        Some(Ok(heap.alloc(
            NumRef::Int(StarlarkIntRef::Small(self.get())) + other.unpack_num()?,
        )))
    }
    fn sub(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            Some(other) => Ok(heap.alloc(NumRef::Int(StarlarkIntRef::Small(self.get())) - other)),
            None => ValueError::unsupported_with(self, "-", other),
        }
    }
    fn mul(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            Some(other) => Ok(heap.alloc(NumRef::Int(StarlarkIntRef::Small(self.get())) * other)),
            None => other.mul(Value::new_int(self.get()), heap),
        }
    }
    fn div(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            Some(other) => {
                Ok(heap.alloc(NumRef::Int(StarlarkIntRef::Small(self.get())).div(other)?))
            }
            None => ValueError::unsupported_with(self, "/", other),
        }
    }
    fn percent(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            Some(other) => {
                Ok(heap.alloc(NumRef::Int(StarlarkIntRef::Small(self.get())).percent(other)?))
            }
            None => ValueError::unsupported_with(self, "%", other),
        }
    }
    fn floor_div(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            Some(other) => {
                Ok(heap.alloc(NumRef::Int(StarlarkIntRef::Small(self.get())).floor_div(other)?))
            }
            None => ValueError::unsupported_with(self, "//", other),
        }
    }

    fn compare(&self, other: Value) -> anyhow::Result<Ordering> {
        match other.unpack_num() {
            None => ValueError::unsupported_with(self, "compare", other),
            Some(other) => Ok(NumRef::Int(StarlarkIntRef::Small(self.get())).cmp(&other)),
        }
    }

    fn bit_and(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match StarlarkIntRef::unpack_value(other) {
            None => ValueError::unsupported_with(self, "&", other),
            Some(StarlarkIntRef::Small(i)) => Ok(Value::new_int(self.get() & i)),
            Some(StarlarkIntRef::Big(b)) => {
                Ok(heap.alloc(StarlarkInt::from(&self.to_bigint() & b.get())))
            }
        }
    }

    fn bit_or(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match StarlarkIntRef::unpack_value(other) {
            None => ValueError::unsupported_with(self, "|", other),
            Some(StarlarkIntRef::Small(i)) => Ok(Value::new_int(self.get() | i)),
            Some(StarlarkIntRef::Big(b)) => {
                Ok(heap.alloc(StarlarkInt::from(&self.to_bigint() | b.get())))
            }
        }
    }

    fn bit_xor(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match StarlarkIntRef::unpack_value(other) {
            None => ValueError::unsupported_with(self, "^", other),
            Some(StarlarkIntRef::Small(i)) => Ok(Value::new_int(self.get() ^ i)),
            Some(StarlarkIntRef::Big(b)) => {
                Ok(heap.alloc(StarlarkInt::from(&self.to_bigint() ^ b.get())))
            }
        }
    }

    fn bit_not(&self, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(Value::new_int(!self.get()))
    }

    fn left_shift(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match StarlarkIntRef::unpack_value(other) {
            None => ValueError::unsupported_with(self, "<<", other),
            Some(other) => Ok(heap.alloc(StarlarkIntRef::Small(self.get()).left_shift(other)?)),
        }
    }

    fn right_shift(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match StarlarkIntRef::unpack_value(other) {
            None => ValueError::unsupported_with(self, ">>", other),
            Some(other) => Ok(heap.alloc(StarlarkIntRef::Small(self.get()).right_shift(other)?)),
        }
    }
}

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

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

    #[test]
    fn test_arithmetic_operators() {
        assert::all_true(
            r#"
+1 == 1
-1 == 0 - 1
1 + 2 == 3
1 + 2.0 == 3.0
1 - 2 == -1
1 - 2.0 == -1.0
2 * 3 == 6
2 * 3.0 == 6.0
4 / 2 == 2.0
5 % 3 == 2
4 // 2 == 2
"#,
        );
    }

    #[test]
    fn test_minus() {
        // `-i32::MIN` should overflow to `StarlarkBigInt`.
        assert::eq("2147483648", "-(-2147483647 - 1)")
    }

    #[test]
    fn test_int_tag() {
        fn check(x: InlineInt) {
            assert_eq!(x, PointerI32::new(x).get())
        }

        for x in -10..10 {
            check(InlineInt::try_from(x).ok().unwrap())
        }
        check(InlineInt::MAX);
        check(InlineInt::MIN);
    }

    #[test]
    fn test_alignment_int_pointer() {
        assert_eq!(1, std::mem::align_of::<PointerI32>());
    }

    #[test]
    fn test_as_avalue_dyn() {
        // `get_type` calls `as_avalue_dyn` internally.
        assert_eq!("int", Value::new_int(InlineInt::MINUS_ONE).get_type());
    }
}


/*
 * 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::cmp::Ordering;
use std::ops::Add;
use std::ops::BitAnd;
use std::ops::BitOr;
use std::ops::BitXor;
use std::ops::Mul;
use std::ops::Neg;
use std::ops::Not;
use std::ops::Sub;
use std::str::FromStr;

use anyhow::Context;
use dupe::Dupe;
use num_bigint::BigInt;
use num_bigint::Sign;
use num_traits::FromPrimitive;
use num_traits::Num;
use num_traits::Signed;
use num_traits::ToPrimitive;
use num_traits::Zero;

use crate::typing::Ty;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::types::bigint::StarlarkBigInt;
use crate::values::types::inline_int::InlineInt;
use crate::values::AllocFrozenValue;
use crate::values::AllocValue;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueLike;

#[derive(Debug, thiserror::Error)]
enum StarlarkIntError {
    #[error("Cannot parse `{0}` as an integer in base {1}")]
    CannotParse(String, u32),
    #[error("Float `{0}` cannot be represented as exact integer")]
    CannotRepresentAsExact(f64),
    #[error("Floor division by zero: {0} // {1}")]
    FloorDivisionByZero(StarlarkInt, StarlarkInt),
    #[error("Modulo by zero: {0} % {1}")]
    ModuloByZero(StarlarkInt, StarlarkInt),
    #[error("Integer overflow computing left shift")]
    LeftShiftOverflow,
    #[error("Negative left shift")]
    LeftShiftNegative,
    #[error("Negative right shift")]
    RightShiftNegative,
}

#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)]
pub(crate) enum StarlarkInt {
    Small(InlineInt),
    Big(StarlarkBigInt),
}

#[derive(Eq, PartialEq, Copy, Clone, Dupe, Debug)]
pub(crate) enum StarlarkIntRef<'v> {
    Small(InlineInt),
    Big(&'v StarlarkBigInt),
}

impl FromStr for StarlarkInt {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Not very efficient, but only used in tests.
        Ok(StarlarkInt::from(BigInt::from_str(s)?))
    }
}

impl StarlarkInt {
    pub(crate) fn from_str_radix(s: &str, base: u32) -> anyhow::Result<StarlarkInt> {
        if let Ok(i) = InlineInt::from_str_radix(s, base) {
            Ok(StarlarkInt::Small(i))
        } else {
            match BigInt::from_str_radix(s, base) {
                Ok(i) => Ok(StarlarkInt::from(i)),
                Err(_) => Err(StarlarkIntError::CannotParse(s.to_owned(), base).into()),
            }
        }
    }

    pub(crate) fn from_f64_exact(f: f64) -> anyhow::Result<StarlarkInt> {
        let i = InlineInt::try_from(f as i32).unwrap_or(InlineInt::ZERO);
        if i.to_f64() == f {
            Ok(StarlarkInt::Small(i))
        } else {
            if let Some(i) = BigInt::from_f64(f) {
                if i.to_f64() == Some(f) {
                    return Ok(StarlarkInt::from(i));
                }
            }
            Err(StarlarkIntError::CannotRepresentAsExact(f).into())
        }
    }

    pub(crate) fn as_ref(&self) -> StarlarkIntRef {
        match self {
            StarlarkInt::Small(i) => StarlarkIntRef::Small(*i),
            StarlarkInt::Big(i) => StarlarkIntRef::Big(i),
        }
    }

    #[inline]
    fn from_impl<I>(value: I) -> StarlarkInt
    where
        I: Copy,
        InlineInt: TryFrom<I>,
        BigInt: From<I>,
    {
        match InlineInt::try_from(value) {
            Ok(i) => StarlarkInt::Small(i),
            Err(_) => StarlarkInt::Big(StarlarkBigInt::unchecked_new(BigInt::from(value))),
        }
    }
}

impl<'v> StarlarkIntRef<'v> {
    pub(crate) fn to_owned(self) -> StarlarkInt {
        match self {
            StarlarkIntRef::Small(i) => StarlarkInt::Small(i),
            StarlarkIntRef::Big(i) => StarlarkInt::Big(i.clone()),
        }
    }

    fn to_big(self) -> BigInt {
        match self {
            StarlarkIntRef::Small(i) => i.to_bigint(),
            StarlarkIntRef::Big(i) => i.get().clone(),
        }
    }

    pub(crate) fn to_f64(self) -> f64 {
        match self {
            StarlarkIntRef::Small(i) => i.to_f64(),
            StarlarkIntRef::Big(i) => i.to_f64(),
        }
    }

    pub(crate) fn to_i32(self) -> Option<i32> {
        match self {
            StarlarkIntRef::Small(i) => Some(i.to_i32()),
            StarlarkIntRef::Big(i) => i.to_i32(),
        }
    }

    pub(crate) fn to_u64(self) -> Option<u64> {
        match self {
            StarlarkIntRef::Small(i) => i.to_u64(),
            StarlarkIntRef::Big(i) => i.get().to_u64(),
        }
    }

    fn floor_div_small_small(a: InlineInt, b: InlineInt) -> anyhow::Result<StarlarkInt> {
        if b == 0 {
            return Err(StarlarkIntError::FloorDivisionByZero(
                StarlarkInt::Small(a),
                StarlarkInt::Small(b),
            )
            .into());
        }
        let sig = b.signum() * a.signum();
        let offset = if sig < 0 && a % b != 0 { 1 } else { 0 };
        match a.checked_div(b) {
            Some(div) => Ok(StarlarkInt::Small(
                div.checked_sub_i32(offset).context("unreachable")?,
            )),
            None => Self::floor_div_big_big(&a.to_bigint(), &b.to_bigint()),
        }
    }

    fn is_negative(self) -> bool {
        match self {
            StarlarkIntRef::Small(i) => i < 0,
            StarlarkIntRef::Big(i) => i.get().is_negative(),
        }
    }

    fn is_zero(self) -> bool {
        match self {
            StarlarkIntRef::Small(i) => i == 0,
            StarlarkIntRef::Big(_) => false,
        }
    }

    fn signum_big(b: &BigInt) -> i32 {
        match b.sign() {
            Sign::Plus => 1,
            Sign::Minus => -1,
            Sign::NoSign => 0,
        }
    }

    fn floor_div_big_big(a: &BigInt, b: &BigInt) -> anyhow::Result<StarlarkInt> {
        if b.is_zero() {
            return Err(StarlarkIntError::FloorDivisionByZero(
                StarlarkInt::from(a.clone()),
                StarlarkInt::from(b.clone()),
            )
            .into());
        }
        let sig = Self::signum_big(b) * Self::signum_big(a);
        // TODO(nga): optimize.
        let offset = if sig < 0 && (a % b).is_zero().not() {
            1
        } else {
            0
        };
        Ok(StarlarkInt::from((a / b) - offset))
    }

    /// `//`.
    pub(crate) fn floor_div(self, other: StarlarkIntRef) -> anyhow::Result<StarlarkInt> {
        match (self, other) {
            (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) => {
                Self::floor_div_small_small(a, b)
            }
            (StarlarkIntRef::Small(a), StarlarkIntRef::Big(b)) => {
                Self::floor_div_big_big(&a.to_bigint(), b.get())
            }
            (StarlarkIntRef::Big(a), StarlarkIntRef::Small(b)) => {
                Self::floor_div_big_big(a.get(), &b.to_bigint())
            }
            (StarlarkIntRef::Big(a), StarlarkIntRef::Big(b)) => {
                Self::floor_div_big_big(a.get(), b.get())
            }
        }
    }

    fn percent_small(a: InlineInt, b: InlineInt) -> anyhow::Result<InlineInt> {
        if b == 0 {
            return Err(StarlarkIntError::ModuloByZero(
                StarlarkInt::Small(a),
                StarlarkInt::Small(b),
            )
            .into());
        }
        // In Rust `i32::min_value() % -1` is overflow, but we should eval it to zero.
        if a == i32::min_value() && b == -1 {
            return Ok(InlineInt::ZERO);
        }
        let r = a % b;
        if r == 0 {
            Ok(InlineInt::ZERO)
        } else {
            Ok(if b.signum() != r.signum() {
                r.checked_add(b).context("unreachable")?
            } else {
                r
            })
        }
    }

    fn percent_big(a: &BigInt, b: &BigInt) -> anyhow::Result<StarlarkInt> {
        if b.is_zero() {
            return Err(StarlarkIntError::ModuloByZero(
                StarlarkInt::from(a.clone()),
                StarlarkInt::from(b.clone()),
            )
            .into());
        }
        let r = a % b;
        if r.is_zero() {
            Ok(StarlarkInt::Small(InlineInt::ZERO))
        } else {
            Ok(StarlarkInt::from(if b.sign() != r.sign() {
                r + b
            } else {
                r
            }))
        }
    }

    /// `%`.
    pub(crate) fn percent(self, other: StarlarkIntRef) -> anyhow::Result<StarlarkInt> {
        match (self, other) {
            (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) => {
                Ok(StarlarkInt::Small(Self::percent_small(a, b)?))
            }
            (StarlarkIntRef::Small(a), StarlarkIntRef::Big(b)) => {
                Self::percent_big(&a.to_bigint(), b.get())
            }
            (StarlarkIntRef::Big(a), StarlarkIntRef::Small(b)) => {
                Self::percent_big(a.get(), &b.to_bigint())
            }
            (StarlarkIntRef::Big(a), StarlarkIntRef::Big(b)) => Self::percent_big(a.get(), b.get()),
        }
    }

    /// `<<`.
    pub(crate) fn left_shift(self, other: StarlarkIntRef) -> anyhow::Result<StarlarkInt> {
        // Handle the most common case first.
        if let (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) = (self, other) {
            if let Some(b) = b.to_u32() {
                if let Some(r) = a.checked_shl(b) {
                    return Ok(StarlarkInt::Small(r));
                }
            }
        }

        if other.is_negative() {
            return Err(StarlarkIntError::LeftShiftNegative.into());
        }
        if self.is_zero() || other.is_zero() {
            return Ok(self.to_owned());
        }
        if other > 100_000 {
            // Limit the size of the BigInt to avoid accidentally consuming
            // too much memory. 100_000 is practically enough for most use cases.
            return Err(StarlarkIntError::LeftShiftOverflow.into());
        }

        match other {
            StarlarkIntRef::Big(_) => Err(StarlarkIntError::LeftShiftOverflow.into()),
            StarlarkIntRef::Small(b) => {
                // No overflow, checked above.
                let b = b.to_u64().unwrap();
                Ok(StarlarkInt::from(self.to_big() << b))
            }
        }
    }

    /// `>>`.
    pub(crate) fn right_shift(self, other: StarlarkIntRef) -> anyhow::Result<StarlarkInt> {
        // Handle the most common case first.
        if let (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) = (self, other) {
            if let Some(b) = b.to_u32() {
                if let Some(r) = a.checked_shr(b) {
                    return Ok(StarlarkInt::Small(r));
                }
            }
        }

        if other.is_negative() {
            return Err(StarlarkIntError::RightShiftNegative.into());
        }
        if self.is_zero() || other.is_zero() {
            return Ok(self.to_owned());
        }
        let Some(other) = other.to_u64() else {
            return if self.is_negative() {
                Ok(StarlarkInt::Small(InlineInt::MINUS_ONE))
            } else {
                Ok(StarlarkInt::Small(InlineInt::ZERO))
            }
        };

        match self {
            StarlarkIntRef::Small(a) => {
                if a < 0 {
                    Ok(StarlarkInt::Small(InlineInt::MINUS_ONE))
                } else {
                    Ok(StarlarkInt::Small(InlineInt::ZERO))
                }
            }
            StarlarkIntRef::Big(a) => Ok(StarlarkInt::from(a.get() >> other)),
        }
    }
}

impl StarlarkTypeRepr for StarlarkInt {
    fn starlark_type_repr() -> Ty {
        StarlarkBigInt::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for StarlarkInt {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        match self {
            StarlarkInt::Small(i) => heap.alloc(i),
            StarlarkInt::Big(i) => heap.alloc(i),
        }
    }
}

impl AllocFrozenValue for StarlarkInt {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        match self {
            StarlarkInt::Small(i) => heap.alloc(i),
            StarlarkInt::Big(i) => heap.alloc(i),
        }
    }
}

impl<'v> StarlarkTypeRepr for StarlarkIntRef<'v> {
    fn starlark_type_repr() -> Ty {
        StarlarkInt::starlark_type_repr()
    }
}

impl<'v> UnpackValue<'v> for StarlarkIntRef<'v> {
    fn unpack_value(value: Value<'v>) -> Option<Self> {
        if let Some(i) = InlineInt::unpack_value(value) {
            Some(StarlarkIntRef::Small(i))
        } else {
            value.downcast_ref().map(StarlarkIntRef::Big)
        }
    }
}

impl From<i32> for StarlarkInt {
    #[inline]
    fn from(value: i32) -> Self {
        StarlarkInt::from_impl(value)
    }
}

impl From<BigInt> for StarlarkInt {
    fn from(value: BigInt) -> Self {
        match InlineInt::try_from(&value) {
            Ok(i) => StarlarkInt::Small(i),
            Err(_) => StarlarkInt::Big(StarlarkBigInt::unchecked_new(value)),
        }
    }
}

impl From<u32> for StarlarkInt {
    #[inline]
    fn from(value: u32) -> Self {
        StarlarkInt::from_impl(value)
    }
}

impl From<i64> for StarlarkInt {
    #[inline]
    fn from(value: i64) -> Self {
        StarlarkInt::from_impl(value)
    }
}

impl From<u64> for StarlarkInt {
    #[inline]
    fn from(value: u64) -> Self {
        StarlarkInt::from_impl(value)
    }
}

impl From<isize> for StarlarkInt {
    #[inline]
    fn from(value: isize) -> Self {
        StarlarkInt::from_impl(value)
    }
}

impl From<usize> for StarlarkInt {
    #[inline]
    fn from(value: usize) -> Self {
        StarlarkInt::from_impl(value)
    }
}

impl<'v> BitAnd for StarlarkIntRef<'v> {
    type Output = StarlarkInt;

    fn bitand(self, other: Self) -> StarlarkInt {
        match (self, other) {
            (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) => StarlarkInt::Small(a & b),
            (a, b) => StarlarkInt::from(a.to_big() & b.to_big()),
        }
    }
}

impl<'v> BitOr for StarlarkIntRef<'v> {
    type Output = StarlarkInt;

    fn bitor(self, other: Self) -> StarlarkInt {
        match (self, other) {
            (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) => StarlarkInt::Small(a | b),
            (a, b) => StarlarkInt::from(a.to_big() | b.to_big()),
        }
    }
}

impl<'v> BitXor for StarlarkIntRef<'v> {
    type Output = StarlarkInt;

    fn bitxor(self, other: Self) -> StarlarkInt {
        match (self, other) {
            (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) => StarlarkInt::Small(a ^ b),
            (a, b) => StarlarkInt::from(a.to_big() ^ b.to_big()),
        }
    }
}

impl<'v> Not for StarlarkIntRef<'v> {
    type Output = StarlarkInt;

    fn not(self) -> StarlarkInt {
        match self {
            StarlarkIntRef::Small(a) => StarlarkInt::Small(!a),
            a => StarlarkInt::from(!a.to_big()),
        }
    }
}

impl<'v> Neg for StarlarkIntRef<'v> {
    type Output = StarlarkInt;

    fn neg(self) -> StarlarkInt {
        if let StarlarkIntRef::Small(i) = self {
            if let Some(n) = i.checked_neg() {
                return StarlarkInt::Small(n);
            }
        }
        StarlarkInt::from(-self.to_big())
    }
}

impl Neg for StarlarkInt {
    type Output = StarlarkInt;

    fn neg(self) -> StarlarkInt {
        // TODO(nga): can negate without allocating in most cases.
        -self.as_ref()
    }
}

impl<'v> Add for StarlarkIntRef<'v> {
    type Output = StarlarkInt;

    fn add(self, other: Self) -> StarlarkInt {
        if let (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) = (self, other) {
            if let Some(c) = a.checked_add(b) {
                return StarlarkInt::Small(c);
            }
        }
        StarlarkInt::from(self.to_big() + other.to_big())
    }
}

impl<'v> Sub for StarlarkIntRef<'v> {
    type Output = StarlarkInt;

    fn sub(self, other: Self) -> StarlarkInt {
        if let (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) = (self, other) {
            if let Some(c) = a.checked_sub(b) {
                return StarlarkInt::Small(c);
            }
        }
        StarlarkInt::from(self.to_big() - other.to_big())
    }
}

impl<'v> Mul<i32> for StarlarkIntRef<'v> {
    type Output = StarlarkInt;

    fn mul(self, rhs: i32) -> Self::Output {
        match self {
            StarlarkIntRef::Small(a) => {
                if let Some(c) = a.checked_mul_i32(rhs) {
                    return StarlarkInt::Small(c);
                }
                StarlarkInt::from(a.to_bigint() * rhs)
            }
            StarlarkIntRef::Big(b) => StarlarkInt::from(b.get() * rhs),
        }
    }
}

impl<'v> Mul<StarlarkIntRef<'v>> for i32 {
    type Output = StarlarkInt;

    fn mul(self, rhs: StarlarkIntRef<'v>) -> Self::Output {
        rhs * self
    }
}

impl<'v> Mul for StarlarkIntRef<'v> {
    type Output = StarlarkInt;

    fn mul(self, other: Self) -> StarlarkInt {
        match (self, other) {
            (StarlarkIntRef::Small(a), b) => a.to_i32() * b,
            (a, StarlarkIntRef::Small(b)) => a * b.to_i32(),
            (StarlarkIntRef::Big(a), StarlarkIntRef::Big(b)) => {
                StarlarkInt::from(a.get() * b.get())
            }
        }
    }
}

impl<'v> PartialOrd for StarlarkIntRef<'v> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<'v> Ord for StarlarkIntRef<'v> {
    fn cmp(&self, other: &Self) -> Ordering {
        match (self, other) {
            (StarlarkIntRef::Small(a), StarlarkIntRef::Small(b)) => a.cmp(b),
            (StarlarkIntRef::Big(a), StarlarkIntRef::Big(b)) => a.cmp(b),
            (StarlarkIntRef::Small(a), StarlarkIntRef::Big(b)) => {
                StarlarkBigInt::cmp_small_big(*a, b)
            }
            (StarlarkIntRef::Big(a), StarlarkIntRef::Small(b)) => {
                StarlarkBigInt::cmp_big_small(a, *b)
            }
        }
    }
}

impl<'v> PartialEq<i32> for StarlarkIntRef<'v> {
    fn eq(&self, other: &i32) -> bool {
        *self == StarlarkInt::from(*other).as_ref()
    }
}

impl<'v> PartialOrd<i32> for StarlarkIntRef<'v> {
    fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
        // TODO(nga): this is inefficient if `i32` cannot fit in `InlineInt`.
        self.partial_cmp(&StarlarkInt::from(*other).as_ref())
    }
}

impl<'v> PartialEq<StarlarkIntRef<'v>> for i32 {
    fn eq(&self, other: &StarlarkIntRef<'v>) -> bool {
        StarlarkInt::from(*self).as_ref() == *other
    }
}

impl<'v> PartialOrd<StarlarkIntRef<'v>> for i32 {
    // TODO(nga): this is inefficient if `i32` cannot fit in `InlineInt`.
    fn partial_cmp(&self, other: &StarlarkIntRef<'v>) -> Option<Ordering> {
        StarlarkInt::from(*self).as_ref().partial_cmp(other)
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use crate::values::types::int_or_big::StarlarkInt;

    fn int(s: &str) -> StarlarkInt {
        StarlarkInt::from_str(s).unwrap()
    }

    fn floor_div(a: &str, b: &str) -> String {
        int(a)
            .as_ref()
            .floor_div(int(b).as_ref())
            .unwrap()
            .to_string()
    }

    fn percent(a: &str, b: &str) -> String {
        int(a)
            .as_ref()
            .percent(int(b).as_ref())
            .unwrap()
            .to_string()
    }

    #[test]
    fn test_floor_div_big() {
        assert_eq!(
            "2",
            floor_div("600000000000000000005", "300000000000000000000")
        );
        assert_eq!(
            "-3",
            floor_div("600000000000000000005", "-300000000000000000000")
        );
        assert_eq!(
            "-3",
            floor_div("-600000000000000000005", "300000000000000000000")
        );
        assert_eq!(
            "2",
            floor_div("-600000000000000000005", "-300000000000000000000")
        );
    }

    #[test]
    fn test_floor_div_big_small() {
        assert_eq!(
            "200000000000000000001",
            floor_div("600000000000000000005", "3"),
        );
        assert_eq!(
            "-200000000000000000002",
            floor_div("600000000000000000005", "-3"),
        );
        assert_eq!(
            "-200000000000000000002",
            floor_div("-600000000000000000005", "3"),
        );
        assert_eq!(
            "200000000000000000001",
            floor_div("-600000000000000000005", "-3"),
        );
    }

    #[test]
    fn test_floor_div_small_big() {
        assert_eq!("0", floor_div("3", "600000000000000000000"));
        assert_eq!("0", floor_div("-3", "-600000000000000000000"));
        assert_eq!("-1", floor_div("3", "-600000000000000000000"));
        assert_eq!("-1", floor_div("-3", "600000000000000000000"));
    }

    #[test]
    fn test_floor_div_small() {
        assert_eq!("4", floor_div("13", "3"));
        assert_eq!("-5", floor_div("13", "-3"));
        assert_eq!("-5", floor_div("-13", "3"));
        assert_eq!("4", floor_div("-13", "-3"));
    }

    #[test]
    fn test_percent_big() {
        assert_eq!(
            "7",
            percent("600000000000000000007", "200000000000000000000")
        );
        assert_eq!(
            "-199999999999999999993",
            percent("600000000000000000007", "-200000000000000000000")
        );
        assert_eq!(
            "199999999999999999993",
            percent("-600000000000000000007", "200000000000000000000")
        );
        assert_eq!(
            "-7",
            percent("-600000000000000000007", "-200000000000000000000")
        );
    }

    #[test]
    fn test_percent_big_small() {
        assert_eq!("7", percent("600000000000000000007", "20"));
        assert_eq!("-13", percent("600000000000000000007", "-20"));
        assert_eq!("13", percent("-600000000000000000007", "20"));
        assert_eq!("-7", percent("-600000000000000000007", "-20"));
    }

    #[test]
    fn test_percent_small_big() {
        assert_eq!("3", percent("3", "600000000000000000001"));
        assert_eq!(
            "-599999999999999999998",
            percent("3", "-600000000000000000001")
        );
        assert_eq!(
            "599999999999999999998",
            percent("-3", "600000000000000000001")
        );
        assert_eq!("-3", percent("-3", "-600000000000000000001"));
    }

    #[test]
    fn test_percent_small() {
        assert_eq!("2", percent("5", "3"));
        assert_eq!("-1", percent("5", "-3"));
        assert_eq!("1", percent("-5", "3"));
        assert_eq!("-2", percent("-5", "-3"));
    }
}


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

use dupe::Dupe;
use hashbrown::HashMap;
use once_cell::sync::Lazy;

use crate::environment::Methods;
use crate::eval::Arguments;
use crate::eval::Evaluator;
use crate::values::dict::value::dict_methods;
use crate::values::function::NativeMeth;
use crate::values::function::NativeMethod;
use crate::values::list::value::list_methods;
use crate::values::string::str_methods;
use crate::values::FrozenRef;
use crate::values::FrozenValueTyped;
use crate::values::Value;

/// Method and a `Methods` container which declares it.
#[derive(Clone, Copy, Dupe)]
pub(crate) struct KnownMethod {
    /// An object where the method is defined.
    pub(crate) type_methods: &'static Methods,
    /// The method.
    method: FrozenValueTyped<'static, NativeMethod>,
    /// Copied here from `method` to faster invocation (one fewer deref).
    imp: FrozenRef<'static, dyn NativeMeth>,
}

impl KnownMethod {
    #[inline]
    pub(crate) fn to_value<'v>(&self) -> Value<'v> {
        self.method.to_value()
    }

    #[inline]
    pub(crate) fn invoke_method<'v>(
        &self,
        this: Value<'v>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        self.imp.invoke(eval, this, args)
    }
}

/// Some of stdlib methods.
struct KnownMethods {
    methods: HashMap<&'static str, KnownMethod>,
}

impl KnownMethods {
    fn build() -> KnownMethods {
        let mut methods = HashMap::new();

        fn add_methods(
            methods: &mut HashMap<&'static str, KnownMethod>,
            type_methods: Option<&'static Methods>,
        ) {
            let type_methods = type_methods.unwrap();
            let mut has_at_least_one_method = false;
            for (name, member) in type_methods.members() {
                // Take methods, ignore attributes.
                if let Some(method) = FrozenValueTyped::new(member) {
                    // First wins, e. g. `list.clear` is hit, and `dict.clear` is miss.
                    methods.entry(name).or_insert(KnownMethod {
                        type_methods,
                        method,
                        imp: method.as_frozen_ref().map(|m| &*m.function),
                    });
                    has_at_least_one_method = true;
                }
            }
            // Sanity check.
            assert!(has_at_least_one_method);
        }

        // We don't need to add all the methods, only the most common ones. This is fine.
        add_methods(&mut methods, list_methods());
        add_methods(&mut methods, dict_methods());
        add_methods(&mut methods, str_methods());

        KnownMethods { methods }
    }
}

/// Get stdlib method by name, or `None` if method is not found
/// or method is not very common. Return arbitrary method if more than one
/// method is found (e. g. `list.clear` and `dict.clear`).
pub(crate) fn get_known_method(name: &str) -> Option<KnownMethod> {
    static ANY_METHODS: Lazy<KnownMethods> = Lazy::new(KnownMethods::build);
    ANY_METHODS.methods.get(name).copied()
}


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

pub mod any;
pub mod any_array;
pub mod array;
pub mod bigint;
pub mod bool;
pub mod dict;
pub mod enumeration;
pub mod exported_name;
pub mod float;
pub mod function;
pub(crate) mod inline_int;
pub mod int;
pub(crate) mod int_or_big;
pub(crate) mod known_methods;
pub mod list;
pub mod none;
pub mod range;
pub mod record;
pub mod regex;
pub mod starlark_value_as_type;
pub mod string;
pub mod structs;
pub mod tuple;
pub(crate) mod unbound;


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

//! The range type, constructed with `range()`.

use std::fmt;
use std::fmt::Display;
use std::num::NonZeroI32;

use allocative::Allocative;
use dupe::Dupe;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;
use starlark_derive::StarlarkDocs;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::starlark_simple_value;
use crate::values::index::convert_index;
use crate::values::index::convert_slice_indices;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::Value;
use crate::values::ValueError;
use crate::values::ValueLike;

/// Representation of `range()` type.
#[derive(
    Clone,
    Copy,
    Dupe,
    Debug,
    ProvidesStaticType,
    NoSerialize,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(builtin = "standard")]
pub struct Range {
    start: i32,
    stop: i32,
    step: NonZeroI32,
}

impl Display for Range {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.step.get() != 1 {
            write!(f, "range({}, {}, {})", self.start, self.stop, self.step)
        } else if self.start != 0 {
            write!(f, "range({}, {})", self.start, self.stop)
        } else {
            write!(f, "range({})", self.stop)
        }
    }
}

starlark_simple_value!(Range);

impl Range {
    /// The result of calling `type()` on a range.
    pub const TYPE: &'static str = "range";

    /// Create a new [`Range`].
    pub fn new(start: i32, stop: i32, step: NonZeroI32) -> Range {
        Range { start, stop, step }
    }

    fn equals_range(&self, other: &Range) -> anyhow::Result<bool> {
        let self_length = self.length()?;
        let other_length = other.length()?;
        if self_length == 0 || other_length == 0 {
            return Ok(self_length == other_length);
        }
        if self.start != other.start {
            return Ok(false);
        }
        if self_length == 1 || other_length == 1 {
            return Ok(self_length == other_length);
        }
        debug_assert!(self_length > 1);
        debug_assert!(other_length > 1);
        if self.step.get() == other.step.get() {
            return Ok(self_length == other_length);
        } else {
            return Ok(false);
        }
    }

    fn rem_range_at_iter(&self, index: usize) -> Option<Range> {
        let index = i64::try_from(index).ok()?;

        let start =
            (self.start as i64).saturating_add(index.saturating_mul(self.step.get() as i64));

        Some(Range {
            start: i32::try_from(start).ok()?,
            stop: self.stop,
            step: self.step,
        })
    }
}

#[starlark_value(type = Range::TYPE)]
impl<'v> StarlarkValue<'v> for Range {
    fn to_bool(&self) -> bool {
        (self.start < self.stop && self.step.get() > 0)
            || (self.start > self.stop && self.step.get() < 0)
    }

    fn length(&self) -> anyhow::Result<i32> {
        if self.start == self.stop {
            return Ok(0);
        }

        // If step is into opposite direction of stop, then length is zero.
        if (self.stop >= self.start) != (self.step.get() > 0) {
            return Ok(0);
        }

        // Convert range and step to `u64`
        let (dist, step) = if self.step.get() >= 0 {
            (
                self.stop.wrapping_sub(self.start) as u64,
                self.step.get() as u64,
            )
        } else {
            (
                self.start.wrapping_sub(self.stop) as u64,
                self.step.get().wrapping_neg() as u64,
            )
        };
        let i = ((dist - 1) / step + 1) as i32;
        if i >= 0 {
            Ok(i)
        } else {
            Err(ValueError::IntegerOverflow.into())
        }
    }

    fn at(&self, index: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        let index = convert_index(index, self.length()?)?;
        // Must not overflow if `length` is computed correctly
        Ok(heap.alloc(self.start + self.step.get() * index))
    }

    fn equals(&self, other: Value) -> anyhow::Result<bool> {
        if let Some(other) = other.downcast_ref::<Self>() {
            self.equals_range(other)
        } else {
            Ok(false)
        }
    }

    fn slice(
        &self,
        start: Option<Value>,
        stop: Option<Value>,
        stride: Option<Value>,
        heap: &'v Heap,
    ) -> anyhow::Result<Value<'v>> {
        let (start, stop, step) = convert_slice_indices(self.length()?, start, stop, stride)?;
        return Ok(heap.alloc(Range {
            start: self
                .start
                .checked_add(
                    start
                        .checked_mul(self.step.get())
                        .ok_or(ValueError::IntegerOverflow)?,
                )
                .ok_or(ValueError::IntegerOverflow)?,
            stop: self
                .start
                .checked_add(
                    stop.checked_mul(self.step.get())
                        .ok_or(ValueError::IntegerOverflow)?,
                )
                .ok_or(ValueError::IntegerOverflow)?,
            step: NonZeroI32::new(
                step.checked_mul(self.step.get())
                    .ok_or(ValueError::IntegerOverflow)?,
            )
            .unwrap(),
        }));
    }

    unsafe fn iterate(&self, me: Value<'v>, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(me)
    }

    unsafe fn iter_next(&self, index: usize, heap: &'v Heap) -> Option<Value<'v>> {
        let rem_range = self.rem_range_at_iter(index)?;

        if !rem_range.to_bool() {
            return None;
        }

        Some(heap.alloc(rem_range.start))
    }

    unsafe fn iter_size_hint(&self, index: usize) -> (usize, Option<usize>) {
        let Some(rem_range) = self.rem_range_at_iter(index) else {
            return (0, Some(0));
        };
        match rem_range.length() {
            Ok(length) => (length as usize, Some(length as usize)),
            Err(_) => (0, None),
        }
    }

    unsafe fn iter_stop(&self) {}

    fn is_in(&self, other: Value) -> anyhow::Result<bool> {
        let other = match other.unpack_num().and_then(|n| n.as_int()) {
            Some(other) => other,
            None => {
                // Consider `"a" in range(3)`
                //
                // Should we error or return false?
                // Go Starlark errors. Python returns false.
                // Discussion at https://github.com/bazelbuild/starlark/issues/175
                return Ok(false);
            }
        };
        if !self.to_bool() {
            return Ok(false);
        }
        if self.start == other {
            return Ok(true);
        }
        if self.step.get() > 0 {
            if other < self.start || other >= self.stop {
                return Ok(false);
            }
            Ok((other.wrapping_sub(self.start) as u64) % (self.step.get() as u64) == 0)
        } else {
            if other > self.start || other <= self.stop {
                return Ok(false);
            }
            Ok(
                (self.start.wrapping_sub(other) as u64) % (self.step.get().wrapping_neg() as u64)
                    == 0,
            )
        }
    }
}

/// For tests
impl PartialEq for Range {
    fn eq(&self, other: &Range) -> bool {
        self.equals_range(other).unwrap()
    }
}

#[cfg(test)]
mod tests {
    use std::num::NonZeroI32;

    use crate::assert;
    use crate::values::range::Range;
    use crate::values::types::inline_int::InlineInt;
    use crate::values::Heap;
    use crate::values::StarlarkValue;
    use crate::values::Value;

    fn range(start: i32, stop: i32, range: i32) -> Range {
        Range {
            start,
            stop,
            step: NonZeroI32::new(range).unwrap(),
        }
    }

    fn range_start_stop(start: i32, stop: i32) -> Range {
        range(start, stop, 1)
    }

    fn range_stop(stop: i32) -> Range {
        range_start_stop(0, stop)
    }

    #[test]
    fn length_stop() {
        assert_eq!(Some(0), range_stop(0).length().ok());
        assert_eq!(Some(17), range_stop(17).length().ok());
    }

    #[test]
    fn length_start_stop() {
        assert_eq!(Some(20), range_start_stop(10, 30).length().ok());
        assert_eq!(Some(0), range_start_stop(10, -30).length().ok());
        assert_eq!(
            Some(i32::max_value()),
            range_start_stop(0, i32::max_value()).length().ok()
        );
        assert!(range_start_stop(-1, i32::max_value()).length().is_err());
    }

    #[test]
    fn length_start_stop_step() {
        assert_eq!(Some(5), range(0, 10, 2).length().ok());
        assert_eq!(Some(5), range(0, 9, 2).length().ok());
        assert_eq!(Some(0), range(0, 10, -2).length().ok());
        assert_eq!(Some(5), range(10, 0, -2).length().ok());
        assert_eq!(Some(5), range(9, 0, -2).length().ok());
        assert_eq!(Some(1), range(4, 14, 10).length().ok());
    }

    #[test]
    fn eq() {
        assert_eq!(range_stop(0), range(2, 1, 3));
    }

    #[test]
    fn test_range_exhaustive() {
        // The range implementation is fairly hairy. Lots of corner cases etc.
        // Especially around equality, length.
        // Therefore, generate ranges exhaustively over a very small range
        // and test lots of properties about them.
        let mut ranges = Vec::with_capacity(294);
        for start in -3..4 {
            for stop in -3..4 {
                for step in -3..3 {
                    let step = if step >= 0 { step + 1 } else { step };
                    ranges.push(range(start, stop, step))
                }
            }
        }
        assert_eq!(ranges.len(), 294); // Assert we don't accidentally take too long

        let heap = Heap::new();
        for x in &ranges {
            let x = heap.alloc_simple(*x);
            let full: Vec<Value> = x.iterate(&heap).unwrap().collect();
            assert_eq!(x.length().unwrap(), full.len() as i32);
            for (i, v) in full.iter().enumerate() {
                assert_eq!(x.at(heap.alloc(i), &heap).unwrap(), *v);
            }
        }

        // Takes 294^2 steps - but completes instantly
        for x in &ranges {
            for y in &ranges {
                let x = heap.alloc_simple(*x);
                let y = heap.alloc_simple(*y);
                assert_eq!(
                    x == y,
                    Iterator::eq(x.iterate(&heap).unwrap(), y.iterate(&heap).unwrap())
                )
            }
        }
    }

    #[test]
    fn test_max_len() {
        assert::eq(
            &InlineInt::MAX.to_string(),
            &format!("len(range({}))", InlineInt::MAX),
        );
        assert::eq(
            &InlineInt::MAX.to_string(),
            &format!("len(range({}, -1))", InlineInt::MIN),
        );
    }
}


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

//! A type [`StarlarkRegex`] which wraps Rust value fancy_regex::Regex.
use std::fmt;
use std::fmt::Display;

use allocative::Allocative;
use fancy_regex::Regex;
use starlark_derive::starlark_module;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;
use starlark_derive::StarlarkDocs;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::environment::Methods;
use crate::environment::MethodsBuilder;
use crate::environment::MethodsStatic;
use crate::starlark_simple_value;
use crate::values::StarlarkValue;

/// A type that can be passed around as a StarlarkRegex, which wraps Rust value
/// fancy_regex::Regex.
#[derive(ProvidesStaticType, Debug, NoSerialize, StarlarkDocs, Allocative)]
#[starlark_docs(builtin = "extension")]
pub struct StarlarkRegex(#[allocative(skip)] pub Regex);

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

impl StarlarkRegex {
    /// The result of calling `type()` on regex.
    pub const TYPE: &'static str = "regex";
}

impl Display for StarlarkRegex {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "regex({:?})", &self.0.as_str())
    }
}

starlark_simple_value!(StarlarkRegex);
impl StarlarkRegex {
    /// Create a new [`StarlarkRegex`] value. Such a value can be allocated on a heap with
    /// `heap.alloc(StarlarkRegex::new(x))`.
    pub fn new(x: &str) -> anyhow::Result<Self> {
        Ok(Self(Regex::new(x)?))
    }
}

#[starlark_module]
fn regex_type_methods(builder: &mut MethodsBuilder) {
    fn r#match(this: &StarlarkRegex, #[starlark(require = pos)] str: &str) -> anyhow::Result<bool> {
        Ok(this.0.is_match(str)?)
    }
}

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

    #[test]
    fn test_match() {
        assert::all_true(
            r#"
experimental_regex("abc|def|ghi").match("abc")
not experimental_regex("abc|def|ghi").match("xyz")
not experimental_regex("^((?!abc).)*$").match("abc")
experimental_regex("^((?!abc).)*$").match("xyz")
"#,
        );
    }

    #[test]
    fn test_str() {
        assert::is_true(
            r#"
str(experimental_regex("foo")) == 'regex("foo")'
"#,
        );
    }
}


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

//! Convert a value implementing [`StarlarkValue`] into a type usable in type expression.

use std::marker::PhantomData;

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

use crate as starlark;
use crate::private::Private;
use crate::typing::Ty;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::AllocFrozenValue;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::StarlarkValue;

/// Utility to declare a value usable in type expression.
///
/// # Example
///
/// ```
/// use allocative::Allocative;
/// use starlark::environment::GlobalsBuilder;
/// use starlark::values::starlark_value_as_type::StarlarkValueAsType;
/// use starlark::values::StarlarkValue;
/// use starlark::any::ProvidesStaticType;
/// use starlark::values::{NoSerialize, starlark_value};
/// #[derive(Debug, derive_more::Display, Allocative, ProvidesStaticType, NoSerialize)]
/// struct Temperature;
///
/// #[starlark_value(type = "temperature")]
/// impl<'v> StarlarkValue<'v> for Temperature {
/// }
///
/// fn my_type_globals(globals: &mut GlobalsBuilder) {
///     // This can now be used like:
///     // ```
///     // def f(x: Temperature): pass
///     // ```
///     const Temperature: StarlarkValueAsType<Temperature> = StarlarkValueAsType::new();
/// }
/// ```
#[derive(
    Debug,
    derive_more::Display,
    Allocative,
    ProvidesStaticType,
    NoSerialize
)]
#[display(fmt = "{}", T::TYPE)]
pub struct StarlarkValueAsType<T: StarlarkValue<'static> + 'static>(PhantomData<fn(&T)>);

impl<T: StarlarkValue<'static>> StarlarkValueAsType<T> {
    /// Constructor.
    pub const fn new() -> Self {
        Self(PhantomData)
    }
}

impl<T: StarlarkValue<'static>> Default for StarlarkValueAsType<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T: StarlarkValue<'static>> AllocFrozenValue for StarlarkValueAsType<T> {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc_simple(self)
    }
}

#[starlark_value(type = "type")]
impl<'v, T: StarlarkValue<'static>> StarlarkValue<'v> for StarlarkValueAsType<T> {
    fn eval_type(&self, _private: Private) -> Option<Ty> {
        Some(T::starlark_type_repr())
    }
}

#[cfg(test)]
mod tests {
    use allocative::Allocative;
    use starlark_derive::starlark_module;
    use starlark_derive::starlark_value;
    use starlark_derive::NoSerialize;
    use starlark_derive::ProvidesStaticType;

    use crate as starlark;
    use crate::assert::Assert;
    use crate::environment::GlobalsBuilder;
    use crate::values::types::starlark_value_as_type::tests;
    use crate::values::types::starlark_value_as_type::StarlarkValueAsType;
    use crate::values::AllocValue;
    use crate::values::Heap;
    use crate::values::StarlarkValue;
    use crate::values::Value;

    #[derive(
        derive_more::Display,
        Debug,
        NoSerialize,
        Allocative,
        ProvidesStaticType
    )]
    struct CompilerArgs(String);

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

    impl<'v> AllocValue<'v> for CompilerArgs {
        fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
            heap.alloc_simple(self)
        }
    }

    #[starlark_module]
    fn compiler_args_globals(globals: &mut GlobalsBuilder) {
        const CompilerArgs: StarlarkValueAsType<CompilerArgs> = StarlarkValueAsType::new();

        fn compiler_args(x: String) -> anyhow::Result<CompilerArgs> {
            Ok(tests::CompilerArgs(x))
        }
    }

    #[test]
    fn test_pass() {
        let mut a = Assert::new();
        a.globals_add(compiler_args_globals);
        a.pass(
            r#"
def f(x: CompilerArgs): pass

f(compiler_args("hello"))
        "#,
        );
    }

    #[test]
    fn test_fail_compile_time() {
        let mut a = Assert::new();
        a.globals_add(compiler_args_globals);
        a.fail(
            r#"
def g(x: CompilerArgs): pass

g([])
            "#,
            r#"Expected type `"compiler_args"` but got"#,
        );
    }

    #[test]
    fn test_fail_runtime() {
        let mut a = Assert::new();
        a.globals_add(compiler_args_globals);
        a.fail(
            r#"
def h(x: CompilerArgs): pass

noop(h)(1)
            "#,
            r#"Value `1` of type `int` does not match the type annotation"#,
        );
    }
}


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

//! Handle special "unbound" globals: methods or attributes.

use crate::values::function::BoundMethodGen;
use crate::values::function::NativeAttribute;
use crate::values::function::NativeMethod;
use crate::values::layout::value_not_special::FrozenValueNotSpecial;
use crate::values::FrozenValueTyped;
use crate::values::Heap;
use crate::values::Value;
use crate::values::ValueLike;

/// A value or an unbound method or unbound attribute.
pub(crate) enum MaybeUnboundValue {
    /// A method with `this` unbound.
    Method(FrozenValueTyped<'static, NativeMethod>),
    /// An attribute with `this` unbound.
    Attr(FrozenValueTyped<'static, NativeAttribute>),
}

impl MaybeUnboundValue {
    /// Bind this object to given `this` value.
    pub(crate) fn bind<'v>(self, this: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match self {
            MaybeUnboundValue::Method(m) => {
                Ok(heap.alloc_complex(BoundMethodGen::new(this.to_value(), m)))
            }
            MaybeUnboundValue::Attr(a) => a.call(this, heap),
        }
    }
}

impl MaybeUnboundValue {
    /// Split into variants.
    #[allow(clippy::same_functions_in_if_condition)] // False positive
    pub(crate) fn new(value: FrozenValueNotSpecial) -> MaybeUnboundValue {
        // TODO(nga): this can be a little faster if we do downcast of `FrozenValueNotSpecial`
        //   instead of converting it to `FrozenValue` first.
        if let Some(method) = FrozenValueTyped::new(value.to_frozen_value()) {
            MaybeUnboundValue::Method(method)
        } else if let Some(attr) = FrozenValueTyped::new(value.to_frozen_value()) {
            MaybeUnboundValue::Attr(attr)
        } else {
            unreachable!("not a member: {}", value);
        }
    }
}


/*
 * 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 crate::typing::Ty;
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::UnpackValue;
use crate::values::Value;

impl StarlarkTypeRepr for u32 {
    fn starlark_type_repr() -> Ty {
        i32::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for u32 {
    #[inline]
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl AllocFrozenValue for u32 {
    #[inline]
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl StarlarkTypeRepr for u64 {
    fn starlark_type_repr() -> Ty {
        i32::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for u64 {
    #[inline]
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl AllocFrozenValue for u64 {
    #[inline]
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl StarlarkTypeRepr for i64 {
    fn starlark_type_repr() -> Ty {
        i32::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for i64 {
    #[inline]
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl AllocFrozenValue for i64 {
    #[inline]
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl StarlarkTypeRepr for usize {
    fn starlark_type_repr() -> Ty {
        i32::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for usize {
    #[inline]
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl AllocFrozenValue for usize {
    #[inline]
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl StarlarkTypeRepr for isize {
    fn starlark_type_repr() -> Ty {
        i32::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for isize {
    #[inline]
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl AllocFrozenValue for isize {
    #[inline]
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc(StarlarkInt::from(self))
    }
}

impl<'v> UnpackValue<'v> for u32 {
    fn unpack_value(value: Value<'v>) -> Option<u32> {
        value.unpack_integer()
    }
}

impl<'v> UnpackValue<'v> for u64 {
    fn unpack_value(value: Value<'v>) -> Option<u64> {
        value.unpack_integer()
    }
}

impl<'v> UnpackValue<'v> for i64 {
    fn unpack_value(value: Value<'v>) -> Option<i64> {
        value.unpack_integer()
    }
}

impl<'v> UnpackValue<'v> for usize {
    fn unpack_value(value: Value<'v>) -> Option<usize> {
        value.unpack_integer()
    }
}

impl<'v> UnpackValue<'v> for isize {
    fn unpack_value(value: Value<'v>) -> Option<isize> {
        value.unpack_integer()
    }
}