/*
 * 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 std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::hash::Hash;
use std::hash::Hasher;

use allocative::Allocative;
use anyhow::Context;
use dupe::Dupe;
use starlark_derive::starlark_module;
use starlark_derive::starlark_value;
use starlark_map::StarlarkHasher;
use thiserror::Error;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::coerce::Coerce;
use crate::environment::GlobalsBuilder;
use crate::environment::Methods;
use crate::environment::MethodsBuilder;
use crate::environment::MethodsStatic;
use crate::private::Private;
use crate::slice_vec_ext::SliceExt;
use crate::typing::Ty;
use crate::values::dict::Dict;
use crate::values::dict::DictRef;
use crate::values::layout::avalue::alloc_static;
use crate::values::layout::avalue::AValueImpl;
use crate::values::layout::avalue::Basic;
use crate::values::layout::heap::repr::AValueRepr;
use crate::values::list::ListRef;
use crate::values::none::NoneType;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::types::tuple::value::Tuple;
use crate::values::types::tuple::value::TupleGen;
use crate::values::AllocValue;
use crate::values::Demand;
use crate::values::Freeze;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::NoSerialize;
use crate::values::StarlarkValue;
use crate::values::Trace;
use crate::values::Value;
use crate::values::ValueLike;

#[derive(Debug, Error)]
enum TypingError {
    /// The value does not have the specified type
    #[error("Value `{0}` of type `{1}` does not match the type annotation `{2}` for {3}")]
    TypeAnnotationMismatch(String, String, String, String),
    /// The given type annotation does not represent a type
    #[error("Type `{0}` is not a valid type annotation")]
    InvalidTypeAnnotation(String),
    /// The given type annotation does not exist, but the user might have forgotten quotes around
    /// it
    #[error(r#"Found `{0}` instead of a valid type annotation. Perhaps you meant `"{1}"`?"#)]
    PerhapsYouMeant(String, String),
    #[error("Value of type `{1}` does not match type `{2}`: {0}")]
    ValueDoesNotMatchType(String, &'static str, String),
}

trait TypeCompiledImpl<'v>: Allocative + Debug + 'v {
    fn as_ty(&self) -> Ty;
    fn matches(&self, value: Value<'v>) -> bool;
    fn is_wildcard(&self) -> bool {
        false
    }
    fn to_frozen(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue>;
}

unsafe impl<'v> ProvidesStaticType<'v> for &'v dyn TypeCompiledImpl<'v> {
    type StaticType = &'static dyn TypeCompiledImpl<'static>;
}

#[derive(Debug, Trace, Freeze, Allocative, ProvidesStaticType, NoSerialize)]
struct TypeCompiledImplAsStarlarkValue<T>(T);

impl<T> TypeCompiledImplAsStarlarkValue<T>
where
    TypeCompiledImplAsStarlarkValue<T>: StarlarkValue<'static>,
{
    const fn alloc_static(imp: T) -> AValueRepr<AValueImpl<Basic, Self>> {
        alloc_static(Basic, TypeCompiledImplAsStarlarkValue(imp))
    }
}

#[starlark_value(type = "eval_type")]
impl<'v, T> StarlarkValue<'v> for TypeCompiledImplAsStarlarkValue<T>
where
    T: TypeCompiledImpl<'v> + Hash + Eq,
    Self: ProvidesStaticType<'v>,
{
    fn type_matches_value(&self, value: Value<'v>, _private: Private) -> bool {
        self.0.matches(value)
    }

    fn provide(&'v self, demand: &mut Demand<'_, 'v>) {
        demand.provide_value::<&'v dyn TypeCompiledImpl<'v>>(&self.0);
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        Hash::hash(&self.0, hasher);
        Ok(())
    }

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        let Some(other) = other.downcast_ref::<Self>() else {
            return Ok(false);
        };
        Ok(self.0 == other.0)
    }

    fn get_methods() -> Option<&'static Methods>
    where
        Self: Sized,
    {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(type_compiled_methods)
    }
}

impl<'v, T: TypeCompiledImpl<'v>> Display for TypeCompiledImplAsStarlarkValue<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "eval_type({})", self.0.as_ty())
    }
}

#[starlark_module]
fn type_compiled_methods(methods: &mut MethodsBuilder) {
    /// True iff the value matches this type.
    fn matches<'v>(this: Value<'v>, value: Value<'v>) -> anyhow::Result<bool> {
        Ok(this.get_ref().type_matches_value(value))
    }

    /// Error if the value does not match this type.
    fn check_matches<'v>(this: Value<'v>, value: Value<'v>) -> anyhow::Result<NoneType> {
        if !this.get_ref().type_matches_value(value) {
            return Err(TypingError::ValueDoesNotMatchType(
                value.to_repr(),
                value.get_type(),
                TypeCompiled(this).to_string(),
            )
            .into());
        }
        Ok(NoneType)
    }
}

#[starlark_module]
pub(crate) fn register_eval_type(globals: &mut GlobalsBuilder) {
    /// Create a runtime type object which can be used to check if a value matches the given type.
    fn eval_type<'v>(
        #[starlark(require = pos)] ty: Value<'v>,
        heap: &'v Heap,
    ) -> anyhow::Result<TypeCompiled<Value<'v>>> {
        TypeCompiled::new(ty, heap)
    }
}

#[derive(
    Debug,
    Allocative,
    Freeze,
    Trace,
    Clone,
    Copy,
    Dupe,
    Coerce,
    ProvidesStaticType
)]
#[repr(transparent)]
pub(crate) struct TypeCompiled<V>(
    /// `V` is `TypeCompiledImplAsStarlarkValue`.
    V,
);

impl<'v, V: ValueLike<'v>> Display for TypeCompiled<V> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self.downcast() {
            Ok(t) => Display::fmt(&t.as_ty(), f),
            Err(_) => {
                // This is unreachable, but we should not panic in `Display`.
                Display::fmt(&self.0, f)
            }
        }
    }
}

impl<V> StarlarkTypeRepr for TypeCompiled<V> {
    fn starlark_type_repr() -> Ty {
        Ty::name("eval_type")
    }
}

impl<'v, V: ValueLike<'v>> AllocValue<'v> for TypeCompiled<V> {
    fn alloc_value(self, _heap: &'v Heap) -> Value<'v> {
        self.0.to_value()
    }
}

impl<'v, V: ValueLike<'v>> TypeCompiled<V> {
    fn downcast(self) -> anyhow::Result<&'v dyn TypeCompiledImpl<'v>> {
        self.to_value()
            .0
            .request_value::<&dyn TypeCompiledImpl>()
            .context("Not TypeCompiledImpl (internal error)")
    }

    pub(crate) fn matches(&self, value: Value<'v>) -> bool {
        self.0.to_value().get_ref().type_matches_value(value)
    }

    pub(crate) fn as_ty(&self) -> Ty {
        self.downcast().unwrap().as_ty()
    }

    pub(crate) fn type_is_wildcard(self) -> bool {
        self.downcast().unwrap().is_wildcard()
    }

    #[cold]
    #[inline(never)]
    fn check_type_error(self, value: Value<'v>, arg_name: Option<&str>) -> anyhow::Result<()> {
        Err(TypingError::TypeAnnotationMismatch(
            value.to_str(),
            value.get_type().to_owned(),
            self.to_string(),
            match arg_name {
                None => "return type".to_owned(),
                Some(x) => format!("argument `{}`", x),
            },
        )
        .into())
    }

    pub(crate) fn check_type(self, value: Value<'v>, arg_name: Option<&str>) -> anyhow::Result<()> {
        if self.matches(value) {
            Ok(())
        } else {
            self.check_type_error(value, arg_name)
        }
    }

    pub(crate) fn to_value(self) -> TypeCompiled<Value<'v>> {
        TypeCompiled(self.0.to_value())
    }

    pub(crate) fn to_inner(self) -> V {
        self.0
    }

    pub(crate) fn write_hash(self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        self.to_value().0.write_hash(hasher)
    }

    pub(crate) fn equals(self, other: Self) -> anyhow::Result<bool> {
        self.to_value().0.equals(other.to_value().0)
    }
}

impl<'v, V: ValueLike<'v>> Hash for TypeCompiled<V> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self.0.to_value().get_hash() {
            Ok(h) => h.hash(state),
            Err(_) => {
                // Unreachable, but we should not panic in `Hash`.
            }
        }
    }
}

impl<'v, V: ValueLike<'v>> PartialEq for TypeCompiled<V> {
    #[allow(clippy::manual_unwrap_or)]
    fn eq(&self, other: &Self) -> bool {
        match self.0.to_value().equals(other.0.to_value()) {
            Ok(b) => b,
            Err(_) => {
                // Unreachable, but we should not panic in `PartialEq`.
                false
            }
        }
    }
}

impl<'v, V: ValueLike<'v>> Eq for TypeCompiled<V> {}

impl<'v, V: ValueLike<'v>> TypeCompiled<V> {
    fn type_anything() -> TypeCompiled<V> {
        #[derive(Eq, PartialEq, Hash, Allocative, Debug, ProvidesStaticType)]
        struct Anything;

        impl<'v> TypeCompiledImpl<'v> for Anything {
            fn as_ty(&self) -> Ty {
                Ty::Any
            }

            fn matches(&self, _value: Value<'v>) -> bool {
                true
            }

            fn is_wildcard(&self) -> bool {
                true
            }

            fn to_frozen(&self, _heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled::type_anything()
            }
        }

        static ANYTHING: AValueRepr<AValueImpl<Basic, TypeCompiledImplAsStarlarkValue<Anything>>> =
            TypeCompiledImplAsStarlarkValue::alloc_static(Anything);

        TypeCompiled(V::from_frozen_value(FrozenValue::new_repr(&ANYTHING)))
    }

    fn type_none() -> TypeCompiled<V> {
        #[derive(Eq, PartialEq, Hash, Allocative, Debug, ProvidesStaticType)]
        struct IsNone;

        impl<'v> TypeCompiledImpl<'v> for IsNone {
            fn as_ty(&self) -> Ty {
                Ty::none()
            }

            fn matches(&self, value: Value<'v>) -> bool {
                value.is_none()
            }

            fn to_frozen(&self, _heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled::type_none()
            }
        }

        static IS_NONE: AValueRepr<AValueImpl<Basic, TypeCompiledImplAsStarlarkValue<IsNone>>> =
            TypeCompiledImplAsStarlarkValue::alloc_static(IsNone);

        TypeCompiled(V::from_frozen_value(FrozenValue::new_repr(&IS_NONE)))
    }

    fn type_string() -> TypeCompiled<V> {
        #[derive(Eq, PartialEq, Hash, Allocative, Debug, ProvidesStaticType)]
        struct IsString;

        impl<'v> TypeCompiledImpl<'v> for IsString {
            fn as_ty(&self) -> Ty {
                Ty::string()
            }

            fn matches(&self, value: Value<'v>) -> bool {
                value.unpack_str().is_some() || value.get_ref().matches_type("string")
            }

            fn to_frozen(&self, _heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled::type_string()
            }
        }

        static IS_STRING: AValueRepr<AValueImpl<Basic, TypeCompiledImplAsStarlarkValue<IsString>>> =
            TypeCompiledImplAsStarlarkValue::alloc_static(IsString);

        TypeCompiled(V::from_frozen_value(FrozenValue::new_repr(&IS_STRING)))
    }

    fn type_int() -> TypeCompiled<V> {
        #[derive(Eq, PartialEq, Hash, Allocative, Debug, ProvidesStaticType)]
        struct IsInt;

        impl<'v> TypeCompiledImpl<'v> for IsInt {
            fn as_ty(&self) -> Ty {
                Ty::int()
            }

            fn matches(&self, value: Value<'v>) -> bool {
                value.unpack_inline_int().is_some() || value.get_ref().matches_type("int")
            }

            fn to_frozen(&self, _heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled::type_int()
            }
        }

        static IS_INT: AValueRepr<AValueImpl<Basic, TypeCompiledImplAsStarlarkValue<IsInt>>> =
            TypeCompiledImplAsStarlarkValue::alloc_static(IsInt);

        TypeCompiled(V::from_frozen_value(FrozenValue::new_repr(&IS_INT)))
    }

    fn type_bool() -> TypeCompiled<V> {
        #[derive(Eq, PartialEq, Hash, Allocative, Debug, ProvidesStaticType)]
        struct IsBool;

        impl<'v> TypeCompiledImpl<'v> for IsBool {
            fn as_ty(&self) -> Ty {
                Ty::bool()
            }

            fn matches(&self, value: Value<'v>) -> bool {
                value.unpack_bool().is_some() || value.get_ref().matches_type("bool")
            }

            fn to_frozen(&self, _heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled::type_bool()
            }
        }

        static IS_BOOL: AValueRepr<AValueImpl<Basic, TypeCompiledImplAsStarlarkValue<IsBool>>> =
            TypeCompiledImplAsStarlarkValue::alloc_static(IsBool);

        TypeCompiled(V::from_frozen_value(FrozenValue::new_repr(&IS_BOOL)))
    }

    fn type_list() -> TypeCompiled<V> {
        #[derive(Eq, PartialEq, Hash, Allocative, Debug, ProvidesStaticType)]
        struct IsList;

        impl<'v> TypeCompiledImpl<'v> for IsList {
            fn as_ty(&self) -> Ty {
                Ty::list(Ty::Any)
            }

            fn matches(&self, value: Value<'v>) -> bool {
                ListRef::from_value(value).is_some()
            }

            fn to_frozen(&self, _heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled::type_list()
            }
        }

        static IS_LIST: AValueRepr<AValueImpl<Basic, TypeCompiledImplAsStarlarkValue<IsList>>> =
            TypeCompiledImplAsStarlarkValue::alloc_static(IsList);

        TypeCompiled(V::from_frozen_value(FrozenValue::new_repr(&IS_LIST)))
    }

    fn type_dict() -> TypeCompiled<V> {
        #[derive(Eq, PartialEq, Hash, Allocative, Debug, ProvidesStaticType)]
        struct IsDict;

        impl<'v> TypeCompiledImpl<'v> for IsDict {
            fn as_ty(&self) -> Ty {
                Ty::dict(Ty::Any, Ty::Any)
            }

            fn matches(&self, value: Value<'v>) -> bool {
                DictRef::from_value(value).is_some()
            }

            fn to_frozen(&self, _heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled::type_dict()
            }
        }

        static IS_DICT: AValueRepr<AValueImpl<Basic, TypeCompiledImplAsStarlarkValue<IsDict>>> =
            TypeCompiledImplAsStarlarkValue::alloc_static(IsDict);

        TypeCompiled(V::from_frozen_value(FrozenValue::new_repr(&IS_DICT)))
    }

    pub(crate) fn to_frozen(self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
        if let Some(v) = self.0.to_value().unpack_frozen() {
            TypeCompiled(v)
        } else {
            self.to_value().downcast().unwrap().to_frozen(heap)
        }
    }
}

// These functions are small, but are deliberately out-of-line so we get better
// information in profiling about the origin of these closures
impl<'v> TypeCompiled<Value<'v>> {
    fn type_concrete(t: &str, heap: &'v Heap) -> TypeCompiled<Value<'v>> {
        #[derive(
            Eq,
            PartialEq,
            Hash,
            Allocative,
            Debug,
            Trace,
            Freeze,
            ProvidesStaticType,
            Clone
        )]
        struct IsConcrete(String);

        impl<'v> TypeCompiledImpl<'v> for IsConcrete {
            fn as_ty(&self) -> Ty {
                Ty::name(&self.0)
            }

            fn matches(&self, value: Value<'v>) -> bool {
                value.get_ref().matches_type(&self.0)
            }

            fn to_frozen(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled(
                    heap.alloc_simple(TypeCompiledImplAsStarlarkValue(IsConcrete::clone(self))),
                )
            }
        }

        TypeCompiled(heap.alloc_simple(TypeCompiledImplAsStarlarkValue(IsConcrete(t.to_owned()))))
    }

    /// Hold `Ty`, but only check name if it is provided.
    fn ty_other(ty: Ty, heap: &'v Heap) -> TypeCompiled<Value<'v>> {
        #[derive(Eq, PartialEq, Allocative, Debug, ProvidesStaticType)]
        struct Erased {
            ty: Ty,
            name: Option<String>,
        }

        impl Hash for Erased {
            fn hash<H: Hasher>(&self, state: &mut H) {
                // TODO(nga): implement `Hash` for `Ty`.
                self.ty.as_name().hash(state)
            }
        }

        impl<'v> TypeCompiledImpl<'v> for Erased {
            fn as_ty(&self) -> Ty {
                self.ty.clone()
            }

            fn matches(&self, value: Value<'v>) -> bool {
                if let Some(name) = &self.name {
                    value.get_ref().matches_type(name)
                } else {
                    true
                }
            }

            fn is_wildcard(&self) -> bool {
                self.name.is_none()
            }

            fn to_frozen(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled(heap.alloc_simple(TypeCompiledImplAsStarlarkValue(Erased {
                    ty: self.ty.clone(),
                    name: self.name.clone(),
                })))
            }
        }

        let name = ty.as_name().map(|s| s.to_owned());
        TypeCompiled(heap.alloc_simple(TypeCompiledImplAsStarlarkValue(Erased { ty, name })))
    }

    pub(crate) fn type_list_of(
        t: TypeCompiled<Value<'v>>,
        heap: &'v Heap,
    ) -> TypeCompiled<Value<'v>> {
        #[derive(Allocative, Debug, Trace, Freeze, ProvidesStaticType)]
        struct IsListOf<V>(TypeCompiled<V>);

        impl<V> PartialEq for IsListOf<V>
        where
            TypeCompiled<V>: PartialEq,
        {
            fn eq(&self, other: &Self) -> bool {
                self.0.eq(&other.0)
            }
        }

        impl<V> Eq for IsListOf<V> where TypeCompiled<V>: Eq {}

        impl<V> Hash for IsListOf<V>
        where
            TypeCompiled<V>: Hash,
        {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.0.hash(state)
            }
        }

        impl<'v, V: ValueLike<'v>> TypeCompiledImpl<'v> for IsListOf<V>
        where
            Self: ProvidesStaticType<'v>,
        {
            fn as_ty(&self) -> Ty {
                Ty::list(self.0.as_ty())
            }

            fn matches(&self, value: Value<'v>) -> bool {
                match ListRef::from_value(value) {
                    None => false,
                    Some(list) => list.iter().all(|v| self.0.matches(v)),
                }
            }

            fn to_frozen(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled(heap.alloc_simple(TypeCompiledImplAsStarlarkValue(IsListOf(
                    self.0.to_frozen(heap),
                ))))
            }
        }

        TypeCompiled(heap.alloc_complex(TypeCompiledImplAsStarlarkValue(IsListOf(t))))
    }

    fn type_any_of_two(
        t1: TypeCompiled<Value<'v>>,
        t2: TypeCompiled<Value<'v>>,
        heap: &'v Heap,
    ) -> TypeCompiled<Value<'v>> {
        #[derive(Allocative, Debug, Trace, Freeze, ProvidesStaticType)]
        struct IsAnyOfTwo<V>(TypeCompiled<V>, TypeCompiled<V>);

        impl<V> Hash for IsAnyOfTwo<V>
        where
            TypeCompiled<V>: Hash,
        {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.0.hash(state);
                self.1.hash(state);
            }
        }

        impl<V> PartialEq for IsAnyOfTwo<V>
        where
            TypeCompiled<V>: PartialEq,
        {
            fn eq(&self, other: &Self) -> bool {
                self.0.eq(&other.0) && self.1.eq(&other.1)
            }
        }

        impl<V> Eq for IsAnyOfTwo<V> where TypeCompiled<V>: Eq {}

        impl<'v, V: ValueLike<'v>> TypeCompiledImpl<'v> for IsAnyOfTwo<V>
        where
            Self: ProvidesStaticType<'v>,
        {
            fn as_ty(&self) -> Ty {
                Ty::union2(self.0.as_ty(), self.1.as_ty())
            }

            fn matches(&self, value: Value<'v>) -> bool {
                self.0.matches(value) || self.1.matches(value)
            }

            fn to_frozen(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled(
                    heap.alloc_simple(TypeCompiledImplAsStarlarkValue(IsAnyOfTwo(
                        self.0.to_frozen(heap),
                        self.1.to_frozen(heap),
                    ))),
                )
            }
        }

        TypeCompiled(heap.alloc_complex(TypeCompiledImplAsStarlarkValue(IsAnyOfTwo(t1, t2))))
    }

    pub(crate) fn type_any_of(
        ts: Vec<TypeCompiled<Value<'v>>>,
        heap: &'v Heap,
    ) -> TypeCompiled<Value<'v>> {
        #[derive(Allocative, Debug, Trace, Freeze, ProvidesStaticType)]
        struct IsAnyOf<V>(Vec<TypeCompiled<V>>);

        impl<V> Hash for IsAnyOf<V>
        where
            TypeCompiled<V>: Hash,
        {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.0.hash(state);
            }
        }

        impl<V> PartialEq for IsAnyOf<V>
        where
            TypeCompiled<V>: PartialEq,
        {
            fn eq(&self, other: &Self) -> bool {
                self.0.eq(&other.0)
            }
        }

        impl<V> Eq for IsAnyOf<V> where TypeCompiled<V>: Eq {}

        impl<'v, V: ValueLike<'v>> TypeCompiledImpl<'v> for IsAnyOf<V>
        where
            Self: ProvidesStaticType<'v>,
        {
            fn as_ty(&self) -> Ty {
                Ty::unions(self.0.map(|t| t.as_ty()))
            }

            fn matches(&self, value: Value<'v>) -> bool {
                self.0.iter().any(|t| t.matches(value))
            }

            fn to_frozen(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled(heap.alloc_simple(TypeCompiledImplAsStarlarkValue(IsAnyOf(
                    self.0.iter().map(|t| t.to_frozen(heap)).collect(),
                ))))
            }
        }

        TypeCompiled(heap.alloc_complex(TypeCompiledImplAsStarlarkValue(IsAnyOf(ts))))
    }

    pub(crate) fn type_dict_of(
        kt: TypeCompiled<Value<'v>>,
        vt: TypeCompiled<Value<'v>>,
        heap: &'v Heap,
    ) -> TypeCompiled<Value<'v>> {
        #[derive(Allocative, Debug, Trace, Freeze, ProvidesStaticType)]
        struct IsDictOf<V>(TypeCompiled<V>, TypeCompiled<V>);

        impl<V> Hash for IsDictOf<V>
        where
            TypeCompiled<V>: Hash,
        {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.0.hash(state);
                self.1.hash(state);
            }
        }

        impl<V> PartialEq for IsDictOf<V>
        where
            TypeCompiled<V>: PartialEq,
        {
            fn eq(&self, other: &Self) -> bool {
                self.0.eq(&other.0) && self.1.eq(&other.1)
            }
        }

        impl<V> Eq for IsDictOf<V> where TypeCompiled<V>: Eq {}

        impl<'v, V: ValueLike<'v>> TypeCompiledImpl<'v> for IsDictOf<V>
        where
            Self: ProvidesStaticType<'v>,
        {
            fn as_ty(&self) -> Ty {
                Ty::dict(self.0.as_ty(), self.1.as_ty())
            }

            fn matches(&self, value: Value<'v>) -> bool {
                match DictRef::from_value(value) {
                    None => false,
                    Some(dict) => dict
                        .iter()
                        .all(|(k, v)| self.0.matches(k) && self.1.matches(v)),
                }
            }

            fn to_frozen(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled(heap.alloc_simple(TypeCompiledImplAsStarlarkValue(IsDictOf(
                    self.0.to_frozen(heap),
                    self.1.to_frozen(heap),
                ))))
            }
        }

        TypeCompiled(heap.alloc_complex(TypeCompiledImplAsStarlarkValue(IsDictOf(kt, vt))))
    }

    pub(crate) fn type_tuple_of(
        ts: Vec<TypeCompiled<Value<'v>>>,
        heap: &'v Heap,
    ) -> TypeCompiled<Value<'v>> {
        #[derive(Allocative, Debug, Trace, Freeze, ProvidesStaticType)]
        struct IsTupleOf<V>(Vec<TypeCompiled<V>>);

        impl<V> Hash for IsTupleOf<V>
        where
            TypeCompiled<V>: Hash,
        {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.0.hash(state);
            }
        }

        impl<V> PartialEq for IsTupleOf<V>
        where
            TypeCompiled<V>: PartialEq,
        {
            fn eq(&self, other: &Self) -> bool {
                self.0.eq(&other.0)
            }
        }

        impl<V> Eq for IsTupleOf<V> where TypeCompiled<V>: Eq {}

        impl<'v, V: ValueLike<'v>> TypeCompiledImpl<'v> for IsTupleOf<V>
        where
            Self: ProvidesStaticType<'v>,
        {
            fn as_ty(&self) -> Ty {
                Ty::Tuple(self.0.map(|t| t.as_ty()))
            }

            fn matches(&self, value: Value<'v>) -> bool {
                match Tuple::from_value(value) {
                    Some(v) if v.len() == self.0.len() => {
                        v.iter().zip(self.0.iter()).all(|(v, t)| t.matches(v))
                    }
                    _ => false,
                }
            }

            fn to_frozen(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
                TypeCompiled(heap.alloc_simple(TypeCompiledImplAsStarlarkValue(IsTupleOf(
                    self.0.iter().map(|t| t.to_frozen(heap)).collect(),
                ))))
            }
        }

        TypeCompiled(heap.alloc_complex(TypeCompiledImplAsStarlarkValue(IsTupleOf(ts))))
    }

    /// Types that are `""` or start with `"_"` are wildcard - they match everything.
    pub(crate) fn is_wildcard(x: &str) -> bool {
        x == "" || x.starts_with('_')
    }

    pub(crate) fn is_wildcard_value(x: Value) -> bool {
        x.unpack_str().map(TypeCompiled::is_wildcard) == Some(true)
    }

    /// For `p: "xxx"`, parse that `"xxx"` as type.
    pub(crate) fn from_str(t: &str, heap: &'v Heap) -> TypeCompiled<Value<'v>> {
        if TypeCompiled::is_wildcard(t) {
            TypeCompiled::type_anything()
        } else {
            match t {
                "string" => TypeCompiled::type_string(),
                "int" => TypeCompiled::type_int(),
                "bool" => TypeCompiled::type_bool(),
                t => TypeCompiled::type_concrete(t, heap),
            }
        }
    }

    fn from_tuple(
        t: &TupleGen<Value<'v>>,
        heap: &'v Heap,
    ) -> anyhow::Result<TypeCompiled<Value<'v>>> {
        let ts = t.content().try_map(|t| TypeCompiled::new(*t, heap))?;
        Ok(TypeCompiled::type_tuple_of(ts, heap))
    }

    /// Parse `[t1, t2, ...]` as type.
    fn from_list(t: &ListRef<'v>, heap: &'v Heap) -> anyhow::Result<TypeCompiled<Value<'v>>> {
        match t.len() {
            0 => Err(TypingError::InvalidTypeAnnotation(t.to_string()).into()),
            1 => {
                // Must be a list with all elements of this type
                let t = *t.first().unwrap();
                if TypeCompiled::is_wildcard_value(t) {
                    // Any type - so avoid the inner iteration
                    Ok(TypeCompiled::type_list())
                } else {
                    let t = TypeCompiled::new(t, heap)?;
                    Ok(TypeCompiled::type_list_of(t, heap))
                }
            }
            2 => {
                // A union type, can match either - special case of the arbitrary choice to go slightly faster
                let t1 = TypeCompiled::new(t[0], heap)?;
                let t2 = TypeCompiled::new(t[1], heap)?;
                Ok(TypeCompiled::type_any_of_two(t1, t2, heap))
            }
            _ => {
                // A union type, can match any
                let ts = t[..].try_map(|t| TypeCompiled::new(*t, heap))?;
                Ok(TypeCompiled::type_any_of(ts, heap))
            }
        }
    }

    fn from_dict(t: DictRef<'v>, heap: &'v Heap) -> anyhow::Result<TypeCompiled<Value<'v>>> {
        // Dictionary with a single element
        fn unpack_singleton_dictionary<'v>(x: &Dict<'v>) -> Option<(Value<'v>, Value<'v>)> {
            if x.len() == 1 { x.iter().next() } else { None }
        }

        if let Some((tk, tv)) = unpack_singleton_dictionary(&t) {
            if TypeCompiled::is_wildcard_value(tk) && TypeCompiled::is_wildcard_value(tv) {
                Ok(TypeCompiled::type_dict())
            } else {
                // Dict of the form {k: v} must all match the k/v types
                let tk = TypeCompiled::new(tk, heap)?;
                let tv = TypeCompiled::new(tv, heap)?;
                Ok(TypeCompiled::type_dict_of(tk, tv, heap))
            }
        } else {
            // Dict type with zero or multiple fields is not allowed
            Err(TypingError::InvalidTypeAnnotation(t.to_string()).into())
        }
    }

    pub(crate) fn from_ty(ty: &Ty, heap: &'v Heap) -> Self {
        match ty {
            Ty::Any => TypeCompiled::type_anything(),
            Ty::Union(xs) => {
                let xs = xs.alternatives().map(|x| TypeCompiled::from_ty(x, heap));
                TypeCompiled::type_any_of(xs, heap)
            }
            Ty::Name(name) => TypeCompiled::from_str(name.as_str(), heap),
            Ty::List(item) => {
                let item = TypeCompiled::from_ty(item, heap);
                TypeCompiled::type_list_of(item, heap)
            }
            Ty::Tuple(xs) => {
                let xs = xs.map(|x| TypeCompiled::from_ty(x, heap));
                TypeCompiled::type_tuple_of(xs, heap)
            }
            Ty::Dict(k_v) => {
                let (k, v) = &**k_v;
                let k = TypeCompiled::from_ty(k, heap);
                let v = TypeCompiled::from_ty(v, heap);
                TypeCompiled::type_dict_of(k, v, heap)
            }
            Ty::Never | Ty::Iter(_) | Ty::Custom(_) => {
                // There are no runtime matchers for these types.
                TypeCompiled::ty_other(ty.clone(), heap)
            }
        }
    }

    pub(crate) fn new(ty: Value<'v>, heap: &'v Heap) -> anyhow::Result<Self> {
        if let Some(s) = ty.unpack_str() {
            Ok(TypeCompiled::from_str(s, heap))
        } else if ty.is_none() {
            Ok(TypeCompiled::type_none())
        } else if let Some(t) = Tuple::from_value(ty) {
            TypeCompiled::from_tuple(t, heap)
        } else if let Some(t) = ListRef::from_value(ty) {
            TypeCompiled::from_list(t, heap)
        } else if let Some(t) = DictRef::from_value(ty) {
            TypeCompiled::from_dict(t, heap)
        } else if ty.request_value::<&dyn TypeCompiledImpl>().is_some() {
            Ok(TypeCompiled(ty))
        } else if let Some(ty) = ty.get_ref().eval_type() {
            Ok(TypeCompiled::from_ty(&ty, heap))
        } else {
            Err(invalid_type_annotation(ty, heap).into())
        }
    }
}

fn invalid_type_annotation<'v>(ty: Value<'v>, heap: &'v Heap) -> TypingError {
    if let Some(name) = ty
        .get_attr("type", heap)
        .ok()
        .flatten()
        .and_then(|v| v.unpack_str())
    {
        TypingError::PerhapsYouMeant(ty.to_str(), name.into())
    } else {
        TypingError::InvalidTypeAnnotation(ty.to_str())
    }
}

#[cfg(test)]
mod tests {
    use crate::assert;
    use crate::values::typing::TypeCompiled;
    use crate::values::Heap;

    #[test]
    fn test_types() {
        let a = assert::Assert::new();
        a.is_true(
            r#"
def f(i: int.type) -> bool.type:
    return i == 3
f(8) == False"#,
        );

        // If the types are either malformed or runtime errors, it should fail
        a.fail("def f(i: made_up):\n pass", "Variable");
        a.fail(
            "def f(i: fail('bad')):\n pass",
            "call expression is not allowed in type expression",
        );

        // Type errors should be caught in arguments
        a.fail(
            "def f_runtime(i: bool.type):\n pass\nf_runtime(noop(1))",
            "Value `1` of type `int` does not match the type annotation `bool.type` for argument `i`",
        );
        a.fail(
            "def f_compile_time(i: bool.type):\n pass\nf_compile_time(1)",
            "Expected type `bool.type` but got `int.type`",
        );
        a.pass(
            r#"Foo = record(value=int.type)
def f(v: bool.type) -> Foo:
    return Foo(value=1)"#,
        );
        a.fails(
            r#"Bar = enum("bar")
def f(v: Bar):
  pass"#,
            &[r#"enum("bar")"#],
        );
        // Type errors should be caught in return positions
        a.fails(
            "def f_return_runtime() -> bool.type:\n return noop(1)\nf_return_runtime()",
            &["type annotation", "`1`", "bool.type", "`int`", "return"],
        );
        a.fail(
            "def f_return_compile_time() -> bool.type:\n return 1\nf_return_compile_time()",
            "Expected type `bool.type` but got `int.type`",
        );
        // And for functions without return
        // TODO(nga): should be compile-time error.
        a.fails(
            "def f_bool_none() -> bool.type:\n pass\nf_bool_none()",
            &["type annotation", "`None`", "bool.type", "return"],
        );
        // And for functions that return None implicitly or explicitly
        a.fails(
            "def f_none_bool_runtime() -> None:\n return noop(True)\nf_none_bool_runtime()",
            &["type annotation", "`None`", "`bool`", "return"],
        );
        a.fail(
            "def f_none_bool_compile_time() -> None:\n return True\nf_none_bool_compile_time()",
            "Expected type `None` but got `bool.type`",
        );
        a.pass("def f() -> None:\n pass\nf()");

        // The following are all valid types
        a.all_true(
            r#"
is_type(1, int.type)
is_type(True, bool.type)
is_type(True, "")
is_type(None, None)
is_type(assert_type, "function")
is_type([], [int.type])
is_type([], [""])
is_type([1, 2, 3], [int.type])
is_type(None, [None, int.type])
is_type('test', [int.type, str.type])
is_type(('test', None), (str.type, None))
is_type({"test": 1, "more": 2}, {str.type: int.type})
is_type({1: 1, 2: 2}, {int.type: int.type})

not is_type(1, None)
not is_type((1, 1), str.type)
not is_type('test', [int.type, bool.type])
not is_type([1,2,None], [int.type])
not is_type({"test": 1, 8: 2}, {str.type: int.type})
not is_type({"test": 1, "more": None}, {str.type: int.type})

is_type(1, "")
is_type([1,2,"test"], ["_a"])
"#,
        );

        // Checking types fails for invalid types
        a.fail("is_type(None, is_type)", "not a valid type");
        a.fail("is_type(None, [])", "not a valid type");
        a.fail("is_type(None, {'1': '', '2': ''})", "not a valid type");
        a.fail("is_type({}, {1: 'string', 2: 'bool'})", "not a valid type");

        // Should check the type of default parameters that aren't used
        a.fail(
            r#"
def foo(f: int.type = None):
    pass
"#,
            "`None` of type `NoneType` does not match the type annotation `int.type`",
        );
    }

    #[ignore] // TODO(nga): depends on D46848632.
    #[test]
    fn test_new_syntax_without_dot_type_compile_time() {
        assert::pass(r"def f() -> int: return 17");
        assert::fail(
            r#"
def f() -> int: return 'tea'
"#,
            "Expected type `int.type` but got `str.type`",
        );
    }

    #[test]
    fn test_new_syntax_without_dot_type_runtime() {
        assert::pass(
            r#"
def f() -> str: return noop('coke')
f()
"#,
        );
        assert::fail(
            r#"
def f() -> str: return noop(19)
f()
"#,
            "Value `19` of type `int`",
        );
    }

    #[test]
    fn test_type_compiled_display() {
        fn t(expected: &str, ty0: &str) {
            let ty = assert::pass(ty0);
            let heap = Heap::new();
            let ty = unsafe { ty.unchecked_frozen_value() }.to_value();
            let ty = TypeCompiled::new(ty, &heap).unwrap();
            assert_eq!(expected, ty.to_string(), "for `{}`", ty0);
        }

        t("\"\"", "\"\"");
        t("[\"\"]", "list.type");
        t("[\"\"]", "[\"\"]");
        t("None", "None");
        t("[\"a\", \"b\"]", "[\"a\", \"b\"]");
    }

    #[test]
    fn test_type_compiled_starlark_api() {
        assert::eq("\"eval_type(int.type)\"", "repr(eval_type(int.type))");
        assert::is_true("eval_type(int.type).matches(1)");
        assert::is_true("not eval_type(int.type).matches([])");
        assert::pass("eval_type(int.type).check_matches(1)");
        assert::fail(
            "eval_type(int.type).check_matches([])",
            "Value of type `list` does not match type `int.type`: []",
        );
    }

    #[test]
    fn test_type_compiled_can_be_used_in_function_signature() {
        assert::pass(
            r#"
ty = eval_type(int.type)
def f(x: ty):
    pass

f(1)
"#,
        );
        assert::fail(
            r#"
ty = eval_type(int.type)
def f(x: ty):
    pass

# Runtime error.
f(noop("x"))
"#,
            "Value `x` of type `string` does not match the type annotation `int.type` for argument `x`",
        );
        assert::fail(
            r#"
ty = eval_type(int.type)
def f(x: ty):
    pass

# Compile-time error.
f("x")
"#,
            "Expected type `int.type` but got `str.type`",
        );
    }

    #[test]
    fn test_new_list_dict_syntax_pass() {
        assert::pass(
            r#"
def uuu(x: list[int]):
    pass

uuu([1, 2, 3])
"#,
        );
    }

    #[test]
    fn test_new_list_dict_syntax_fail_compile_time() {
        assert::fail(
            r#"
def uuu(x: list[int]):
    pass

uuu(["mm"])
"#,
            "Expected type `[int.type]` but got `[str.type]`",
        );
    }

    #[test]
    fn test_new_list_dict_syntax_fail_runtime() {
        assert::fail(
            r#"
def uuu(x: list[int]):
    pass

noop(uuu)(["mm"])
"#,
            r#"Value `["mm"]` of type `list` does not match"#,
        );
    }
}


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

//! Parameter conversion utilities for `starlark_module` macros.

use std::ops::Deref;

use dupe::Dupe;
use either::Either;

use crate::typing::Ty;
use crate::values::list::ListRef;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::types::tuple::value::Tuple;
use crate::values::AllocValue;
use crate::values::Heap;
use crate::values::Value;
use crate::values::ValueError;

/// How to convert a [`Value`] to a Rust type. Required for all arguments in
/// a [`#[starlark_module]`](macro@crate::starlark_module) definition.
///
/// Note for simple references it often can be implemented with `#[starlark_value(UnpackValue)]`,
/// for example:
///
/// ```
/// # use allocative::Allocative;
/// # use starlark::values::StarlarkValue;
/// # use starlark::any::ProvidesStaticType;
/// # use starlark::values::{NoSerialize, starlark_value};
///
/// #[derive(Debug, derive_more::Display, Allocative, NoSerialize, ProvidesStaticType)]
/// struct MySimpleValue;
///
/// #[starlark_value(type = "MySimpleValue", UnpackValue, StarlarkTypeRepr)]
/// impl<'v> StarlarkValue<'v> for MySimpleValue {}
/// ```
pub trait UnpackValue<'v>: Sized + StarlarkTypeRepr {
    /// Description of values acceptable by `unpack_value`, e. g. `list or str`.
    fn expected() -> String {
        Self::starlark_type_repr().to_string()
    }

    /// Given a [`Value`], try and unpack it into the given type, which may involve some element of conversion.
    fn unpack_value(value: Value<'v>) -> Option<Self>;

    /// Unpack a value, but return error instead of `None` if unpacking fails.
    fn unpack_value_err(value: Value<'v>) -> anyhow::Result<Self> {
        #[derive(thiserror::Error, Debug)]
        #[error("Expected `{0}`, but got `{1}`")]
        struct Error(String, &'static str);

        Self::unpack_value(value).ok_or_else(|| Error(Self::expected(), value.get_type()).into())
    }

    /// Unpack value, but instead of `None` return error about incorrect argument type.
    #[inline]
    fn unpack_param(value: Value<'v>) -> anyhow::Result<Self> {
        #[cold]
        fn error<'v, U: UnpackValue<'v>>(value: Value<'v>) -> anyhow::Error {
            ValueError::IncorrectParameterTypeWithExpected(
                U::expected(),
                value.get_type().to_owned(),
            )
            .into()
        }

        Self::unpack_value(value).ok_or_else(|| error::<Self>(value))
    }

    /// Unpack value, but instead of `None` return error about incorrect named argument type.
    #[inline]
    fn unpack_named_param(value: Value<'v>, param_name: &str) -> anyhow::Result<Self> {
        #[cold]
        fn error<'v, U: UnpackValue<'v>>(value: Value<'v>, param_name: &str) -> anyhow::Error {
            ValueError::IncorrectParameterTypeNamedWithExpected(
                param_name.to_owned(),
                U::expected(),
                value.get_type().to_owned(),
            )
            .into()
        }

        Self::unpack_value(value).ok_or_else(|| error::<Self>(value, param_name))
    }
}

impl<'v> UnpackValue<'v> for Value<'v> {
    fn expected() -> String {
        "Value".to_owned()
    }

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        Some(value)
    }
}

/// A wrapper that keeps the original value on the heap for use elsewhere,
/// and also, when unpacked, unpacks the value to validate it is of
/// the correct type. Has an [`UnpackValue`] instance, so often used as
/// an argument to [`#[starlark_module]`](macro@crate::starlark_module) defined
/// functions.
///
/// Two container specializations of this are [`ListOf`](crate::values::list::ListOf)
/// and [`DictOf`](crate::values::dict::DictOf), which
/// validate the types of their containers on unpack, but do not store the
/// resulting Vec/Map
#[derive(Debug, Copy, Clone, Dupe)]
pub struct ValueOf<'v, T: UnpackValue<'v>> {
    /// The original [`Value`] on the same heap.
    pub value: Value<'v>,
    /// The value that was unpacked.
    pub typed: T,
}

impl<'v, T: UnpackValue<'v>> Deref for ValueOf<'v, T> {
    type Target = Value<'v>;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl<'v, T: UnpackValue<'v>> StarlarkTypeRepr for ValueOf<'v, T> {
    fn starlark_type_repr() -> Ty {
        T::starlark_type_repr()
    }
}

impl<'v, T: UnpackValue<'v>> UnpackValue<'v> for ValueOf<'v, T> {
    fn expected() -> String {
        T::expected()
    }

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        let typed = T::unpack_value(value)?;
        Some(Self { value, typed })
    }
}

impl<'v, T: UnpackValue<'v>> AllocValue<'v> for ValueOf<'v, T> {
    fn alloc_value(self, _heap: &'v Heap) -> Value<'v> {
        self.value
    }
}

impl<'v, TLeft: UnpackValue<'v>, TRight: UnpackValue<'v>> UnpackValue<'v>
    for Either<TLeft, TRight>
{
    fn expected() -> String {
        format!("either {} or {}", TLeft::expected(), TRight::expected())
    }

    // Only implemented for types that implement [`UnpackValue`]. Nonsensical for other types.
    fn unpack_value(value: Value<'v>) -> Option<Self> {
        if let Some(left) = TLeft::unpack_value(value) {
            Some(Self::Left(left))
        } else {
            TRight::unpack_value(value).map(Self::Right)
        }
    }
}

impl<'v, T: UnpackValue<'v>> UnpackValue<'v> for Vec<T> {
    fn expected() -> String {
        format!("list or tuple of {}", T::expected())
    }

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        if let Some(o) = ListRef::from_value(value) {
            o.iter().map(T::unpack_value).collect::<Option<Vec<_>>>()
        } else if let Some(o) = Tuple::from_value(value) {
            o.iter().map(T::unpack_value).collect::<Option<Vec<_>>>()
        } else {
            None
        }
    }
}


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

use std::fmt::Debug;
use std::fmt::Formatter;
use std::marker::PhantomData;

use allocative::Allocative;
use dupe::Clone_;
use dupe::Copy_;
use dupe::Dupe_;

use crate::typing::Ty;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::AllocValue;
use crate::values::Heap;
use crate::values::Trace;
use crate::values::Tracer;
use crate::values::UnpackValue;
use crate::values::Value;

/// Starlark value with type annotation.
///
/// Can be used in function signatures to provide types to the type checker.
///
/// Note this type does not actually check the type of the value.
/// Providing incorrect type annotation will result
/// in incorrect error reporting by the type checker.
#[derive(Clone_, Copy_, Dupe_, Allocative)]
#[allocative(bound = "")]
pub struct ValueOfUnchecked<'v, T: StarlarkTypeRepr>(Value<'v>, PhantomData<T>);

impl<'v, T: StarlarkTypeRepr> Debug for ValueOfUnchecked<'v, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("ValueOfUnchecked").field(&self.0).finish()
    }
}

impl<'v, T: StarlarkTypeRepr> ValueOfUnchecked<'v, T> {
    /// New.
    #[inline]
    pub fn new(value: Value<'v>) -> Self {
        Self(value, PhantomData)
    }

    /// Construct after checking the type.
    #[inline]
    pub fn new_checked(value: Value<'v>) -> anyhow::Result<Self>
    where
        T: UnpackValue<'v>,
    {
        T::unpack_value_err(value)?;
        Ok(Self::new(value))
    }

    /// Get the value.
    #[inline]
    pub fn get(self) -> Value<'v> {
        self.0
    }
}

impl<'v, T: StarlarkTypeRepr> StarlarkTypeRepr for ValueOfUnchecked<'v, T> {
    fn starlark_type_repr() -> Ty {
        T::starlark_type_repr()
    }
}

impl<'v, T: StarlarkTypeRepr> AllocValue<'v> for ValueOfUnchecked<'v, T> {
    #[inline]
    fn alloc_value(self, _heap: &'v Heap) -> Value<'v> {
        self.0
    }
}

impl<'v, T: StarlarkTypeRepr> UnpackValue<'v> for ValueOfUnchecked<'v, T> {
    #[inline]
    fn unpack_value(value: Value<'v>) -> Option<Self> {
        Some(Self::new(value))
    }
}

unsafe impl<'v, T: StarlarkTypeRepr> Trace<'v> for ValueOfUnchecked<'v, T> {
    fn trace(&mut self, tracer: &Tracer<'v>) {
        // TODO(nga): should derive, but to do that we need to implement `#[trace(bound = "")]`
        let ValueOfUnchecked(value, phantom) = self;
        value.trace(tracer);
        phantom.trace(tracer);
    }
}


/*
 * 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::alloc::Layout;
use std::mem;
use std::ops::Add;

use allocative::Allocative;
use dupe::Dupe;

use crate::values::layout::heap::repr::AValueHeader;

/// Allocations in Starlark are word-aligned, and this type represents the size of an allocation.
#[derive(
    Copy, Clone, Dupe, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Allocative
)]
#[repr(transparent)]
pub(crate) struct AlignedSize {
    /// Starlark only supports objects smaller than 1<<32.
    bytes: u32,
}

impl AlignedSize {
    const MAX_SIZE: AlignedSize =
        AlignedSize::new_bytes(u32::MAX as usize - AValueHeader::ALIGN + 1);

    #[inline]
    pub(crate) const fn new_bytes(bytes: usize) -> AlignedSize {
        assert!(bytes % AValueHeader::ALIGN == 0);
        assert!(bytes as u32 as usize == bytes);
        let bytes = bytes as u32;
        AlignedSize { bytes }
    }

    #[inline]
    pub(crate) const fn align_up(bytes: usize) -> AlignedSize {
        assert!(bytes <= AlignedSize::MAX_SIZE.bytes() as usize);
        let bytes = (bytes + AValueHeader::ALIGN - 1) & !(AValueHeader::ALIGN - 1);
        let bytes = bytes as u32;
        AlignedSize { bytes }
    }

    #[inline]
    pub(crate) const fn bytes(self) -> u32 {
        self.bytes
    }

    #[inline]
    pub(crate) const fn of<T>() -> AlignedSize {
        AlignedSize::align_up(mem::size_of::<T>())
    }

    #[inline]
    pub(crate) const fn layout(self) -> Layout {
        match Layout::from_size_align(self.bytes as usize, AValueHeader::ALIGN) {
            Ok(layout) => layout,
            Err(_) => panic!("Layout::from_size_align failed"),
        }
    }
}

impl Add for AlignedSize {
    type Output = AlignedSize;

    #[inline]
    fn add(self, rhs: AlignedSize) -> AlignedSize {
        AlignedSize::new_bytes(self.bytes.checked_add(rhs.bytes).unwrap() as usize)
    }
}


/*
 * 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::any::type_name;
use std::any::TypeId;
use std::cmp;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::mem;

use allocative::Allocative;
use derive_more::Display;
use serde::Serialize;
use serde::Serializer;
use starlark_map::small_map::SmallMap;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::cast::transmute;
use crate::collections::maybe_uninit_backport::maybe_uninit_write_slice;
use crate::collections::StarlarkHashValue;
use crate::eval::compiler::def::FrozenDef;
use crate::private::Private;
use crate::slice_vec_ext::SliceExt;
use crate::values::bool::StarlarkBool;
use crate::values::dict::value::DictGen;
use crate::values::dict::value::FrozenDictData;
use crate::values::layout::aligned_size::AlignedSize;
use crate::values::layout::heap::arena::MIN_ALLOC;
use crate::values::layout::heap::repr::AValueForward;
use crate::values::layout::heap::repr::AValueHeader;
use crate::values::layout::heap::repr::AValueRepr;
use crate::values::layout::heap::repr::ForwardPtr;
use crate::values::layout::value_alloc_size::ValueAllocSize;
use crate::values::layout::vtable::AValueVTable;
use crate::values::list::value::ListGen;
use crate::values::none::NoneType;
use crate::values::string::StarlarkStr;
use crate::values::types::any_array::AnyArray;
use crate::values::types::array::Array;
use crate::values::types::list::value::FrozenListData;
use crate::values::types::list::value::ListData;
use crate::values::types::tuple::value::FrozenTuple;
use crate::values::types::tuple::value::Tuple;
use crate::values::ComplexValue;
use crate::values::Freezer;
use crate::values::FrozenValue;
use crate::values::StarlarkValue;
use crate::values::Trace;
use crate::values::Tracer;
use crate::values::Value;
use crate::values::ValueTyped;

pub(crate) const fn alloc_static<M, T>(mode: M, value: T) -> AValueRepr<AValueImpl<M, T>>
where
    M: AValueMode,
    AValueImpl<M, T>: AValue<'static>,
{
    mem::forget(mode);
    let payload = AValueImpl::<M, _>::new(value);
    AValueRepr::with_metadata(AValueVTable::new::<AValueImpl<M, T>>(), payload)
}

pub(crate) static VALUE_NONE: AValueRepr<AValueImpl<Basic, NoneType>> =
    alloc_static(Basic, NoneType);

pub(crate) static VALUE_FALSE: AValueRepr<AValueImpl<Basic, StarlarkBool>> =
    alloc_static(Basic, StarlarkBool(false));
pub(crate) static VALUE_TRUE: AValueRepr<AValueImpl<Basic, StarlarkBool>> =
    alloc_static(Basic, StarlarkBool(true));

pub(crate) const VALUE_STR_A_VALUE_PTR: AValueHeader =
    AValueHeader::new_const::<StarlarkStrAValue>();

pub(crate) static VALUE_EMPTY_TUPLE: AValueRepr<AValueImpl<Direct, FrozenTuple>> =
    alloc_static(Direct, unsafe { FrozenTuple::new(0) });

pub(crate) static VALUE_EMPTY_FROZEN_LIST: AValueRepr<AValueImpl<Direct, ListGen<FrozenListData>>> =
    alloc_static(Direct, unsafe { ListGen(FrozenListData::new(0)) });

pub(crate) static VALUE_EMPTY_FROZEN_DICT: AValueRepr<AValueImpl<Simple, DictGen<FrozenDictData>>> =
    alloc_static(
        Simple,
        DictGen(FrozenDictData {
            content: SmallMap::new(),
        }),
    );

/// `Array` is not `Sync`, so wrap it into this struct to store it in static variable.
/// Empty `Array` is logically `Sync`.
pub(crate) struct ValueEmptyArray(AValueRepr<AValueImpl<Direct, Array<'static>>>);
unsafe impl Sync for ValueEmptyArray {}

pub(crate) static VALUE_EMPTY_ARRAY: ValueEmptyArray =
    ValueEmptyArray(alloc_static(Direct, unsafe { Array::new(0, 0) }));

impl ValueEmptyArray {
    pub(crate) fn repr<'v>(
        &'static self,
    ) -> &'v AValueRepr<impl AValue<'v, StarlarkValue = Array<'v>>> {
        // Cast lifetimes. Cannot use `crate::cast::ptr_lifetime` here
        // because type parameter of `AValue` also need to be casted.
        unsafe {
            transmute!(
                &AValueRepr<AValueImpl<Direct, Array>>,
                &AValueRepr<AValueImpl<Direct, Array>>,
                &self.0
            )
        }
    }
}

#[derive(Debug, thiserror::Error)]
enum AValueError {
    #[error("Value of type `{0}` cannot be frozen")]
    CannotBeFrozen(&'static str),
}

/// Sized counterpart of [`AValueDyn`].
pub(crate) trait AValue<'v>: Sized + 'v {
    /// Unwrapped type.
    type StarlarkValue: StarlarkValue<'v>;

    /// Certain types like `Tuple` or `StarlarkStr` have payload array
    /// placed in a heap after `Self`. This is the type of an element of that array.
    type ExtraElem: 'v;

    /// Payload array length.
    fn extra_len(&self) -> usize;

    /// Offset of field holding content, in bytes.
    ///
    /// Return `mem::size_of::<Self>()` if there's no extra content.
    fn offset_of_extra() -> usize;

    /// Type is `StarlarkStr`.
    const IS_STR: bool = false;

    /// Memory size of starlark value including `AValueHeader`.
    fn alloc_size_for_extra_len(extra_len: usize) -> ValueAllocSize {
        assert!(
            Self::offset_of_extra() % mem::align_of::<Self::ExtraElem>() == 0,
            "extra must be aligned"
        );
        ValueAllocSize::new(cmp::max(
            cmp::max(
                AlignedSize::of::<AValueRepr<Self::StarlarkValue>>(),
                MIN_ALLOC,
            ),
            // Content is not necessarily aligned to end of `A`.
            AlignedSize::align_up(
                AValueRepr::<Self>::offset_of_extra()
                    + (mem::size_of::<Self::ExtraElem>() * extra_len),
            ),
        ))
    }

    unsafe fn heap_freeze(
        me: *mut AValueRepr<Self>,
        freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue>;

    unsafe fn heap_copy(me: *mut AValueRepr<Self>, tracer: &Tracer<'v>) -> Value<'v>;
}

#[inline]
pub(crate) fn starlark_str<'v>(
    len: usize,
    hash: StarlarkHashValue,
) -> impl AValue<'v, ExtraElem = usize> + Send + Sync {
    AValueImpl::<Direct, _>::new(unsafe { StarlarkStr::new(len, hash) })
}

pub(crate) fn tuple_avalue<'v>(len: usize) -> impl AValue<'v, ExtraElem = Value<'v>> {
    AValueImpl::<Direct, _>::new(unsafe { Tuple::new(len) })
}

pub(crate) fn frozen_tuple_avalue(len: usize) -> impl AValue<'static, ExtraElem = FrozenValue> {
    AValueImpl::<Direct, _>::new(unsafe { FrozenTuple::new(len) })
}

pub(crate) fn list_avalue<'v>(
    content: ValueTyped<'v, Array<'v>>,
) -> impl AValue<'v, StarlarkValue = ListGen<ListData<'v>>, ExtraElem = ()> {
    AValueImpl::<Direct, _>::new(ListGen(ListData::new(content)))
}

pub(crate) fn frozen_list_avalue(len: usize) -> impl AValue<'static, ExtraElem = FrozenValue> {
    AValueImpl::<Direct, _>::new(unsafe { ListGen(FrozenListData::new(len)) })
}

pub(crate) fn array_avalue<'v>(
    cap: u32,
) -> impl AValue<'v, StarlarkValue = Array<'v>, ExtraElem = Value<'v>> {
    AValueImpl::<Direct, _>::new(unsafe { Array::new(0, cap) })
}

pub(crate) fn any_array_avalue<T: Debug + 'static>(
    cap: usize,
) -> impl AValue<'static, StarlarkValue = AnyArray<T>, ExtraElem = T> {
    AValueImpl::<Direct, _>::new(unsafe { AnyArray::new(cap) })
}

pub(crate) fn simple<T: StarlarkValue<'static> + Send + Sync>(
    x: T,
) -> impl AValue<'static, ExtraElem = ()> + Send + Sync {
    assert!(!T::is_special(Private));
    AValueImpl::<Simple, _>::new(x)
}

pub(crate) fn complex<'v, C>(x: C) -> impl AValue<'v, ExtraElem = ()>
where
    C: ComplexValue<'v>,
    C::Frozen: StarlarkValue<'static>,
{
    assert!(!C::is_special(Private));
    AValueImpl::<Complex, _>::new(x)
}

pub(crate) fn complex_no_freeze<'v, C>(x: C) -> impl AValue<'v, ExtraElem = ()>
where
    C: StarlarkValue<'v> + Trace<'v>,
{
    assert!(!C::is_special(Private));
    AValueImpl::<ComplexNoFreeze, _>::new(x)
}

pub(crate) trait AValueMode: Send + Sync + 'static {}

// A type where the second element is in control of what instances are in scope
pub(crate) struct Direct;
impl AValueMode for Direct {}

// A type that implements StarlarkValue but nothing else, so will never be stored
// in the heap (e.g. bool, None)
pub(crate) struct Basic;
impl AValueMode for Basic {}

// A non-special type with no references to other Starlark values.
pub(crate) struct Simple;
impl AValueMode for Simple {}

// A type that implements ComplexValue.
pub(crate) struct Complex;
impl AValueMode for Complex {}

// A value which can be traced, but cannot be frozen.
pub(crate) struct ComplexNoFreeze;
impl AValueMode for ComplexNoFreeze {}

// We want to define several types (Simple, Complex) that wrap a StarlarkValue,
// reimplement it, and do some things custom. The easiest way to avoid repeating
// the StarlarkValue trait each time is to make them all share a single wrapper,
// where Mode is one of Simple/Complex.
#[repr(C)]
pub(crate) struct AValueImpl<Mode: AValueMode, T>(PhantomData<Mode>, pub(crate) T);

impl<Mode: AValueMode, T> AValueImpl<Mode, T> {
    pub(crate) const fn new(value: T) -> Self {
        AValueImpl(PhantomData, value)
    }
}

/// The overwrite operation in the heap requires that the LSB not be set.
/// For FrozenValue this is the case, but for Value the LSB is always set.
/// Fortunately, the consumer of the overwritten value reapplies the
/// FrozenValue/Value tags, so we can freely discard it here.
fn clear_lsb(x: usize) -> usize {
    x & !1
}

impl<'v, T: StarlarkValue<'v>> AValue<'v> for AValueImpl<Basic, T> {
    type StarlarkValue = T;

    type ExtraElem = ();

    fn extra_len(&self) -> usize {
        0
    }

    fn offset_of_extra() -> usize {
        mem::size_of::<Self>()
    }

    unsafe fn heap_freeze(
        _me: *mut AValueRepr<Self>,
        _freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        unreachable!("Basic types don't appear in the heap")
    }
    unsafe fn heap_copy(_me: *mut AValueRepr<Self>, _tracer: &Tracer<'v>) -> Value<'v> {
        unreachable!("Basic types don't appear in the heap")
    }
}

pub(crate) type StarlarkStrAValue = AValueImpl<Direct, StarlarkStr>;

impl<'v> AValue<'v> for AValueImpl<Direct, StarlarkStr> {
    type StarlarkValue = StarlarkStr;

    type ExtraElem = usize;

    fn extra_len(&self) -> usize {
        StarlarkStr::payload_len_for_len(self.1.len())
    }

    fn offset_of_extra() -> usize {
        StarlarkStr::offset_of_content()
    }

    const IS_STR: bool = true;

    unsafe fn heap_freeze(
        me: *mut AValueRepr<Self>,
        freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        debug_assert!(
            (*me).payload.1.len() > 1,
            "short strings are allocated statically"
        );

        let s = (*me).payload.1.as_str();
        let fv = freezer.alloc(s);
        debug_assert!(fv.is_str());
        AValueHeader::overwrite_with_forward::<Self>(me, ForwardPtr::new(fv.0.raw().ptr_value()));
        Ok(fv)
    }

    unsafe fn heap_copy(me: *mut AValueRepr<Self>, tracer: &Tracer<'v>) -> Value<'v> {
        debug_assert!(
            (*me).payload.1.len() > 1,
            "short strings are allocated statically"
        );

        let s = (*me).payload.1.as_str();
        let v = tracer.alloc_str(s);
        debug_assert!(v.is_str());
        AValueHeader::overwrite_with_forward::<Self>(
            me,
            ForwardPtr::new(v.0.raw().ptr_value() & !1),
        );
        v
    }
}

impl<'v> AValue<'v> for AValueImpl<Direct, Tuple<'v>> {
    type StarlarkValue = Tuple<'v>;

    type ExtraElem = Value<'v>;

    fn extra_len(&self) -> usize {
        self.1.len()
    }

    fn offset_of_extra() -> usize {
        Tuple::offset_of_content()
    }

    unsafe fn heap_freeze(
        me: *mut AValueRepr<Self>,
        freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        debug_assert!(
            (*me).payload.1.len() != 0,
            "empty tuple is allocated statically"
        );

        AValueForward::assert_does_not_overwrite_extra::<Self>();
        let content = (*me).payload.1.content();

        let (fv, r, extra) =
            freezer.reserve_with_extra::<AValueImpl<Direct, FrozenTuple>>(content.len());
        AValueHeader::overwrite_with_forward::<Self>(me, ForwardPtr::new(fv.0.raw().ptr_value()));

        // TODO: this allocation is unnecessary
        let frozen_values = content.try_map(|v| freezer.freeze(*v))?;
        r.fill(AValueImpl(
            PhantomData::<Direct>,
            FrozenTuple::new(content.len()),
        ));
        maybe_uninit_write_slice(extra, &frozen_values);

        Ok(fv)
    }

    unsafe fn heap_copy(me: *mut AValueRepr<Self>, tracer: &Tracer<'v>) -> Value<'v> {
        debug_assert!(
            (*me).payload.1.len() != 0,
            "empty tuple is allocated statically"
        );

        AValueForward::assert_does_not_overwrite_extra::<Self>();
        let content = (*me).payload.1.content_mut();

        let (v, r, extra) = tracer.reserve_with_extra::<Self>(content.len());
        let x = AValueHeader::overwrite_with_forward::<Self>(
            me,
            ForwardPtr::new(clear_lsb(v.0.raw().ptr_value())),
        );

        debug_assert_eq!(content.len(), x.1.len());

        for elem in content.iter_mut() {
            tracer.trace(elem);
        }
        r.fill(x);
        maybe_uninit_write_slice(extra, content);
        v
    }
}

impl<'v> AValue<'v> for AValueImpl<Direct, FrozenTuple> {
    type StarlarkValue = FrozenTuple;

    type ExtraElem = FrozenValue;

    fn extra_len(&self) -> usize {
        self.1.len()
    }

    fn offset_of_extra() -> usize {
        FrozenTuple::offset_of_content()
    }

    unsafe fn heap_freeze(
        _me: *mut AValueRepr<Self>,
        _freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        panic!("already frozen");
    }

    unsafe fn heap_copy(_me: *mut AValueRepr<Self>, _tracer: &Tracer<'v>) -> Value<'v> {
        panic!("shouldn't be copying frozen values");
    }
}

impl<'v> AValue<'v> for AValueImpl<Direct, ListGen<ListData<'v>>> {
    type StarlarkValue = ListGen<ListData<'v>>;

    type ExtraElem = ();

    fn extra_len(&self) -> usize {
        0
    }

    fn offset_of_extra() -> usize {
        mem::size_of::<Self>()
    }

    unsafe fn heap_freeze(
        me: *mut AValueRepr<Self>,
        freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        let content = (*me).payload.1.0.content();

        if content.is_empty() {
            let fv = FrozenValue::new_repr(&VALUE_EMPTY_FROZEN_LIST);
            AValueHeader::overwrite_with_forward::<Self>(
                me,
                ForwardPtr::new(fv.0.raw().ptr_value()),
            );
            return Ok(fv);
        }

        let (fv, r, extra) = freezer
            .reserve_with_extra::<AValueImpl<Direct, ListGen<FrozenListData>>>(content.len());
        AValueHeader::overwrite_with_forward::<Self>(me, ForwardPtr::new(fv.0.raw().ptr_value()));
        r.fill(AValueImpl::<Direct, _>::new(ListGen(FrozenListData::new(
            content.len(),
        ))));
        assert_eq!(extra.len(), content.len());
        for (elem_place, elem) in extra.iter_mut().zip(content) {
            elem_place.write(freezer.freeze(*elem)?);
        }
        Ok(fv)
    }

    unsafe fn heap_copy(me: *mut AValueRepr<Self>, tracer: &Tracer<'v>) -> Value<'v> {
        Self::heap_copy_impl(me, tracer, Trace::trace)
    }
}

impl<'v> AValue<'v> for AValueImpl<Direct, ListGen<FrozenListData>> {
    type StarlarkValue = ListGen<FrozenListData>;

    type ExtraElem = FrozenValue;

    fn extra_len(&self) -> usize {
        self.1.0.len()
    }

    fn offset_of_extra() -> usize {
        ListGen::<FrozenListData>::offset_of_content()
    }

    unsafe fn heap_freeze(
        _me: *mut AValueRepr<Self>,
        _freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        panic!("already frozen");
    }

    unsafe fn heap_copy(_me: *mut AValueRepr<Self>, _tracer: &Tracer<'v>) -> Value<'v> {
        panic!("shouldn't be copying frozen values");
    }
}

impl<'v> AValue<'v> for AValueImpl<Direct, Array<'v>> {
    type StarlarkValue = Array<'v>;

    type ExtraElem = Value<'v>;

    fn extra_len(&self) -> usize {
        // Note we return capacity, not length here.
        self.1.capacity()
    }

    fn offset_of_extra() -> usize {
        Array::offset_of_content()
    }

    unsafe fn heap_freeze(
        _me: *mut AValueRepr<Self>,
        _freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        panic!("arrays should not be frozen")
    }

    unsafe fn heap_copy(me: *mut AValueRepr<Self>, tracer: &Tracer<'v>) -> Value<'v> {
        debug_assert!(
            (*me).payload.1.capacity() != 0,
            "empty array is allocated statically"
        );

        if (*me).payload.1.len() == 0 {
            return FrozenValue::new_repr(&VALUE_EMPTY_ARRAY.0).to_value();
        }

        AValueForward::assert_does_not_overwrite_extra::<Self>();
        let content = (*me).payload.1.content_mut();

        let (v, r, extra) = tracer.reserve_with_extra::<Self>(content.len());
        let x = AValueHeader::overwrite_with_forward::<Self>(
            me,
            ForwardPtr::new(clear_lsb(v.0.raw().ptr_value())),
        );

        debug_assert_eq!(content.len(), x.1.len());

        content.trace(tracer);

        // Note when copying we are dropping extra capacity.
        r.fill(AValueImpl::<Direct, _>::new(Array::new(
            content.len() as u32,
            content.len() as u32,
        )));
        maybe_uninit_write_slice(extra, content);
        v
    }
}

impl<'v, T: Debug + 'static> AValue<'v> for AValueImpl<Direct, AnyArray<T>> {
    type StarlarkValue = AnyArray<T>;
    type ExtraElem = T;

    fn extra_len(&self) -> usize {
        self.1.len
    }

    fn offset_of_extra() -> usize {
        AnyArray::<T>::offset_of_content()
    }

    unsafe fn heap_freeze(
        _me: *mut AValueRepr<Self>,
        _freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        panic!("AnyArray for now can only be allocated in FrozenHeap");
    }

    unsafe fn heap_copy(_me: *mut AValueRepr<Self>, _tracer: &Tracer<'v>) -> Value<'v> {
        panic!("AnyArray for now can only be allocated in FrozenHeap");
    }
}

impl<Mode: AValueMode, C> AValueImpl<Mode, C> {
    /// `heap_freeze` implementation for simple `StarlarkValue` and `StarlarkFloat`
    /// (`StarlarkFloat` is logically a simple type, but it is not considered simple type).
    unsafe fn heap_freeze_simple_impl<'v>(
        me: *mut AValueRepr<Self>,
        freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue>
    where
        Self: AValue<'v, ExtraElem = ()>,
    {
        let (fv, r) = freezer.reserve::<Self>();
        let x = AValueHeader::overwrite_with_forward::<Self>(
            me,
            ForwardPtr::new(fv.0.raw().ptr_value()),
        );
        r.fill(x);
        Ok(fv)
    }
}

impl<T: StarlarkValue<'static>> AValue<'static> for AValueImpl<Simple, T> {
    type StarlarkValue = T;

    type ExtraElem = ();

    fn extra_len(&self) -> usize {
        0
    }

    fn offset_of_extra() -> usize {
        mem::size_of::<Self>()
    }

    unsafe fn heap_freeze(
        me: *mut AValueRepr<Self>,
        freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        Self::heap_freeze_simple_impl(me, freezer)
    }

    unsafe fn heap_copy(me: *mut AValueRepr<Self>, tracer: &Tracer<'static>) -> Value<'static> {
        Self::heap_copy_impl(me, tracer, |_v, _tracer| {})
    }
}

impl<Mode: AValueMode, C> AValueImpl<Mode, C> {
    /// Common `heap_copy` implementation for types without extra.
    unsafe fn heap_copy_impl<'v>(
        me: *mut AValueRepr<Self>,
        tracer: &Tracer<'v>,
        trace: impl FnOnce(&mut C, &Tracer<'v>),
    ) -> Value<'v>
    where
        Self: AValue<'v, ExtraElem = ()>,
    {
        let (v, r) = tracer.reserve::<Self>();
        let mut x = AValueHeader::overwrite_with_forward::<Self>(
            me,
            ForwardPtr::new(clear_lsb(v.0.raw().ptr_value())),
        );
        // We have to put the forwarding node in _before_ we trace in case there are cycles
        trace(&mut x.1, tracer);
        r.fill(x);
        v
    }
}

impl<'v, T> AValue<'v> for AValueImpl<Complex, T>
where
    T: ComplexValue<'v>,
    T::Frozen: StarlarkValue<'static>,
{
    type StarlarkValue = T;

    type ExtraElem = ();

    fn extra_len(&self) -> usize {
        0
    }

    fn offset_of_extra() -> usize {
        mem::size_of::<Self>()
    }

    unsafe fn heap_freeze(
        me: *mut AValueRepr<Self>,
        freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        let (fv, r) = freezer.reserve::<AValueImpl<Simple, T::Frozen>>();
        let x = AValueHeader::overwrite_with_forward::<Self>(
            me,
            ForwardPtr::new(fv.0.raw().ptr_value()),
        );
        let res = x.1.freeze(freezer)?;
        r.fill(AValueImpl::<Simple, _>::new(res));
        if TypeId::of::<T::Frozen>() == TypeId::of::<FrozenDef>() {
            let frozen_def = fv.downcast_frozen_ref().unwrap();
            freezer.frozen_defs.borrow_mut().push(frozen_def);
        }
        Ok(fv)
    }

    unsafe fn heap_copy(me: *mut AValueRepr<Self>, tracer: &Tracer<'v>) -> Value<'v> {
        Self::heap_copy_impl(me, tracer, Trace::trace)
    }
}

impl<'v, T> AValue<'v> for AValueImpl<ComplexNoFreeze, T>
where
    T: StarlarkValue<'v> + Trace<'v>,
{
    type StarlarkValue = T;

    type ExtraElem = ();

    fn extra_len(&self) -> usize {
        0
    }

    fn offset_of_extra() -> usize {
        mem::size_of::<Self>()
    }

    unsafe fn heap_freeze(
        _me: *mut AValueRepr<Self>,
        _freezer: &Freezer,
    ) -> anyhow::Result<FrozenValue> {
        Err(AValueError::CannotBeFrozen(type_name::<T>()).into())
    }

    unsafe fn heap_copy(me: *mut AValueRepr<Self>, tracer: &Tracer<'v>) -> Value<'v> {
        Self::heap_copy_impl(me, tracer, Trace::trace)
    }
}

#[derive(Debug, Display, ProvidesStaticType, Allocative)]
#[display(fmt = "BlackHole")]
pub(crate) struct BlackHole(pub(crate) ValueAllocSize);

impl Serialize for BlackHole {
    fn serialize<S>(&self, _s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        panic!()
    }
}

impl<'v, Mode: AValueMode, T: StarlarkValue<'v>> Serialize for AValueImpl<Mode, T> {
    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        erased_serde::serialize(&self.1, s)
    }
}

#[cfg(test)]
mod tests {
    use crate::environment::Module;
    use crate::values::types::list::value::ListData;

    #[test]
    fn tuple_cycle_freeze() {
        let module = Module::new();
        let list = module.heap().alloc_list(&[]);
        let tuple = module.heap().alloc_tuple(&[list]);
        ListData::from_value_mut(list)
            .unwrap()
            .push(tuple, module.heap());
        module.set("t", tuple);
        module.freeze().unwrap();
    }
}


/*
 * 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::marker::PhantomData;

use dupe::Clone_;
use dupe::Copy_;
use dupe::Dupe_;
use either::Either;

use crate::typing::Ty;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::AllocValue;
use crate::values::ComplexValue;
use crate::values::StarlarkValue;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueLike;

/// Value which is either a complex mutable value or a frozen value.
#[derive(Copy_, Clone_, Dupe_)]
pub struct ValueOfComplex<'v, T>(Value<'v>, PhantomData<T>)
where
    T: ComplexValue<'v>,
    T::Frozen: StarlarkValue<'static>;

impl<'v, T> ValueOfComplex<'v, T>
where
    T: ComplexValue<'v>,
    T::Frozen: StarlarkValue<'static>,
{
    /// Get the value back.
    #[inline]
    pub fn to_value(self) -> Value<'v> {
        self.0
    }

    /// Unpack the mutable or frozen value.
    #[inline]
    pub fn unpack(self) -> Either<&'v T, &'v T::Frozen> {
        if let Some(v) = self.0.downcast_ref::<T>() {
            Either::Left(v)
        } else if let Some(v) =
            unsafe { self.0.to_value().cast_lifetime() }.downcast_ref::<T::Frozen>()
        {
            Either::Right(v)
        } else {
            unreachable!("validated at construction")
        }
    }
}

impl<'v, T> StarlarkTypeRepr for ValueOfComplex<'v, T>
where
    T: ComplexValue<'v>,
    T::Frozen: StarlarkValue<'static>,
{
    fn starlark_type_repr() -> Ty {
        T::starlark_type_repr()
    }
}

impl<'v, T> AllocValue<'v> for ValueOfComplex<'v, T>
where
    T: ComplexValue<'v>,
    T::Frozen: StarlarkValue<'static>,
{
    #[inline]
    fn alloc_value(self, _heap: &'v crate::values::Heap) -> Value<'v> {
        self.0
    }
}

impl<'v, T> UnpackValue<'v> for ValueOfComplex<'v, T>
where
    T: ComplexValue<'v>,
    T::Frozen: StarlarkValue<'static>,
{
    fn unpack_value(value: Value<'v>) -> Option<Self> {
        if value.downcast_ref::<T>().is_some()
            || unsafe { value.cast_lifetime() }
                .downcast_ref::<T::Frozen>()
                .is_some()
        {
            Some(ValueOfComplex(value, PhantomData))
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests {
    use allocative::Allocative;
    use anyhow::Context;
    use either::Either;
    use starlark_derive::starlark_module;
    use starlark_derive::Freeze;
    use starlark_derive::NoSerialize;
    use starlark_derive::Trace;

    use crate as starlark;
    use crate::any::ProvidesStaticType;
    use crate::assert::Assert;
    use crate::const_frozen_string;
    use crate::environment::GlobalsBuilder;
    use crate::values::layout::complex::ValueOfComplex;
    use crate::values::starlark_value;
    use crate::values::StarlarkValue;
    use crate::values::Value;
    use crate::values::ValueLike;

    #[derive(
        Trace,
        Freeze,
        Debug,
        derive_more::Display,
        Allocative,
        ProvidesStaticType,
        NoSerialize
    )]
    struct TestValueOfComplex<V>(V);

    #[starlark_value(type = "test_value_of_complex")]
    impl<'v, V: ValueLike<'v>> StarlarkValue<'v> for TestValueOfComplex<V> where
        Self: ProvidesStaticType<'v>
    {
    }

    #[starlark_module]
    fn test_module(globals: &mut GlobalsBuilder) {
        fn test_unpack<'v>(
            v: ValueOfComplex<'v, TestValueOfComplex<Value<'v>>>,
        ) -> anyhow::Result<&'v str> {
            Ok(match v.unpack() {
                Either::Left(v) => v.0.unpack_str().context("not a string")?,
                Either::Right(v) => v.0.to_value().unpack_str().context("not a string")?,
            })
        }
    }

    #[test]
    fn test_unpack() {
        let mut a = Assert::new();
        a.globals_add(test_module);
        a.setup_eval(|eval| {
            let s = eval.heap().alloc("test1");
            let x = eval.heap().alloc_complex(TestValueOfComplex(s));
            let y = eval.frozen_heap().alloc_simple(TestValueOfComplex(
                const_frozen_string!("test2").to_frozen_value(),
            ));
            eval.module().set("x", x);
            eval.module().set("y", y.to_value());
        });
        a.eq("'test1'", "test_unpack(x)");
        a.eq("'test2'", "test_unpack(y)");
    }
}


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

/// Create a [`FrozenStringValue`](crate::values::FrozenStringValue).
#[macro_export]
macro_rules! const_frozen_string {
    ($s:expr) => {{
        $crate::values::constant_string($s).unwrap_or_else(|| {
            // `$s.len() <= 1`, `StarlarkStrNRepr::new` should not be called
            // because it fails and it should be handled by `constant_string`.
            // But we still have to put something in `static`.
            // so for `$s.len() <= 1` we put dummy string of length 2 there,
            // and `N == 1` in that case.
            const UNREACHABLE: bool = $s.len() <= 1;
            const N: usize = if UNREACHABLE {
                1
            } else {
                $crate::values::string::StarlarkStr::payload_len_for_len($s.len())
            };
            static X: $crate::values::StarlarkStrNRepr<N> =
                $crate::values::StarlarkStrNRepr::new(if UNREACHABLE { "xx" } else { $s });
            if UNREACHABLE {
                unreachable!()
            } else {
                X.erase()
            }
        })
    }};
}

#[cfg(test)]
mod tests {
    use crate::values::FrozenHeap;
    use crate::values::Heap;

    #[test]
    fn test_const_frozen_string_for_short_strings() {
        assert!(
            const_frozen_string!("a")
                .to_value()
                .ptr_eq(const_frozen_string!("a").to_value())
        );

        let heap = Heap::new();
        assert!(
            const_frozen_string!("a")
                .to_value()
                .ptr_eq(heap.alloc_str("a").to_value())
        );

        let frozen_heap = FrozenHeap::new();
        assert!(
            const_frozen_string!("a")
                .to_value()
                .ptr_eq(frozen_heap.alloc_str("a").to_value())
        );
    }

    #[test]
    fn test_const_frozen_string() {
        assert_eq!("", const_frozen_string!("").as_str());
        assert_eq!("a", const_frozen_string!("a").as_str());
        assert_eq!("ab", const_frozen_string!("ab").as_str());
        assert_eq!("abc", const_frozen_string!("abc").as_str());
        assert_eq!("abcd", const_frozen_string!("abcd").as_str());
        assert_eq!("abcde", const_frozen_string!("abcde").as_str());
        assert_eq!("abcdef", const_frozen_string!("abcdef").as_str());
        assert_eq!("abcdefg", const_frozen_string!("abcdefg").as_str());
        assert_eq!("abcdefgh", const_frozen_string!("abcdefgh").as_str());
        assert_eq!("abcdefghi", const_frozen_string!("abcdefghi").as_str());
        assert_eq!("abcdefghij", const_frozen_string!("abcdefghij").as_str());
        assert_eq!("abcdefghijk", const_frozen_string!("abcdefghijk").as_str());
    }
}


/*
 * 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::any::TypeId;

/// `TypeId` wrapper/provider until `const_type_id` feature is stabilized.
pub(crate) struct ConstTypeId {
    #[cfg(rust_nightly)]
    type_id: TypeId,
    #[cfg(not(rust_nightly))]
    type_id_fn: fn() -> TypeId,
}

impl ConstTypeId {
    pub(crate) const fn of<T: ?Sized + 'static>() -> ConstTypeId {
        ConstTypeId {
            #[cfg(rust_nightly)]
            type_id: TypeId::of::<T>(),
            #[cfg(not(rust_nightly))]
            type_id_fn: TypeId::of::<T>,
        }
    }

    #[inline]
    pub(crate) fn get(&self) -> TypeId {
        #[cfg(rust_nightly)]
        return self.type_id;
        #[cfg(not(rust_nightly))]
        return (self.type_id_fn)();
    }
}


/*
 * 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::marker::PhantomData;

use allocative::Allocative;
use dupe::Dupe;

use crate::values::layout::pointer::RawPointer;
use crate::values::Value;

/// An opaque value representing the identity of a given Value. Two values have the same identity
/// if and only if [`Value::ptr_eq`] would return [`true`] on them.
#[derive(Eq, PartialEq, Copy, Clone, Dupe, Hash, Debug, Allocative)]
pub struct ValueIdentity<'v> {
    identity: RawPointer,
    phantom: PhantomData<&'v ()>,
}

impl<'v> ValueIdentity<'v> {
    #[inline]
    pub(crate) fn new(value: Value<'v>) -> ValueIdentity<'v> {
        ValueIdentity {
            identity: value.ptr_value(),
            phantom: PhantomData,
        }
    }
}


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

// Possible optimisations:
// Encoding none, bool etc in the pointer of frozen value

pub(crate) mod aligned_size;
pub(crate) mod avalue;
pub(crate) mod complex;
mod const_frozen_string;
pub(crate) mod const_type_id;
pub(crate) mod heap;
pub(crate) mod identity;
pub(crate) mod pointer;
pub(crate) mod static_string;
pub(crate) mod typed;
pub(crate) mod value;
pub(crate) mod value_alloc_size;
pub(crate) mod value_captured;
pub(crate) mod value_not_special;
pub(crate) mod vtable;


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

// We use pointer tagging on the bottom three bits:
// ?00 => frozen pointer
// ?01 => mutable pointer
// ?10 => int (32 bit)
// third bit is a tag set by the user (get_user_tag)

// We group our bytes based on the tag info, not traditional alignment.
#![allow(clippy::unusual_byte_groupings)]

use std::cell::Cell;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::mem;
use std::num::NonZeroUsize;

use allocative::Allocative;
use dupe::Dupe;
use either::Either;
use static_assertions::assert_eq_size;

use crate::cast;
use crate::values::int::PointerI32;
use crate::values::layout::heap::repr::AValueHeader;
use crate::values::layout::heap::repr::AValueOrForward;
use crate::values::types::inline_int::InlineInt;

/// Tagged pointer logically equivalent to `*mut AValueHeader`.
#[derive(Clone, Copy, Dupe, PartialEq, Eq, Hash, Allocative)]
pub(crate) struct RawPointer(pub(crate) NonZeroUsize);

impl Debug for RawPointer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("RawPointer")
            .field(&format_args!("0x{:x}", self.ptr_value()))
            .finish()
    }
}

impl RawPointer {
    #[inline]
    pub(crate) unsafe fn new_unchecked(ptr: usize) -> RawPointer {
        debug_assert!(ptr != 0);
        RawPointer(NonZeroUsize::new_unchecked(ptr))
    }

    #[inline]
    pub(crate) fn new_int(i: InlineInt) -> RawPointer {
        let ptr = ((i.to_i32() as isize) << INT_SHIFT) as usize | TAG_INT;
        unsafe { Self::new_unchecked(ptr) }
    }

    #[inline]
    pub(crate) fn new_unfrozen(ptr: &AValueHeader, is_string: bool) -> RawPointer {
        let ptr = cast::ptr_to_usize(ptr);
        debug_assert!(ptr & TAG_MASK == 0);
        let ptr = if is_string { ptr | TAG_STR } else { ptr };
        let ptr = ptr | TAG_UNFROZEN;
        unsafe { Self::new_unchecked(ptr) }
    }

    #[inline]
    pub(crate) fn new_frozen(ptr: &AValueHeader, is_string: bool) -> RawPointer {
        let ptr = cast::ptr_to_usize(ptr);
        debug_assert!(ptr & TAG_MASK == 0);
        let ptr = if is_string { ptr | TAG_STR } else { ptr };
        unsafe { Self::new_unchecked(ptr) }
    }

    #[inline]
    pub(crate) fn ptr_value(self) -> usize {
        self.0.get()
    }

    #[inline]
    pub(crate) fn is_str(self) -> bool {
        (self.0.get() & TAG_STR) != 0
    }

    #[inline]
    pub(crate) fn is_int(self) -> bool {
        (self.0.get() & TAG_INT) != 0
    }

    #[inline]
    pub(crate) fn is_unfrozen(self) -> bool {
        (self.0.get() & TAG_UNFROZEN) != 0
    }

    #[inline]
    pub(crate) fn unpack_int(self) -> Option<InlineInt> {
        if !self.is_int() {
            None
        } else {
            unsafe { Some(self.unpack_int_unchecked()) }
        }
    }

    /// Unpack integer when it is known to be not a pointer.
    #[inline]
    pub(crate) unsafe fn unpack_int_unchecked(self) -> InlineInt {
        debug_assert!(self.is_int());
        debug_assert!(self.0.get() & !INT_DATA_MASK == TAG_INT);

        InlineInt::new_unchecked(((self.0.get() as isize) >> INT_SHIFT) as i32)
    }

    #[inline]
    pub(crate) unsafe fn unpack_ptr_no_int_unchecked<'v>(self) -> &'v AValueOrForward {
        debug_assert!(!self.is_int());
        let ptr = self.0.get() & !(TAG_STR | TAG_UNFROZEN);
        cast::usize_to_ptr(ptr)
    }
}

// A structure that is morally a `PointerUnpack`, but gets encoded in one
// pointer sized lump. The two types P1 and P2 are arbitrary pointers (which we
// instantiate to FrozenValueMem and ValueMem)
#[derive(Clone, Copy, Dupe)]
pub(crate) struct Pointer<'p> {
    ptr: RawPointer,
    // Make sure we are invariant in all the types/lifetimes.
    // See https://stackoverflow.com/questions/62659221/why-does-a-program-compile-despite-an-apparent-lifetime-mismatch
    _phantom: PhantomData<Cell<&'p AValueHeader>>,
}

// Similar to `Pointer` but allows widening lifetime, which is valid operation for frozen pointers.
#[derive(Clone, Copy, Dupe)]
pub(crate) struct FrozenPointer<'p> {
    ptr: RawPointer,
    phantom: PhantomData<&'p AValueHeader>,
}

fn _test_lifetime_covariant<'a>(p: FrozenPointer<'static>) -> FrozenPointer<'a> {
    p
}

assert_eq_size!(Pointer<'static>, usize);
assert_eq_size!(Option<Pointer<'static>>, usize);
assert_eq_size!(FrozenPointer<'static>, usize);
assert_eq_size!(Option<FrozenPointer<'static>>, usize);

#[allow(dead_code)] // False positive.
const TAG_BITS: usize = 3;
const TAG_MASK: usize = 0b111;
#[allow(clippy::assertions_on_constants)]
const _: () = assert!(TAG_MASK == (1 << TAG_BITS) - 1);

const TAG_INT: usize = 0b010;
const TAG_STR: usize = 0b100;
// Pointer to an object, which is not frozen.
// Note, an object can be changed from unfrozen to frozen, not vice versa.
const TAG_UNFROZEN: usize = 0b001;

/// `InlineInt` is shift by this number of bits to the left to be stored in a pointer.
const INT_SHIFT: usize = mem::size_of::<usize>() * 8 - InlineInt::BITS;
const INT_DATA_MASK: usize = ((1usize << InlineInt::BITS) - 1) << INT_SHIFT;
#[allow(clippy::assertions_on_constants)]
const _: () = assert!(INT_SHIFT >= TAG_BITS);

#[inline]
unsafe fn untag_pointer<'a>(x: usize) -> &'a AValueOrForward {
    cast::usize_to_ptr(x & !TAG_MASK)
}

impl<'p> Pointer<'p> {
    #[inline]
    unsafe fn new(ptr: RawPointer) -> Pointer<'p> {
        Pointer {
            ptr,
            _phantom: PhantomData,
        }
    }

    #[inline]
    pub(crate) unsafe fn new_unfrozen_usize_with_str_tag(x: usize) -> Self {
        debug_assert!((x & TAG_MASK & !TAG_STR) == 0);
        Self::new(RawPointer::new_unchecked(x | TAG_UNFROZEN))
    }

    #[inline]
    pub fn new_unfrozen(x: &'p AValueHeader, is_string: bool) -> Self {
        unsafe { Self::new(RawPointer::new_unfrozen(x, is_string)) }
    }

    #[inline]
    pub(crate) fn is_str(self) -> bool {
        self.ptr.is_str()
    }

    #[inline]
    pub fn is_unfrozen(self) -> bool {
        self.ptr.is_unfrozen()
    }

    #[inline]
    pub fn unpack(self) -> Either<&'p AValueOrForward, &'static PointerI32> {
        if !self.ptr.is_int() {
            Either::Left(unsafe { self.ptr.unpack_ptr_no_int_unchecked() })
        } else {
            Either::Right(unsafe { PointerI32::from_raw_pointer_unchecked(self.ptr) })
        }
    }

    #[inline]
    pub fn unpack_int(self) -> Option<InlineInt> {
        self.ptr.unpack_int()
    }

    #[inline]
    pub fn unpack_ptr(self) -> Option<&'p AValueOrForward> {
        let p = self.ptr.0.get();
        if p & TAG_INT == 0 {
            Some(unsafe { untag_pointer(p) })
        } else {
            None
        }
    }

    /// Unpack pointer when it is known to be not an integer.
    #[inline]
    pub(crate) unsafe fn unpack_ptr_no_int_unchecked(self) -> &'p AValueOrForward {
        let p = self.ptr.0.get();
        debug_assert!(p & TAG_INT == 0);
        untag_pointer(p)
    }

    /// Unpack integer when it is known to be not a pointer.
    #[inline]
    pub(crate) unsafe fn unpack_int_unchecked(self) -> InlineInt {
        self.ptr.unpack_int_unchecked()
    }

    #[inline]
    pub fn ptr_eq(self, other: Pointer<'_>) -> bool {
        self.ptr == other.ptr
    }

    #[inline]
    pub fn raw(self) -> RawPointer {
        self.ptr
    }

    #[inline]
    pub unsafe fn cast_lifetime<'p2>(self) -> Pointer<'p2> {
        Pointer {
            ptr: self.ptr,
            _phantom: PhantomData,
        }
    }

    #[inline]
    pub(crate) unsafe fn to_frozen_pointer_unchecked(self) -> FrozenPointer<'p> {
        FrozenPointer::new(self.ptr)
    }
}

impl<'p> FrozenPointer<'p> {
    #[inline]
    pub(crate) unsafe fn new(ptr: RawPointer) -> FrozenPointer<'p> {
        debug_assert!(!ptr.is_unfrozen());
        FrozenPointer {
            ptr,
            phantom: PhantomData,
        }
    }

    #[inline]
    pub fn new_frozen_usize_with_str_tag(x: usize) -> Self {
        debug_assert!((x & TAG_MASK & !TAG_STR) == 0);
        unsafe { Self::new(RawPointer::new_unchecked(x)) }
    }

    #[inline]
    pub(crate) fn new_frozen(x: &'p AValueHeader, is_str: bool) -> Self {
        unsafe { Self::new(RawPointer::new_frozen(x, is_str)) }
    }

    #[inline]
    pub(crate) fn new_int(x: InlineInt) -> Self {
        FrozenPointer {
            ptr: RawPointer::new_int(x),
            phantom: PhantomData,
        }
    }

    /// It is safe to bitcast `FrozenPointer` to `Pointer`
    /// but not vice versa.
    #[inline]
    pub(crate) fn to_pointer(self) -> Pointer<'p> {
        Pointer {
            ptr: self.ptr,
            _phantom: PhantomData,
        }
    }

    #[inline]
    pub(crate) fn raw(self) -> RawPointer {
        self.ptr
    }

    /// Unpack pointer when it is known to be not an integer.
    #[inline]
    pub(crate) unsafe fn unpack_ptr_no_int_unchecked(self) -> &'p AValueOrForward {
        debug_assert!(!self.ptr.is_int());
        self.ptr.unpack_ptr_no_int_unchecked()
    }

    /// Unpack integer when it is known to be not a pointer.
    #[inline]
    pub(crate) unsafe fn unpack_int_unchecked(self) -> InlineInt {
        self.ptr.unpack_int_unchecked()
    }

    /// Unpack pointer when it is known to be not an integer, not a string, and not frozen.
    #[inline]
    pub(crate) unsafe fn unpack_ptr_no_int_no_str_unchecked(self) -> &'p AValueOrForward {
        let p = self.ptr.0.get();
        debug_assert!(p & TAG_MASK == 0);
        cast::usize_to_ptr(p)
    }
}

#[cfg(test)]
#[test]
fn test_int_tag() {
    fn check(x: InlineInt) {
        assert_eq!(x, RawPointer::new_int(x).unpack_int().unwrap());
    }

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