/*
 * 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::collections::HashMap;
use std::fmt::Write;

use once_cell::sync::Lazy;

use crate::environment::Globals;
use crate::stdlib::LibraryExtension;
use crate::syntax::AstModule;
use crate::syntax::Dialect;
use crate::tests::golden_test_template::golden_test_template;
use crate::typing::function::Param;
use crate::typing::oracle::traits::OracleNoAttributes;
use crate::typing::oracle::traits::OracleSeq;
use crate::typing::oracle::traits::TypingAttr;
use crate::typing::Interface;
use crate::typing::OracleDocs;
use crate::typing::OracleStandard;
use crate::typing::Ty;
use crate::typing::TypingOracle;

fn mk_oracle() -> impl TypingOracle {
    static ORACLE: Lazy<OracleSeq<Box<dyn TypingOracle + Send + Sync + 'static>>> =
        Lazy::new(|| {
            let standard = OracleStandard::new(LibraryExtension::all());
            OracleSeq(vec![Box::new(standard), Box::new(OracleNoAttributes)])
        });
    &*ORACLE
}

#[test]
fn test_oracle() {
    let o = mk_oracle();

    let mut b = OracleDocs::new();
    b.add_module(&Globals::extended().documentation());

    assert_eq!(
        o.attribute(&Ty::string(), TypingAttr::Regular("removeprefix")),
        Some(Ok(Ty::function(
            vec![Param::pos_only(Ty::string())],
            Ty::string()
        )))
    );
    assert_eq!(
        b.builtin("hash"),
        Ok(Ty::function(vec![Param::pos_only(Ty::string())], Ty::int()))
    );
    assert_eq!(
        b.builtin("any"),
        Ok(Ty::function(
            vec![Param::pos_only(Ty::iter(Ty::Any))],
            Ty::bool()
        ))
    );
    assert_eq!(
        b.builtin("fail"),
        Ok(Ty::function(vec![Param::args(Ty::Any)], Ty::Never))
    );
    assert_eq!(b.builtin("not_a_symbol"), Err(()));

    fn has_type(x: &Result<Ty, ()>) -> bool {
        match x {
            Ok(Ty::Custom(c)) => {
                matches!(c.0.attribute_dyn(TypingAttr::Regular("type")), Some(Ok(_)))
            }
            _ => false,
        }
    }

    assert!(has_type(&b.builtin("int")));
    assert!(has_type(&b.builtin("str")));
    assert!(has_type(&b.builtin("list")));
    assert!(!has_type(&b.builtin("hash")));
}

#[derive(Default)]
struct TypeCheck {
    expect_interface: Vec<String>,
    loads: HashMap<String, Interface>,
}

impl TypeCheck {
    fn new() -> Self {
        Self::default()
    }

    fn ty(mut self, name: &str) -> Self {
        self.expect_interface.push(name.to_owned());
        self
    }

    fn load(mut self, file: &str, interface: Interface) -> Self {
        self.loads.insert(file.to_owned(), interface);
        self
    }

    fn check(&self, test_name: &str, code: &str) -> Interface {
        let (errors, _, interface, approximations) =
            AstModule::parse("filename", code.to_owned(), &Dialect::Extended)
                .unwrap()
                .typecheck(&mk_oracle(), &Globals::extended(), &self.loads);

        let mut output = String::new();
        writeln!(output, "Code:").unwrap();
        writeln!(output, "{}", code.trim()).unwrap();
        if errors.is_empty() {
            writeln!(output).unwrap();
            writeln!(output, "No errors.").unwrap();
        } else {
            for error in errors {
                writeln!(output).unwrap();
                writeln!(output, "Error:").unwrap();
                // Note we are using `:#` here instead of `:?` because
                // `:?` includes rust backtrace.
                // The issue: https://github.com/dtolnay/anyhow/issues/300
                writeln!(output, "{}", format!("{:#}", error).trim_end()).unwrap();
            }
        }

        if !approximations.is_empty() {
            writeln!(output).unwrap();
            writeln!(output, "Approximations:").unwrap();
            for appox in approximations {
                writeln!(output, "{}", appox).unwrap();
            }
        }

        if !self.expect_interface.is_empty() {
            writeln!(output).unwrap();
            writeln!(output, "Interfaces:").unwrap();
            for k in &self.expect_interface {
                let intf = interface.get(k).expect("no interface for key");
                writeln!(output, "{}: {}", k, intf).unwrap();
            }
        }

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

        interface
    }
}

#[test]
fn test_success() {
    TypeCheck::default().ty("y").check(
        "success",
        r#"
def foo(x: str.type) -> str.type:
    return x.removeprefix("test")
y = hash(foo("magic"))
   "#,
    );
}

#[test]
fn test_failure() {
    TypeCheck::new().check("failure", r#"hash(1)"#);
}

#[test]
fn test_load() {
    let interface = TypeCheck::new().check(
        "load_0",
        r#"
def foo(x: [bool.type]) -> str.type:
    return "test"
   "#,
    );
    TypeCheck::new().load("foo.bzl", interface).ty("res").check(
        "load_1",
        r#"
load("foo.bzl", "foo")
res = [foo([])]
"#,
    );
}

/// Test things that have previous claimed incorrectly they were type errors
#[test]
fn test_false_negative() {
    TypeCheck::new().check(
        "false_negative",
        r#"fail("Expected variable expansion in string: `{}`".format("x"))"#,
    );
}

#[test]
fn test_type_kwargs() {
    TypeCheck::new().check(
        "type_kwargs",
        r#"
def foo(**kwargs):
    pass
foo(**{1: "x"})
"#,
    );
}

#[test]
fn test_dot_type() {
    TypeCheck::new().check(
        "dot_type_0",
        r#"
def foo(x: list.type) -> bool.type:
    return type(x) == list.type
foo([1,2,3])
"#,
    );
    TypeCheck::new().check(
        "dot_type_1",
        r#"
def foo(x: list.type) -> bool.type:
    return type(x) == []
foo(True)
"#,
    );
}

#[test]
fn test_special_function_zip() {
    TypeCheck::new().ty("x").check(
        "zip",
        r#"
x = zip([1,2], [True, False], ["a", "b"])
"#,
    );
}

#[test]
fn test_special_function_struct() {
    TypeCheck::new().ty("x").check(
        "struct",
        r#"
x = struct(a = 1, b = "test")
"#,
    );
}

#[test]
fn test_call_callable() {
    TypeCheck::new().check(
        "call_callable",
        r#"
def foo(x: "function"):
    x()
"#,
    );
}

#[test]
fn test_call_not_callable() {
    TypeCheck::new().check(
        "call_not_callable",
        r#"
def foo(x: [""]):
    x()
"#,
    );
}

#[test]
fn test_call_unknown() {
    TypeCheck::new().check(
        "call_unknown",
        r#"
def foo(x: "unknown"):
    x()
"#,
    );
}

#[test]
fn test_call_callable_or_not_callable() {
    TypeCheck::new().check(
        "call_callable_or_not_callable",
        r#"
def foo(x: ["function", str.type], y: [str.type, "function"]):
    x()
    y()
"#,
    );
}

#[test]
fn test_call_callable_or_unknown() {
    TypeCheck::new().check(
        "call_callable_or_unknown",
        r#"
def foo(x: ["function", "unknown"], y: ["unknown", "function"]):
    x()
    y()
"#,
    );
}

#[test]
fn test_call_not_callable_or_unknown() {
    TypeCheck::new().check(
        "call_not_callable_or_unknown",
        r#"
def foo(x: [str.type, "unknown"], y: ["unknown", str.type]):
    x()
    y()
"#,
    );
}

#[test]
fn test_tuple() {
    TypeCheck::new().check(
        "tuple",
        r#"
def empty_tuple_fixed_name() -> (): return tuple()
def empty_tuple_name_fixed() -> tuple.type: return ()
"#,
    );
}

#[test]
fn test_test_new_syntax_without_dot_type() {
    TypeCheck::new().check(
        "new_syntax_without_dot_type",
        r#"
def foo(x: str): pass

# good
foo("test")

# bad
foo(1)
"#,
    );
}

#[test]
fn test_calls() {
    TypeCheck::new().check(
        "calls",
        r#"
def f(y): pass

# Extra parameter.
f(1, 2)

# Not enough parameters.
f()
"#,
    );
}

#[test]
fn test_list_append() {
    TypeCheck::new().ty("x").check(
        "list_append",
        r#"
# Type of `x` should be inferred as list of either `int` or `str`.
x = []
x.append(1)
x.append("")
"#,
    );
}

#[test]
fn test_list_append_bug() {
    // TODO(nga): fix.
    TypeCheck::new().ty("x").check(
        "list_append_bug",
        r#"
x = []
x.append(x)
"#,
    );
}

#[test]
fn test_new_list_dict_syntax() {
    TypeCheck::new().ty("x").check(
        "new_list_dict_syntax",
        r#"
def new_list_dict_syntax(x: dict[str, int]) -> list[str]:
    return list(x.keys())

# Check type is properly parsed from the function return type.
x = new_list_dict_syntax({"a": 1, "b": 2})
"#,
    );
}


/*
 * 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;
use std::any::Any;
use std::any::TypeId;
use std::cmp::Ordering;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::slice;

use allocative::Allocative;
use cmp_any::OrdAny;
use cmp_any::PartialEqAny;
use either::Either;
use serde::Serialize;
use serde::Serializer;

use crate::codemap::CodeMap;
use crate::codemap::Span;
use crate::codemap::Spanned;
use crate::docs::DocFunction;
use crate::docs::DocMember;
use crate::docs::DocParam;
use crate::docs::DocProperty;
use crate::docs::DocType;
use crate::eval::compiler::constants::Constants;
use crate::eval::compiler::scope::payload::CstIdent;
use crate::eval::compiler::scope::payload::CstPayload;
use crate::eval::compiler::scope::payload::CstTypeExpr;
use crate::eval::compiler::scope::ResolvedIdent;
use crate::slice_vec_ext::SliceExt;
use crate::slice_vec_ext::VecExt;
use crate::syntax::type_expr::TypeExprUnpackP;
use crate::typing::ctx::TypingContext;
use crate::typing::error::InternalError;
use crate::typing::error::TypingError;
use crate::typing::function::Arg;
use crate::typing::function::Param;
use crate::typing::function::ParamMode;
use crate::typing::function::TyCustomFunction;
use crate::typing::function::TyCustomFunctionImpl;
use crate::typing::function::TyFunction;
use crate::typing::mode::TypecheckMode;
use crate::typing::oracle::ctx::TypingOracleCtx;
use crate::typing::oracle::traits::TypingAttr;
use crate::typing::structs::TyStruct;
use crate::typing::TypingOracle;
use crate::values::typing::TypeCompiled;
use crate::values::FrozenValue;
use crate::values::Heap;

/// A typing operation wasn't able to produce a precise result,
/// so made some kind of approximation.
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Approximation {
    /// The category of the approximation, e.g. `"Unknown type"`.
    pub category: &'static str,
    /// The precise details of this approximation, e.g. which type was unknown.
    pub message: String,
}

impl Approximation {
    /// Create a new [`Approximation`].
    pub fn new(category: &'static str, message: impl Debug) -> Self {
        Self {
            category,
            message: format!("{:?}", message),
        }
    }
}

impl Display for Approximation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Approximation: {} = {:?}", self.category, self.message)
    }
}

/// A Starlark type.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Allocative)]
pub enum Ty {
    /// Type that can't be inhabited.
    /// If an expression has this type, then the code cannot be reached.
    Never,
    /// Type that contain anything
    Any,
    /// A series of alternative types.
    ///
    /// When typechecking, we try all alternatives, and if at least one of them
    /// succeeds, then the whole expression is considered to be a success.
    ///
    /// For example, when typechecking:
    ///
    /// ```python
    /// x = ... # string or int
    /// y = ... # string
    /// x + y   # `int + string` fails, but `string + string` succeeds,
    ///         # so the whole expression is typechecked successfully as `string`
    /// ```
    ///
    /// This is different handling of union types than in TypeScript for example,
    /// TypeScript would consider such expression to be an error.
    Union(TyUnion),
    /// A name, represented by `"name"` in the Starlark type.
    /// Will never be a type that can be represented by another operation,
    /// e.g. never `"list"` because `Ty::List` could be used instead.
    Name(TyName),
    /// Iter is a type that supports iteration, only used as arguments to primitive functions.
    /// The inner type is applicable for each iteration element.
    Iter(Box<Ty>),
    /// A list.
    List(Box<Ty>),
    /// A tuple. May be empty, to indicate the empty tuple.
    Tuple(Vec<Ty>),
    /// A dictionary, with key and value types
    Dict(Box<(Ty, Ty)>),
    /// Custom type.
    Custom(TyCustom),
}

impl Serialize for Ty {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Arbitrary custom types are not deserializable, so serialization to string is enough.
        serializer.serialize_str(&self.to_string())
    }
}

/// The name of an atomic type.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Allocative)]
pub struct TyName(String);

impl Display for TyName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.0.as_str() {
            "string" => write!(f, "str.type"),
            "int" => write!(f, "int.type"),
            "bool" => write!(f, "bool.type"),
            "NoneType" => write!(f, "None"),
            other => write!(f, "\"{}\"", other),
        }
    }
}

impl PartialEq<str> for TyName {
    fn eq(&self, other: &str) -> bool {
        self.0 == other
    }
}

impl TyName {
    /// Get the underlying `str` for a `TyName`.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

/// A series of types that are unioned together.
/// Must be at least two elements, all distinct elements, with no nested `Union` types directly inside it.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Allocative)]
pub struct TyUnion(Vec<Ty>);

impl Display for TyUnion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        display_container::fmt_container(f, "[", "]", &self.0)
    }
}

impl TyUnion {
    /// The alternatives within a union, will always be at least two elements.
    pub fn alternatives(&self) -> &[Ty] {
        &self.0
    }
}

/// Custom type implementation. [`Display`] must implement the representation of the type.
pub trait TyCustomImpl: Debug + Display + Clone + Ord + Allocative + Send + Sync + 'static {
    fn as_name(&self) -> Option<&str>;
    fn validate_call(
        &self,
        span: Span,
        args: &[Spanned<Arg>],
        oracle: TypingOracleCtx,
    ) -> Result<Ty, TypingError>;
    fn attribute(&self, attr: TypingAttr) -> Option<Result<Ty, ()>>;
    fn union2(x: Box<Self>, other: Box<Self>) -> Result<Box<Self>, (Box<Self>, Box<Self>)>;
}

pub(crate) trait TyCustomDyn: Debug + Display + Allocative + Send + Sync + 'static {
    fn eq_token(&self) -> PartialEqAny;
    fn cmp_token(&self) -> (OrdAny, &'static str);
    fn into_any(self: Box<Self>) -> Box<dyn Any>;

    fn clone_box_dyn(&self) -> Box<dyn TyCustomDyn>;
    fn as_name_dyn(&self) -> Option<&str>;
    fn validate_call_dyn(
        &self,
        span: Span,
        args: &[Spanned<Arg>],
        oracle: TypingOracleCtx,
    ) -> Result<Ty, TypingError>;
    fn attribute_dyn(&self, attr: TypingAttr) -> Option<Result<Ty, ()>>;
    fn union2_dyn(
        self: Box<Self>,
        other: Box<dyn TyCustomDyn>,
    ) -> Result<Box<dyn TyCustomDyn>, (Box<dyn TyCustomDyn>, Box<dyn TyCustomDyn>)>;
}

impl<T: TyCustomImpl> TyCustomDyn for T {
    fn eq_token(&self) -> PartialEqAny {
        PartialEqAny::new(self)
    }

    fn cmp_token(&self) -> (OrdAny, &'static str) {
        (OrdAny::new(self), any::type_name::<Self>())
    }

    fn into_any(self: Box<Self>) -> Box<dyn Any> {
        self
    }

    fn clone_box_dyn(&self) -> Box<dyn TyCustomDyn> {
        Box::new(self.clone())
    }

    fn as_name_dyn(&self) -> Option<&str> {
        self.as_name()
    }

    fn validate_call_dyn(
        &self,
        span: Span,
        args: &[Spanned<Arg>],
        oracle: TypingOracleCtx,
    ) -> Result<Ty, TypingError> {
        self.validate_call(span, args, oracle)
    }

    fn attribute_dyn(&self, attr: TypingAttr) -> Option<Result<Ty, ()>> {
        self.attribute(attr)
    }

    fn union2_dyn(
        self: Box<Self>,
        other: Box<dyn TyCustomDyn>,
    ) -> Result<Box<dyn TyCustomDyn>, (Box<dyn TyCustomDyn>, Box<dyn TyCustomDyn>)> {
        if TypeId::of::<Self>() == other.eq_token().type_id() {
            let other: Box<Self> = other.into_any().downcast().unwrap();
            T::union2(self, other)
                .map::<Box<dyn TyCustomDyn>, _>(|x| x)
                .map_err::<(Box<dyn TyCustomDyn>, Box<dyn TyCustomDyn>), _>(|(x, y)| (x, y))
        } else {
            Err((self, other))
        }
    }
}

#[derive(Debug, derive_more::Display, Allocative)]
pub struct TyCustom(pub(crate) Box<dyn TyCustomDyn>);

impl TyCustom {
    pub(crate) fn as_name(&self) -> Option<&str> {
        self.0.as_name_dyn()
    }

    pub(crate) fn union2(x: TyCustom, y: TyCustom) -> Result<TyCustom, (TyCustom, TyCustom)> {
        x.0.union2_dyn(y.0)
            .map(TyCustom)
            .map_err(|(x, y)| (TyCustom(x), TyCustom(y)))
    }

    #[allow(clippy::if_same_then_else, clippy::needless_bool)]
    pub(crate) fn intersects(x: &TyCustom, y: &TyCustom) -> bool {
        if x.as_name() == Some("function") && y.as_name() == Some("function") {
            true
        } else if x.0.eq_token().type_id() == y.0.eq_token().type_id() {
            // FIXME: Can probably be a bit more precise here
            true
        } else {
            false
        }
    }
}

impl PartialEq for TyCustom {
    fn eq(&self, other: &Self) -> bool {
        self.0.eq_token() == other.0.eq_token()
    }
}

impl Eq for TyCustom {}

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

impl Ord for TyCustom {
    fn cmp(&self, other: &Self) -> Ordering {
        let (a_cmp, a_type_name) = self.0.cmp_token();
        let (b_cmp, b_type_name) = other.0.cmp_token();

        // Type ids are comparable, but we want comparison independent of hashing.
        if OrdAny::type_id(&a_cmp) != OrdAny::type_id(&b_cmp) {
            let type_name_cmp = a_type_name.cmp(b_type_name);
            if type_name_cmp != Ordering::Equal {
                return type_name_cmp;
            }

            // This is unreachable: if the type names are the same,
            // the type ids should be the same.
        }

        a_cmp.cmp(&b_cmp)
    }
}

impl Clone for TyCustom {
    fn clone(&self) -> TyCustom {
        TyCustom(self.0.clone_box_dyn())
    }
}

fn merge_adjacent<T>(xs: Vec<T>, f: impl Fn(T, T) -> Either<T, (T, T)>) -> Vec<T> {
    let mut res = Vec::new();
    let mut last = None;
    for x in xs {
        match last {
            None => last = Some(x),
            Some(l) => match f(l, x) {
                Either::Left(x) => last = Some(x),
                Either::Right((l, x)) => {
                    res.push(l);
                    last = Some(x)
                }
            },
        }
    }
    if let Some(l) = last {
        res.push(l)
    }
    res
}

impl Ty {
    /// Create a [`Ty::Any`], but tagged in such a way it can easily be found.
    pub fn todo() -> Self {
        Ty::Any
    }

    /// Create a [`Ty::Name`], or one of the standard functions.
    pub fn name(name: &str) -> Self {
        match name {
            "list" => Self::List(Box::new(Ty::Any)),
            "dict" => Self::Dict(Box::new((Ty::Any, Ty::Any))),
            "function" => {
                Self::function(vec![Param::args(Ty::Any), Param::kwargs(Ty::Any)], Ty::Any)
            }
            "struct" => Self::custom(TyStruct::any()),
            "never" => Self::Never,
            // Note that "tuple" cannot be converted to Ty::Tuple
            // since we don't know the length of the tuple.
            _ => Self::Name(TyName(name.to_owned())),
        }
    }

    /// Turn a type back into a name, potentially erasing some structure.
    /// E.g. the type `[bool]` would return `list`.
    /// Types like [`Ty::Any`] will return `None`.
    pub fn as_name(&self) -> Option<&str> {
        match self {
            Ty::Name(x) => Some(x.as_str()),
            Ty::List(_) => Some("list"),
            Ty::Tuple(_) => Some("tuple"),
            Ty::Dict(_) => Some("dict"),
            Ty::Never => Some("never"),
            Ty::Custom(c) => c.as_name(),
            Ty::Any | Ty::Union(_) | Ty::Iter(_) => None,
        }
    }

    /// Create a `None` type.
    pub fn none() -> Self {
        Self::name("NoneType")
    }

    /// Create a boolean type.
    pub fn bool() -> Self {
        Self::name("bool")
    }

    /// Create the int type.
    pub fn int() -> Self {
        Self::name("int")
    }

    /// Create a float type.
    pub fn float() -> Self {
        Self::name("float")
    }

    /// Create a string type.
    pub fn string() -> Self {
        Self::name("string")
    }

    /// Create a list type.
    pub fn list(element: Ty) -> Self {
        Ty::List(Box::new(element))
    }

    /// Create a iterable type.
    pub fn iter(item: Ty) -> Self {
        Ty::Iter(Box::new(item))
    }

    /// Create a dictionary type.
    pub fn dict(key: Ty, value: Ty) -> Self {
        Ty::Dict(Box::new((key, value)))
    }

    /// Create a tuple of two elements
    pub fn tuple2(a: Ty, b: Ty) -> Self {
        Ty::Tuple(vec![a, b])
    }

    /// Create a function type.
    pub fn function(params: Vec<Param>, result: Ty) -> Self {
        Self::custom(TyCustomFunction(TyFunction {
            type_attr: String::new(),
            params,
            result: Box::new(result),
        }))
    }

    /// Create a function, where the first argument is the result of `.type`.
    pub fn ctor_function(type_attr: &str, params: Vec<Param>, result: Ty) -> Self {
        Self::custom(TyCustomFunction(TyFunction {
            type_attr: type_attr.to_owned(),
            params,
            result: Box::new(result),
        }))
    }

    pub(crate) fn is_any(&self) -> bool {
        self == &Ty::Any
    }

    pub(crate) fn is_never(&self) -> bool {
        self == &Ty::Never
    }

    pub(crate) fn is_list(&self) -> bool {
        matches!(self, Ty::List(_))
    }

    pub(crate) fn is_name(&self, name: &str) -> bool {
        match self {
            Ty::Name(x) => x == name,
            _ => false,
        }
    }

    /// Create a unions type, which will be normalised before being created.
    pub fn unions(mut xs: Vec<Self>) -> Self {
        xs = xs.into_iter().flat_map(|x| x.into_iter_union()).collect();
        xs.sort();
        xs.dedup();
        xs.retain(|x| x != &Ty::Never);
        if xs.contains(&Ty::Any) {
            return Ty::Any;
        }
        // Try merging adjacent elements
        xs = merge_adjacent(xs, |x, y| match (x, y) {
            (Ty::List(x), Ty::List(y)) => Either::Left(Ty::list(Ty::union2(*x, *y))),
            (Ty::Dict(x), Ty::Dict(y)) => {
                Either::Left(Ty::dict(Ty::union2(x.0, y.0), Ty::union2(x.1, y.1)))
            }
            (Ty::Custom(x), Ty::Custom(y)) => match TyCustom::union2(x, y) {
                Ok(u) => Either::Left(Ty::Custom(u)),
                Err((x, y)) => Either::Right((Ty::Custom(x), Ty::Custom(y))),
            },
            xy => Either::Right(xy),
        });

        if xs.is_empty() {
            Ty::Never
        } else if xs.len() == 1 {
            xs.pop().unwrap()
        } else {
            Self::Union(TyUnion(xs))
        }
    }

    /// Iterate over the types within a union, pretending the type is a singleton union if not a union.
    pub(crate) fn iter_union(&self) -> &[Self] {
        match self {
            Self::Union(xs) => &xs.0,
            Self::Never => &[],
            _ => slice::from_ref(self),
        }
    }

    /// Iterate over the types within a union, pretending the type is a singleton union if not a union.
    pub(crate) fn into_iter_union(self) -> impl Iterator<Item = Self> {
        match self {
            Self::Union(xs) => Either::Left(xs.0.into_iter()),
            Self::Never => Either::Left(Vec::new().into_iter()),
            _ => Either::Right(std::iter::once(self)),
        }
    }

    /// Create a union of two entries.
    pub fn union2(a: Self, b: Self) -> Self {
        Self::unions(vec![a, b])
    }

    /// Create a custom type.
    /// This is called from generated code.
    pub fn custom(t: impl TyCustomImpl) -> Self {
        Ty::Custom(TyCustom(Box::new(t)))
    }

    /// Create a custom function type.
    pub fn custom_function(f: impl TyCustomFunctionImpl) -> Self {
        Ty::custom(TyCustomFunction(f))
    }

    /// If I do `self[i]` what will the resulting type be.
    pub(crate) fn indexed(self, i: usize) -> Ty {
        match self {
            Ty::Any => Ty::Any,
            Ty::Never => Ty::Never,
            Ty::List(x) => *x,
            Ty::Tuple(xs) => xs.get(i).cloned().unwrap_or(Ty::Never),
            Ty::Union(xs) => Ty::unions(xs.0.into_map(|x| x.indexed(i))),
            // Not exactly sure what we should do here
            _ => Ty::Any,
        }
    }

    /// Returns false on Void, since that is definitely not a list
    pub(crate) fn probably_a_list(&self, ctx: TypingOracleCtx) -> bool {
        if self.is_never() {
            return false;
        }
        self.intersects(&Self::list(Ty::Any), ctx)
    }

    /// See what lies behind an attribute on a type
    pub(crate) fn attribute(&self, attr: TypingAttr, ctx: &TypingContext) -> Result<Ty, ()> {
        // There are some structural types which have to be handled in a specific way
        match self {
            Ty::Any => Ok(Ty::Any),
            Ty::Never => Ok(Ty::Never),
            Ty::Union(xs) => {
                let rs = xs
                    .alternatives()
                    .iter()
                    .flat_map(|x| x.attribute(attr, ctx))
                    .collect::<Vec<_>>();
                if rs.is_empty() {
                    // Since xs wasn't empty, we must have had all types give us an invalid attribute.
                    // So therefore this attribute must be invalid.
                    Err(())
                } else {
                    Ok(Ty::unions(rs))
                }
            }
            _ => match ctx.oracle.attribute(self, attr) {
                Some(r) => r,
                None => Ok(ctx.approximation("oracle.attribute", format!("{}.{}", self, attr))),
            },
        }
    }

    /// If you get to a point where these types are being checked, might they succeed
    pub(crate) fn intersects(&self, other: &Self, oracle: TypingOracleCtx) -> bool {
        if self.is_any() || self.is_never() || other.is_any() || other.is_never() {
            return true;
        }

        let equal_names =
            |x: &TyName, y: &TyName| x == y || oracle.subtype(x, y) || oracle.subtype(y, x);

        let itered = |ty: &Ty| oracle.attribute(ty, TypingAttr::Iter)?.ok();

        for x in self.iter_union() {
            for y in other.iter_union() {
                let b = match (x, y) {
                    (Ty::Name(x), Ty::Name(y)) => equal_names(x, y),
                    (Ty::List(x), Ty::List(y)) => x.intersects(y, oracle),
                    (Ty::Dict(x), Ty::Dict(y)) => {
                        x.0.intersects(&y.0, oracle) && x.1.intersects(&y.1, oracle)
                    }
                    (Ty::Tuple(_), t) | (t, Ty::Tuple(_)) if t.is_name("tuple") => true,
                    (Ty::Tuple(xs), Ty::Tuple(ys)) if xs.len() == ys.len() => {
                        std::iter::zip(xs, ys).all(|(x, y)| x.intersects(y, oracle))
                    }
                    (Ty::Iter(x), Ty::Iter(y)) => x.intersects(y, oracle),
                    (Ty::Iter(x), y) | (y, Ty::Iter(x)) => match itered(y) {
                        Some(yy) => x.intersects(&yy, oracle),
                        None => false,
                    },
                    (Ty::Custom(x), Ty::Custom(y)) => TyCustom::intersects(x, y),
                    (x, y)
                        if x.as_name() == Some("function") && y.as_name() == Some("function") =>
                    {
                        true
                    }
                    // There are lots of other cases that overlap, but add them as we need them
                    (x, y) => x == y,
                };
                if b {
                    return true;
                }
            }
        }
        return false;
    }

    pub(crate) fn from_type_expr_opt(
        x: &Option<Box<CstTypeExpr>>,
        typecheck_mode: TypecheckMode,
        approximations: &mut Vec<Approximation>,
        codemap: &CodeMap,
    ) -> Result<Self, InternalError> {
        match x {
            None => Ok(Ty::Any),
            Some(x) => Self::from_type_expr(x, typecheck_mode, approximations, codemap),
        }
    }

    pub(crate) fn from_type_expr(
        x: &CstTypeExpr,
        typecheck_mode: TypecheckMode,
        approximations: &mut Vec<Approximation>,
        codemap: &CodeMap,
    ) -> Result<Self, InternalError> {
        match typecheck_mode {
            TypecheckMode::Lint => {
                // TODO(nga): remove this branch: in lint, populate types in CstPayload
                //   before running typechecking, and always fetch the type from the payload.
                Self::from_type_expr_for_lint(x, codemap, approximations)
            }
            TypecheckMode::Compiler => match x.payload {
                Some(ty) => Ok(ty.as_ty()),
                None => Err(InternalError::msg(
                    "type payload is not populated",
                    x.span,
                    codemap,
                )),
            },
        }
    }

    fn from_type_expr_for_lint(
        x: &CstTypeExpr,
        codemap: &CodeMap,
        approximations: &mut Vec<Approximation>,
    ) -> Result<Self, InternalError> {
        let x = TypeExprUnpackP::unpack(&x.expr, codemap)
            .map_err(InternalError::from_eval_exception)?;
        Ok(Self::from_expr_impl(&x, approximations))
    }

    // This should go away when `ExprType` is disconnected from `Expr`.
    fn from_expr_impl(
        x: &Spanned<TypeExprUnpackP<CstPayload>>,
        approximations: &mut Vec<Approximation>,
    ) -> Self {
        let mut unknown = || {
            approximations.push(Approximation::new("Unknown type", x));
            Ty::Any
        };

        fn ident_global(ident: &CstIdent) -> Option<FrozenValue> {
            match &ident.node.1 {
                Some(ResolvedIdent::Global(x)) => Some(*x),
                _ => None,
            }
        }

        match &x.node {
            TypeExprUnpackP::Tuple(xs) => {
                Ty::Tuple(xs.map(|x| Self::from_expr_impl(x, approximations)))
            }
            TypeExprUnpackP::Any(xs) => {
                Ty::unions(xs.map(|x| Self::from_expr_impl(x, approximations)))
            }
            TypeExprUnpackP::ListOf(x) => Ty::list(Self::from_expr_impl(x, approximations)),
            TypeExprUnpackP::DictOf(k, v) => Ty::dict(
                Self::from_expr_impl(k, approximations),
                Self::from_expr_impl(v, approximations),
            ),
            TypeExprUnpackP::Literal(x) => {
                if x.is_empty() || x.starts_with('_') {
                    Ty::Any
                } else {
                    Ty::name(x)
                }
            }
            TypeExprUnpackP::Path(first, rem) => {
                if rem.is_empty() {
                    if let Some(v) = ident_global(first) {
                        let heap = Heap::new();
                        match TypeCompiled::new(v.to_value(), &heap) {
                            Ok(ty) => ty.as_ty(),
                            Err(_) => unknown(),
                        }
                    } else {
                        unknown()
                    }
                } else if rem.len() == 1 {
                    if rem[0].node == "type" {
                        if first.node.0 == "str" {
                            Ty::string()
                        } else {
                            Ty::name(&first.node.0)
                        }
                    } else {
                        unknown()
                    }
                } else {
                    unknown()
                }
            }
            TypeExprUnpackP::Index(a, i) => {
                if let Some(a) = ident_global(a) {
                    if !a.to_value().ptr_eq(Constants::get().fn_list.0.to_value()) {
                        approximations.push(Approximation::new("Not list", x));
                        return Ty::Any;
                    }
                    let i = Self::from_expr_impl(i, approximations);
                    let heap = Heap::new();
                    let i = TypeCompiled::from_ty(&i, &heap);
                    match a.to_value().get_ref().at(i.to_inner(), &heap) {
                        Ok(t) => match TypeCompiled::new(t, &heap) {
                            Ok(ty) => ty.as_ty(),
                            Err(_) => {
                                approximations
                                    .push(Approximation::new("TypeCompiled::new failed", x));
                                Ty::Any
                            }
                        },
                        Err(e) => {
                            approximations.push(Approximation::new("Getitem failed", e));
                            Ty::Any
                        }
                    }
                } else {
                    approximations.push(Approximation::new("Not global", x));
                    Ty::Any
                }
            }
            TypeExprUnpackP::Index2(a, i0, i1) => {
                if let Some(a) = ident_global(a) {
                    if !a.to_value().ptr_eq(Constants::get().fn_dict.0.to_value()) {
                        approximations.push(Approximation::new("Not dict", x));
                        return Ty::Any;
                    }
                    let i0 = Self::from_expr_impl(i0, approximations);
                    let i1 = Self::from_expr_impl(i1, approximations);
                    let heap = Heap::new();
                    let i0 = TypeCompiled::from_ty(&i0, &heap);
                    let i1 = TypeCompiled::from_ty(&i1, &heap);
                    match a
                        .to_value()
                        .get_ref()
                        .at2(i0.to_inner(), i1.to_inner(), &heap)
                    {
                        Ok(t) => match TypeCompiled::new(t, &heap) {
                            Ok(ty) => ty.as_ty(),
                            Err(_) => {
                                approximations
                                    .push(Approximation::new("TypeCompiled::new failed", x));
                                Ty::Any
                            }
                        },
                        Err(e) => {
                            approximations.push(Approximation::new("Getitem2 failed", e));
                            Ty::Any
                        }
                    }
                } else {
                    approximations.push(Approximation::new("Not global", x));
                    Ty::Any
                }
            }
        }
    }

    pub(crate) fn from_docs_member(member: &DocMember) -> Self {
        match member {
            DocMember::Property(x) => Self::from_docs_type(&x.typ),
            DocMember::Function(x) => Self::from_docs_function(x),
        }
    }

    pub(crate) fn from_docs_property(property: &DocProperty) -> Self {
        Self::from_docs_type(&property.typ)
    }

    pub(crate) fn from_docs_function(function: &DocFunction) -> Self {
        let mut params = Vec::with_capacity(function.params.len());
        let mut seen_no_args = false;
        for p in &function.params {
            match p {
                DocParam::Arg {
                    name,
                    typ,
                    default_value,
                    ..
                } => {
                    let mut r = if seen_no_args {
                        Param::name_only(name, Ty::from_docs_type(typ))
                    } else {
                        Param::pos_or_name(name, Ty::from_docs_type(typ))
                    };
                    if default_value.is_some() {
                        r = r.optional();
                    }
                    params.push(r);
                }
                DocParam::OnlyPosBefore => {
                    for x in params.iter_mut() {
                        if matches!(x.mode, ParamMode::PosOrName(_)) {
                            x.mode = ParamMode::PosOnly;
                        }
                    }
                }
                DocParam::NoArgs => seen_no_args = true,
                DocParam::Args { typ, .. } => {
                    seen_no_args = true;
                    params.push(Param::args(Ty::from_docs_type(typ)))
                }
                DocParam::Kwargs { typ, .. } => params.push(Param::kwargs(Ty::from_docs_type(typ))),
            }
        }
        let result = Self::from_docs_type(&function.ret.typ);
        match &function.dot_type {
            None => Ty::function(params, result),
            Some(type_attr) => Ty::ctor_function(type_attr, params, result),
        }
    }

    pub(crate) fn from_docs_type(ty: &Option<DocType>) -> Self {
        match ty {
            None => Ty::Any,
            Some(x) => x.raw_type.clone(),
        }
    }
}

impl Display for Ty {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Ty::Never => write!(f, "\"never\""),
            Ty::Any => write!(f, "\"\""),
            Ty::Union(xs) => write!(f, "{}", xs),
            Ty::Name(x) => write!(f, "{}", x),
            Ty::Iter(x) => write!(f, "iter({})", x),
            Ty::List(x) => write!(f, "[{}]", x),
            Ty::Tuple(xs) => {
                if xs.len() == 1 {
                    write!(f, "({},)", xs[0])
                } else {
                    display_container::fmt_container(f, "(", ")", xs)
                }
            }
            Ty::Dict(k_v) => write!(f, "{{{}: {}}}", k_v.0, k_v.1),
            Ty::Custom(c) => Display::fmt(c, f),
        }
    }
}


/*
 * 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::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Display;
use std::slice;

use dupe::Dupe;

use crate::codemap::CodeMap;
use crate::codemap::FileSpanRef;
use crate::codemap::Span;
use crate::environment::names::MutableNames;
use crate::environment::Globals;
use crate::eval::compiler::scope::payload::CstStmt;
use crate::eval::compiler::scope::BindingId;
use crate::eval::compiler::scope::BindingSource;
use crate::eval::compiler::scope::ModuleScopes;
use crate::eval::compiler::EvalException;
use crate::syntax::ast::StmtP;
use crate::syntax::ast::Visibility;
use crate::syntax::AstModule;
use crate::syntax::Dialect;
use crate::typing::bindings::Bindings;
use crate::typing::bindings::BindingsCollect;
use crate::typing::bindings::Interface;
use crate::typing::ctx::TypingContext;
use crate::typing::error::InternalError;
use crate::typing::error::TypingError;
use crate::typing::mode::TypecheckMode;
use crate::typing::oracle::ctx::TypingOracleCtx;
use crate::typing::oracle::traits::TypingOracle;
use crate::typing::ty::Approximation;
use crate::typing::ty::Ty;
use crate::typing::OracleDocs;
use crate::values::FrozenHeap;

// Things which are None in the map have type void - they are never constructed
pub(crate) fn solve_bindings(
    oracle: &dyn TypingOracle,
    globals: &Globals,
    bindings: Bindings,
    codemap: &CodeMap,
) -> (Vec<TypingError>, HashMap<BindingId, Ty>, Vec<Approximation>) {
    let mut types = bindings
        .expressions
        .keys()
        .map(|x| (*x, Ty::Never))
        .collect::<HashMap<_, _>>();
    for (k, ty) in bindings.types {
        types.insert(k, ty);
    }
    // FIXME: Should be a fixed point, just do 10 iterations since that probably converges
    let mut changed = false;
    let mut global_docs = OracleDocs::new();
    global_docs.add_module(&globals.documentation());
    let mut ctx = TypingContext {
        oracle: TypingOracleCtx { oracle, codemap },
        global_docs,
        errors: RefCell::new(Vec::new()),
        approximoations: RefCell::new(Vec::new()),
        types,
    };
    const ITERATIONS: usize = 100;
    for _iteration in 0..ITERATIONS {
        changed = false;
        ctx.errors.borrow_mut().clear();
        for (name, exprs) in &bindings.expressions {
            for expr in exprs {
                let ty = ctx.expression_bind_type(expr);
                let t = ctx.types.get_mut(name).unwrap();
                let new = Ty::union2(t.clone(), ty);
                if &new != t {
                    changed = true;
                    *t = new;
                }
            }
        }
        if !changed {
            break;
        }
    }
    if changed {
        ctx.approximoations.borrow_mut().push(Approximation::new(
            "Fixed point didn't converge",
            ITERATIONS,
        ));
    }
    // Make sure we check every expression, looking for failures
    for x in &bindings.check {
        ctx.expression_type(x);
    }
    for (span, e, require) in &bindings.check_type {
        let ty = match e {
            None => Ty::none(),
            Some(x) => ctx.expression_type(x),
        };
        ctx.validate_type(&ty, require, *span);
    }
    (
        ctx.errors.into_inner(),
        ctx.types,
        ctx.approximoations.into_inner(),
    )
}

/// Structure containing all the inferred types.
#[derive(Debug)]
pub struct TypeMap {
    codemap: CodeMap,
    bindings: HashMap<BindingId, (String, Span, Ty)>,
}

impl Display for TypeMap {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Iteration in unstable order - but that's fine because this is just for diagnostics
        for (name, span, ty) in self.bindings.values() {
            writeln!(
                f,
                "{} ({}) = {}",
                name,
                FileSpanRef {
                    file: &self.codemap,
                    span: *span
                },
                ty
            )?;
        }
        Ok(())
    }
}

impl AstModule {
    /// Typecheck a module
    pub fn typecheck(
        self,
        oracle: &dyn TypingOracle,
        globals: &Globals,
        loads: &HashMap<String, Interface>,
    ) -> (Vec<anyhow::Error>, TypeMap, Interface, Vec<Approximation>) {
        let codemap = self.codemap.dupe();
        let names = MutableNames::new();
        let frozen_heap = FrozenHeap::new();
        let (
            scope_errors,
            ModuleScopes {
                cst,
                scope_data,
                module_bindings,
                ..
            },
        ) = ModuleScopes::check_module(
            &names,
            &frozen_heap,
            loads,
            self.statement,
            frozen_heap.alloc_any_display_from_debug(globals.dupe()),
            frozen_heap.alloc_any_display_from_debug(self.codemap.dupe()),
            &Dialect::Extended,
        );
        // We don't really need to properly unpack top-level statements,
        // but make it safe against future changes.
        let cst: &[CstStmt] = match &cst.node {
            StmtP::Statements(x) => x,
            _ => slice::from_ref(&cst),
        };
        let bindings = match BindingsCollect::collect(cst, TypecheckMode::Lint, &codemap) {
            Ok(bindings) => bindings,
            Err(e) => {
                return (
                    vec![InternalError::into_anyhow(e)],
                    TypeMap {
                        codemap,
                        bindings: HashMap::new(),
                    },
                    Interface::default(),
                    Vec::new(),
                );
            }
        };
        let mut approximations = bindings.approximations;
        let (solve_errors, types, solve_approximations) =
            solve_bindings(oracle, globals, bindings.bindings, &codemap);

        approximations.extend(solve_approximations);

        let mut typemap = HashMap::with_capacity(types.len());
        for (id, ty) in &types {
            let binding = scope_data.get_binding(*id);
            let name = binding.name.as_str().to_owned();
            let span = match binding.source {
                BindingSource::Source(span, _) => span,
                BindingSource::FromModule => Span::default(),
            };
            typemap.insert(*id, (name, span, ty.clone()));
        }
        let typemap = TypeMap {
            bindings: typemap,
            codemap: codemap.dupe(),
        };

        let errors = scope_errors
            .into_iter()
            .map(EvalException::into_anyhow)
            .chain(solve_errors.into_iter().map(TypingError::into_anyhow))
            .collect();

        let mut res = HashMap::new();
        for (name, vis) in names.all_names_and_visibilities() {
            if vis == Visibility::Public {
                let ty = types[module_bindings.get(name.as_str()).unwrap()].clone();
                res.insert(name.as_str().to_owned(), ty);
            }
        }
        let interface = Interface::new(res);

        (errors, typemap, interface, approximations)
    }
}


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

use dupe::Dupe;

use crate::codemap::CodeMap;
use crate::codemap::Span;
use crate::codemap::Spanned;
use crate::typing::error::TypingError;
use crate::typing::function::Arg;
use crate::typing::function::Param;
use crate::typing::function::ParamMode;
use crate::typing::function::TyFunction;
use crate::typing::Ty;
use crate::typing::TyName;
use crate::typing::TypingAttr;
use crate::typing::TypingBinOp;
use crate::typing::TypingOracle;

#[derive(Debug, thiserror::Error)]
enum TypingOracleCtxError {
    #[error("Expected type `{require}` but got `{got}`")]
    IncompatibleType { got: String, require: String },
    #[error("Call to a non-callable type `{ty}`")]
    CallToNonCallable { ty: String },
    #[error("Missing required parameter `{name}`")]
    MissingRequiredParameter { name: String },
    #[error("Unexpected parameter named `{name}`")]
    UnexpectedNamedArgument { name: String },
    #[error("Too many positional arguments")]
    TooManyPositionalArguments,
    #[error("Call arguments incompatible")]
    CallArgumentsIncompatible,
}

/// Oracle reference with utility methods.
///
/// This type is stateless.
#[derive(Clone, Copy, Dupe)]
pub struct TypingOracleCtx<'a> {
    pub(crate) oracle: &'a dyn TypingOracle,
    pub(crate) codemap: &'a CodeMap,
}

impl<'a> TypingOracle for TypingOracleCtx<'a> {
    fn attribute(&self, ty: &Ty, attr: TypingAttr) -> Option<Result<Ty, ()>> {
        Some(Ok(match ty {
            ty if ty == &Ty::none() => return Some(Err(())),
            Ty::Tuple(tys) => match attr {
                TypingAttr::BinOp(TypingBinOp::In) => {
                    Ty::function(vec![Param::pos_only(Ty::unions(tys.clone()))], Ty::bool())
                }
                TypingAttr::Iter => Ty::unions(tys.clone()),
                TypingAttr::Index => {
                    Ty::function(vec![Param::pos_only(Ty::int())], Ty::unions(tys.clone()))
                }
                _ => return Some(Err(())),
            },
            Ty::Name(x) if x == "tuple" => match attr {
                TypingAttr::Iter => Ty::Any,
                TypingAttr::BinOp(TypingBinOp::In) => {
                    Ty::function(vec![Param::pos_only(Ty::Any)], Ty::bool())
                }
                TypingAttr::Index => Ty::function(vec![Param::pos_only(Ty::int())], Ty::Any),
                _ => return Some(Err(())),
            },
            Ty::Custom(c) => return c.0.attribute_dyn(attr),
            ty => return self.oracle.attribute(ty, attr),
        }))
    }

    fn as_function(&self, ty: &TyName) -> Option<Result<TyFunction, ()>> {
        self.oracle.as_function(ty)
    }

    fn subtype(&self, require: &TyName, got: &TyName) -> bool {
        self.oracle.subtype(require, got)
    }
}

impl<'a> TypingOracleCtx<'a> {
    pub(crate) fn mk_error(&self, span: Span, err: impl Into<anyhow::Error>) -> TypingError {
        TypingError::new(err.into(), span, self.codemap)
    }

    pub(crate) fn msg_error(&self, span: Span, msg: impl Display) -> TypingError {
        TypingError::msg(msg, span, self.codemap)
    }

    pub(crate) fn validate_type(
        &self,
        got: &Ty,
        require: &Ty,
        span: Span,
    ) -> Result<(), TypingError> {
        if !got.intersects(require, *self) {
            Err(self.mk_error(
                span,
                TypingOracleCtxError::IncompatibleType {
                    got: got.to_string(),
                    require: require.to_string(),
                },
            ))
        } else {
            Ok(())
        }
    }

    fn validate_args(
        &self,
        params: &[Param],
        args: &[Spanned<Arg>],
        span: Span,
    ) -> Result<(), TypingError> {
        // Want to figure out which arguments go in which positions
        let mut param_args: Vec<Vec<Spanned<&Ty>>> = vec![vec![]; params.len()];
        // The next index a positional parameter might fill
        let mut param_pos = 0;
        let mut seen_vargs = false;

        for arg in args {
            match &arg.node {
                Arg::Pos(ty) => loop {
                    match params.get(param_pos) {
                        None => {
                            return Err(self.mk_error(
                                arg.span,
                                TypingOracleCtxError::TooManyPositionalArguments,
                            ));
                        }
                        Some(param) => {
                            let found_index = param_pos;
                            if param.mode != ParamMode::Args {
                                param_pos += 1;
                            }
                            if param.allows_pos() {
                                param_args[found_index].push(Spanned {
                                    span: arg.span,
                                    node: ty,
                                });
                                break;
                            }
                        }
                    }
                },
                Arg::Name(name, ty) => {
                    let mut success = false;
                    for (i, param) in params.iter().enumerate() {
                        if param.name() == name || param.mode == ParamMode::Kwargs {
                            param_args[i].push(Spanned {
                                span: arg.span,
                                node: ty,
                            });
                            success = true;
                            break;
                        }
                    }
                    if !success {
                        return Err(self.mk_error(
                            arg.span,
                            TypingOracleCtxError::UnexpectedNamedArgument { name: name.clone() },
                        ));
                    }
                }
                Arg::Args(_) => {
                    param_pos = params.len();
                    seen_vargs = true;
                }
                Arg::Kwargs(_) => {
                    seen_vargs = true;
                }
            }
        }

        for (param, args) in std::iter::zip(params, param_args) {
            if !param.allows_many() && args.len() > 1 {
                panic!("bad")
            }
            if args.is_empty() {
                // We assume that *args/**kwargs might have splatted things everywhere.
                if !param.optional && !seen_vargs {
                    return Err(self.mk_error(
                        span,
                        TypingOracleCtxError::MissingRequiredParameter {
                            name: param.name().to_owned(),
                        },
                    ));
                }
                continue;
            }
            match param.mode {
                ParamMode::PosOnly | ParamMode::PosOrName(_) | ParamMode::NameOnly(_) => {
                    self.validate_type(args[0].node, &param.ty, args[0].span)?;
                }
                ParamMode::Args => {
                    for ty in args {
                        // For an arg, we require the type annotation to be inner value,
                        // rather than the outer (which is always a tuple)
                        self.validate_type(ty.node, &param.ty, ty.span)?;
                    }
                }
                ParamMode::Kwargs => {
                    let val_types: Vec<_> = param
                        .ty
                        .iter_union()
                        .iter()
                        .filter_map(|x| match x {
                            Ty::Dict(k_v) => Some(k_v.1.clone()),
                            _ => None,
                        })
                        .collect();
                    if !val_types.is_empty() {
                        let require = Ty::unions(val_types);
                        for ty in args {
                            self.validate_type(ty.node, &require, ty.span)?;
                        }
                    }
                }
            }
        }
        Ok(())
    }

    pub(crate) fn validate_fn_call(
        &self,
        span: Span,
        fun: &TyFunction,
        args: &[Spanned<Arg>],
    ) -> Result<Ty, TypingError> {
        self.validate_args(&fun.params, args, span)?;
        Ok((*fun.result).clone())
    }

    #[allow(clippy::collapsible_else_if)]
    pub(crate) fn validate_call(
        &self,
        span: Span,
        fun: &Ty,
        args: &[Spanned<Arg>],
    ) -> Result<Ty, TypingError> {
        match fun {
            Ty::Never => Ok(Ty::Never),
            Ty::Any => Ok(Ty::Any),
            Ty::Name(n) => match self.oracle.as_function(n) {
                None => {
                    // Unknown type, may be callable.
                    Ok(Ty::Any)
                }
                Some(Ok(f)) => self.validate_fn_call(span, &f, args),
                Some(Err(())) => Err(self.mk_error(
                    span,
                    TypingOracleCtxError::CallToNonCallable {
                        ty: fun.to_string(),
                    },
                )),
            },
            Ty::List(_) | Ty::Dict(_) | Ty::Tuple(_) => Err(self.mk_error(
                span,
                TypingOracleCtxError::CallToNonCallable {
                    ty: fun.to_string(),
                },
            )),
            Ty::Iter(_) => {
                // Unknown type, may be callable.
                Ok(Ty::Any)
            }
            Ty::Custom(t) => t.0.validate_call_dyn(span, args, *self),
            Ty::Union(variants) => {
                let mut successful = Vec::new();
                let mut errors = Vec::new();
                for variant in variants.alternatives() {
                    match self.validate_call(span, variant, args) {
                        Ok(ty) => successful.push(ty),
                        Err(e) => errors.push(e),
                    }
                }
                if !successful.is_empty() {
                    Ok(Ty::unions(successful))
                } else {
                    if errors.len() == 1 {
                        Err(errors.pop().unwrap())
                    } else {
                        Err(self.mk_error(span, TypingOracleCtxError::CallArgumentsIncompatible))
                    }
                }
            }
        }
    }
}


/*
 * 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::collections::HashMap;

use crate::docs::Doc;
use crate::docs::DocItem;
use crate::docs::DocModule;
use crate::typing::oracle::traits::TypingAttr;
use crate::typing::Ty;
use crate::typing::TypingOracle;

/// A [`TypingOracle`] based on information from documentation.
#[derive(Default)]
pub struct OracleDocs {
    /// Indexed by type name, then the attribute
    objects: HashMap<String, HashMap<String, Ty>>,
    functions: HashMap<String, Ty>,
}

impl OracleDocs {
    /// Create a new [`OracleDocs`] with no information.
    /// You can then call the various methods to populate it with data.
    pub fn new() -> Self {
        Self::default()
    }

    /// Add multiple entries to the documentation, usually given the results of
    /// [`get_registered_starlark_docs`](crate::docs::get_registered_starlark_docs).
    pub fn add_docs(&mut self, docs: &[Doc]) {
        for doc in docs {
            self.add_doc(doc);
        }
    }

    /// Add information from a [`Doc`] to the documentation, overwriting existing items.
    pub fn add_doc(&mut self, doc: &Doc) {
        match &doc.item {
            DocItem::Module(modu) => self.add_module(modu),
            DocItem::Object(obj) => {
                let mut items = HashMap::with_capacity(obj.members.len());
                for (name, member) in &obj.members {
                    items.insert(name.clone(), Ty::from_docs_member(member));
                }
                self.objects.insert(doc.id.name.clone(), items);
            }
            DocItem::Property(x) => {
                self.functions
                    .insert(doc.id.name.clone(), Ty::from_docs_property(x));
            }
            DocItem::Function(x) => {
                self.functions
                    .insert(doc.id.name.clone(), Ty::from_docs_function(x));
            }
        }
    }

    /// Add documentation usually from
    /// [`Globals::documentation`](crate::environment::Globals::documentation),
    /// overwriting any duplicates.
    pub fn add_module(&mut self, docs: &DocModule) {
        for (name, member) in &docs.members {
            self.functions
                .insert(name.clone(), Ty::from_docs_member(member));
        }
    }

    /// Is information known about this object.
    pub fn known_object(&self, name: &str) -> bool {
        self.objects.contains_key(name)
    }

    pub(crate) fn builtin(&self, name: &str) -> Result<Ty, ()> {
        Ok(self.functions.get(name).ok_or(())?.clone())
    }
}

impl TypingOracle for OracleDocs {
    fn attribute(&self, ty: &Ty, attr: TypingAttr) -> Option<Result<Ty, ()>> {
        match attr {
            TypingAttr::Regular(attr) => match self.objects.get(ty.as_name()?)?.get(attr) {
                None => Some(Err(())),
                Some(res) => Some(Ok(res.clone())),
            },
            _ => {
                // We don't record operator info in the docs, so it is always missing
                return None;
            }
        }
    }
}


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

pub(crate) mod ctx;
pub(crate) mod docs;
pub(crate) mod standard;
pub(crate) mod traits;


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

use crate::docs::Doc;
use crate::docs::DocItem;
use crate::environment::Globals;
use crate::stdlib::LibraryExtension;
use crate::typing::function::Param;
use crate::typing::oracle::docs::OracleDocs;
use crate::typing::oracle::traits::TypingAttr;
use crate::typing::oracle::traits::TypingBinOp;
use crate::typing::oracle::traits::TypingOracle;
use crate::typing::oracle::traits::TypingUnOp;
use crate::typing::ty::Ty;
use crate::typing::ty::TyName;
use crate::values::StarlarkValue;

/// A [`TypingOracle`] based on information from documentation.
pub struct OracleStandard {
    /// The things we don't have special code for, but just use whatever docs tells us
    fallback: OracleDocs,
}

impl OracleStandard {
    /// Create a new [`OracleDocs`], following the Starlark standard, but with the given extension you intend to enable.
    pub fn new(extensions: &[LibraryExtension]) -> Self {
        let mut fallback = OracleDocs::new();
        fallback.add_module(&Globals::extended_by(extensions).documentation());

        fn add<T: StarlarkValue<'static>>(fallback: &mut OracleDocs) {
            if let Some(m) = T::get_methods() {
                fallback.add_doc(&Doc::named_item(
                    T::TYPE.to_owned(),
                    DocItem::Object(m.documentation()),
                ));
            }
        }

        add::<crate::values::bool::StarlarkBool>(&mut fallback);
        add::<crate::values::enumeration::FrozenEnumType>(&mut fallback);
        add::<crate::values::float::StarlarkFloat>(&mut fallback);
        add::<crate::values::int::PointerI32>(&mut fallback);
        add::<crate::values::none::NoneType>(&mut fallback);
        add::<crate::values::range::Range>(&mut fallback);
        add::<crate::values::record::field::FrozenField>(&mut fallback);
        add::<crate::values::record::record_type::FrozenRecordType>(&mut fallback);
        add::<crate::values::regex::StarlarkRegex>(&mut fallback);
        add::<crate::values::dict::value::DictGen<crate::values::dict::value::FrozenDictData>>(
            &mut fallback,
        );
        add::<crate::values::list::value::ListGen<crate::values::list::value::FrozenListData>>(
            &mut fallback,
        );
        add::<crate::values::string::StarlarkStr>(&mut fallback);
        add::<crate::values::structs::value::FrozenStruct>(&mut fallback);
        add::<crate::values::tuple::value::FrozenTuple>(&mut fallback);

        Self { fallback }
    }
}

impl TypingOracle for OracleStandard {
    fn attribute(&self, ty: &Ty, attr: TypingAttr) -> Option<Result<Ty, ()>> {
        let fallback = || match self.fallback.attribute(ty, attr) {
            // We know we have full knowledge, so if we don't know, that must mean the attribute does not exist
            None => Some(Err(())),
            x => x,
        };
        // We have to explicitly implement operators (e.g. `__in__` since we don't generate documentation for them).
        // We explicitly implement polymorphic functions (e.g. `dict.get`) so they can get much more precise types.
        Some(Ok(match ty {
            Ty::List(elem) => match attr {
                TypingAttr::Slice => ty.clone(),
                TypingAttr::BinOp(TypingBinOp::Less) => {
                    // This is a bit weak, beacuse it only looks at this oracle
                    return self.attribute(ty, attr);
                }

                TypingAttr::Iter => (**elem).clone(),
                TypingAttr::BinOp(TypingBinOp::In) => {
                    Ty::function(vec![Param::pos_only((**elem).clone())], Ty::bool())
                }
                TypingAttr::Index => {
                    Ty::function(vec![Param::pos_only(Ty::int())], (**elem).clone())
                }
                TypingAttr::BinOp(TypingBinOp::Add) => {
                    Ty::function(vec![Param::pos_only(Ty::Any)], Ty::list(Ty::Any))
                }
                TypingAttr::BinOp(TypingBinOp::Mul) => {
                    Ty::function(vec![Param::pos_only(Ty::int())], ty.clone())
                }
                TypingAttr::Regular("pop") => Ty::function(
                    vec![Param::pos_only(Ty::int()).optional()],
                    (**elem).clone(),
                ),
                TypingAttr::Regular("index") => Ty::function(
                    vec![
                        Param::pos_only((**elem).clone()),
                        Param::pos_only(Ty::int()).optional(),
                    ],
                    Ty::int(),
                ),
                TypingAttr::Regular("remove") => {
                    Ty::function(vec![Param::pos_only((**elem).clone())], Ty::none())
                }
                _ => return fallback(),
            },
            Ty::Dict(tk_tv) => {
                let (ref tk, ref tv) = **tk_tv;
                match attr {
                    TypingAttr::BinOp(TypingBinOp::In) => {
                        Ty::function(vec![Param::pos_only(tk.clone())], Ty::bool())
                    }
                    TypingAttr::BinOp(TypingBinOp::BitOr) => {
                        Ty::function(vec![Param::pos_only(ty.clone())], ty.clone())
                    }
                    TypingAttr::Iter => tk.clone(),
                    TypingAttr::Index => {
                        Ty::function(vec![Param::pos_only(tk.clone())], tv.clone())
                    }
                    TypingAttr::Regular("get") => Ty::union2(
                        Ty::function(
                            vec![Param::pos_only(tk.clone())],
                            Ty::union2(tv.clone(), Ty::none()),
                        ),
                        // This second signature is a bit too lax, but get with a default is much rarer
                        Ty::function(
                            vec![Param::pos_only(tk.clone()), Param::pos_only(Ty::Any)],
                            Ty::Any,
                        ),
                    ),
                    TypingAttr::Regular("keys") => Ty::function(vec![], Ty::list(tk.clone())),
                    TypingAttr::Regular("values") => Ty::function(vec![], Ty::list(tv.clone())),
                    TypingAttr::Regular("items") => {
                        Ty::function(vec![], Ty::list(Ty::Tuple(vec![tk.clone(), tv.clone()])))
                    }
                    TypingAttr::Regular("popitem") => {
                        Ty::function(vec![], Ty::Tuple(vec![tk.clone(), tv.clone()]))
                    }
                    _ => return fallback(),
                }
            }
            Ty::Name(x) if x == "int" => match attr {
                TypingAttr::BinOp(TypingBinOp::Less) => {
                    Ty::function(vec![Param::pos_only(Ty::int())], Ty::bool())
                }
                TypingAttr::BinOp(TypingBinOp::Sub) => {
                    Ty::function(vec![Param::pos_only(Ty::int())], Ty::int())
                }
                TypingAttr::BinOp(TypingBinOp::Add) => {
                    Ty::function(vec![Param::pos_only(Ty::int())], Ty::int())
                }
                TypingAttr::UnOp(TypingUnOp::Minus) => Ty::function(vec![], Ty::int()),
                TypingAttr::BinOp(TypingBinOp::Div) => {
                    Ty::function(vec![Param::pos_only(Ty::Any)], Ty::float())
                }
                _ => return Some(Err(())),
            },
            Ty::Name(x) if x == "float" => match attr {
                TypingAttr::BinOp(TypingBinOp::Less) => {
                    Ty::function(vec![Param::pos_only(Ty::float())], Ty::bool())
                }
                TypingAttr::BinOp(TypingBinOp::Sub) => {
                    Ty::function(vec![Param::pos_only(Ty::float())], Ty::float())
                }
                TypingAttr::BinOp(TypingBinOp::Add) => {
                    Ty::function(vec![Param::pos_only(Ty::float())], Ty::float())
                }
                TypingAttr::UnOp(TypingUnOp::Minus) => Ty::function(vec![], Ty::float()),
                TypingAttr::BinOp(TypingBinOp::Div) => {
                    Ty::function(vec![Param::pos_only(Ty::Any)], Ty::float())
                }
                _ => return Some(Err(())),
            },
            Ty::Name(x) if x == "string" => match attr {
                TypingAttr::BinOp(TypingBinOp::Less) => {
                    Ty::function(vec![Param::pos_only(Ty::string())], Ty::bool())
                }
                TypingAttr::Index => Ty::function(vec![Param::pos_only(Ty::int())], Ty::string()),
                TypingAttr::BinOp(TypingBinOp::In) => {
                    Ty::function(vec![Param::pos_only(Ty::string())], Ty::bool())
                }
                TypingAttr::BinOp(TypingBinOp::Add) => {
                    Ty::function(vec![Param::pos_only(Ty::string())], Ty::string())
                }
                TypingAttr::BinOp(TypingBinOp::Mul) => {
                    Ty::function(vec![Param::pos_only(Ty::int())], Ty::string())
                }
                TypingAttr::Slice => Ty::string(),
                TypingAttr::BinOp(TypingBinOp::Percent) => {
                    Ty::function(vec![Param::pos_only(Ty::Any)], Ty::string())
                }
                _ => return fallback(),
            },
            Ty::Name(x) if x == "range" => match attr {
                TypingAttr::Iter => Ty::int(),
                TypingAttr::BinOp(TypingBinOp::In) => {
                    Ty::function(vec![Param::pos_only(Ty::int())], Ty::bool())
                }
                _ => return Some(Err(())),
            },
            _ => {
                let res = self.fallback.attribute(ty, attr);
                if res.is_none() && self.fallback.known_object(ty.as_name().unwrap_or_default()) {
                    return Some(Err(()));
                } else {
                    return res;
                }
            }
        }))
    }

    fn subtype(&self, _require: &TyName, _got: &TyName) -> bool {
        false
    }
}


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

// This makes for a better API.
#![allow(clippy::result_unit_err)]

use dupe::Dupe;

use crate::typing::function::TyFunction;
use crate::typing::ty::Ty;
use crate::typing::ty::TyName;

/// Unary operator for [`TypingOracle::attribute`].
#[derive(Copy, Clone, Dupe, Eq, PartialEq, derive_more::Display)]
pub enum TypingUnOp {
    /// `+`.
    #[display(fmt = "+")]
    Plus,
    /// `+`.
    #[display(fmt = "-")]
    Minus,
    /// `~`.
    #[display(fmt = "~")]
    BitNot,
}

/// Binary operator for [`TypingOracle::attribute`].
#[derive(Copy, Clone, Dupe, Eq, PartialEq, derive_more::Display)]
pub enum TypingBinOp {
    /// `+`.
    #[display(fmt = "+")]
    Add,
    /// `-`.
    #[display(fmt = "-")]
    Sub,
    /// `/`.
    #[display(fmt = "/")]
    Div,
    /// `//`.
    #[display(fmt = "/")]
    FloorDiv,
    /// `*`.
    #[display(fmt = "*")]
    Mul,
    /// `%`.
    #[display(fmt = "%")]
    Percent,
    /// `y in x`.
    #[display(fmt = "in")]
    In,
    /// `|`.
    #[display(fmt = "|")]
    BitOr,
    /// `^`.
    #[display(fmt = "^")]
    BitXor,
    /// `&`.
    #[display(fmt = "&")]
    BitAnd,
    /// `<`.
    #[display(fmt = "<")]
    Less,
    /// `<<`.
    #[display(fmt = "<<")]
    LeftShift,
    /// `>>`.
    #[display(fmt = ">>")]
    RightShift,
}

/// Attribute for [`TypingOracle::attribute`].
#[derive(Copy, Clone, Dupe, Eq, PartialEq, derive_more::Display)]
pub enum TypingAttr<'a> {
    /// Apply unary operator.
    #[display(fmt = "unary {}", _0)]
    UnOp(TypingUnOp),
    /// Apply binary operator.
    #[display(fmt = "binary {}", _0)]
    BinOp(TypingBinOp),
    /// `x[a:b:c]`.
    #[display(fmt = "[::]")]
    Slice,
    /// Return iterable element type.
    #[display(fmt = "iter")]
    Iter,
    /// `x[a]`, return the function.
    #[display(fmt = "[]")]
    Index,
    /// Get a regular attribute.
    Regular(&'a str),
}

/// Callbacks which provide types when typechecking a module.
///
/// The instance for slices/`Vec` allow composing a series of oracles
/// which are tried in order until one succeeds.
#[allow(unused_variables)] // Otherwise the types are bad in completions
pub trait TypingOracle {
    /// Given a type and a `.` attribute, what is its type.
    /// Return [`Err`] to indicate we _know_ this isn't a valid attribute.
    /// Return [`None`] if we aren't sure.
    fn attribute(&self, _ty: &Ty, _attr: TypingAttr) -> Option<Result<Ty, ()>> {
        None
    }

    /// If type is callable, return it as function signature.
    fn as_function(&self, ty: &TyName) -> Option<Result<TyFunction, ()>> {
        None
    }

    /// If we require the first type, but got the second type, is that OK?
    /// Usually its OK if the requirement is a subtype of the one we got.
    fn subtype(&self, require: &TyName, got: &TyName) -> bool {
        false
    }
}

/// Declare that there are no attributes, usually used at the end of a [`Vec`].
#[cfg(test)]
pub(crate) struct OracleNoAttributes;

#[cfg(test)]
impl TypingOracle for OracleNoAttributes {
    fn attribute(&self, _ty: &Ty, _attr: TypingAttr) -> Option<Result<Ty, ()>> {
        Some(Err(()))
    }
}

/// Sequence of oracles, first one that returns [`Some`] wins.
pub struct OracleSeq<T>(pub Vec<T>)
where
    T: TypingOracle;

impl<T: TypingOracle> TypingOracle for OracleSeq<T> {
    fn attribute(&self, ty: &Ty, attr: TypingAttr) -> Option<Result<Ty, ()>> {
        self.0.iter().find_map(|oracle| oracle.attribute(ty, attr))
    }

    fn as_function(&self, ty: &TyName) -> Option<Result<TyFunction, ()>> {
        self.0.iter().find_map(|oracle| oracle.as_function(ty))
    }

    fn subtype(&self, require: &TyName, got: &TyName) -> bool {
        self.0.iter().any(|oracle| oracle.subtype(require, got))
    }
}

pub(crate) struct OracleAny;

impl TypingOracle for OracleAny {}

// Forwarding traits

impl<'a, T: TypingOracle + ?Sized> TypingOracle for &'a T {
    fn attribute(&self, ty: &Ty, attr: TypingAttr) -> Option<Result<Ty, ()>> {
        (*self).attribute(ty, attr)
    }
    fn as_function(&self, ty: &TyName) -> Option<Result<TyFunction, ()>> {
        (*self).as_function(ty)
    }
    fn subtype(&self, require: &TyName, got: &TyName) -> bool {
        (*self).subtype(require, got)
    }
}

impl<T: TypingOracle + ?Sized> TypingOracle for Box<T> {
    fn attribute(&self, ty: &Ty, attr: TypingAttr) -> Option<Result<Ty, ()>> {
        self.as_ref().attribute(ty, attr)
    }
    fn as_function(&self, ty: &TyName) -> Option<Result<TyFunction, ()>> {
        self.as_ref().as_function(ty)
    }
    fn subtype(&self, require: &TyName, got: &TyName) -> bool {
        self.as_ref().subtype(require, got)
    }
}


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

//! This mod defines utilities to easily create Rust values as Starlark values.

use either::Either;

use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::FrozenHeap;
use crate::values::FrozenStringValue;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StringValue;
use crate::values::Value;

/// Trait for things that can be created on a [`Heap`] producing a [`Value`].
///
/// Note, this trait does not represent Starlark types.
/// For example, this trait is implemented for `char`,
/// but there's no Starlark type for `char`, this trait
/// is implemented for `char` to construct Starlark `str`.
pub trait AllocValue<'v>: StarlarkTypeRepr {
    /// Allocate the value on a heap and return a reference to the allocated value.
    ///
    /// Note, for certain values (e.g. empty strings) no allocation is actually performed,
    /// and a reference to the statically allocated object is returned.
    fn alloc_value(self, heap: &'v Heap) -> Value<'v>;
}

/// Type which allocates a string.
pub trait AllocStringValue<'v>: AllocValue<'v> + Sized {
    /// Allocate a string.
    fn alloc_string_value(self, heap: &'v Heap) -> StringValue<'v>;
}

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

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

impl<'v, T> AllocValue<'v> for Option<T>
where
    T: AllocValue<'v>,
{
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        match self {
            Some(v) => v.alloc_value(heap),
            None => Value::new_none(),
        }
    }
}

impl<'v, A: AllocValue<'v>, B: AllocValue<'v>> AllocValue<'v> for Either<A, B> {
    #[inline]
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        match self {
            Either::Left(a) => a.alloc_value(heap),
            Either::Right(b) => b.alloc_value(heap),
        }
    }
}

impl<A: AllocFrozenValue, B: AllocFrozenValue> AllocFrozenValue for Either<A, B> {
    #[inline]
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        match self {
            Either::Left(a) => a.alloc_frozen_value(heap),
            Either::Right(b) => b.alloc_frozen_value(heap),
        }
    }
}

/// Trait for things that can be allocated on a [`FrozenHeap`] producing a [`FrozenValue`].
pub trait AllocFrozenValue {
    /// Allocate a value in the frozen heap and return a reference to the allocated value.
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue;
}

/// Type which allocates a string.
pub trait AllocFrozenStringValue: AllocFrozenValue + Sized {
    /// Allocate a string.
    fn alloc_frozen_string_value(self, heap: &FrozenHeap) -> FrozenStringValue;
}

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


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

use std::cmp::Ordering;
use std::hash::Hash;

use itertools::Itertools;
use starlark_map::Equivalent;

use crate::collections::SmallMap;

pub(crate) fn equals_slice<E, X1, X2>(
    xs: &[X1],
    ys: &[X2],
    f: impl Fn(&X1, &X2) -> Result<bool, E>,
) -> Result<bool, E> {
    if xs.len() != ys.len() {
        return Ok(false);
    }
    for (x, y) in xs.iter().zip(ys) {
        if !f(x, y)? {
            return Ok(false);
        }
    }
    Ok(true)
}

pub(crate) fn equals_small_map<E, K1: Eq, K2: Eq, V1, V2>(
    x: &SmallMap<K1, V1>,
    y: &SmallMap<K2, V2>,
    f: impl Fn(&V1, &V2) -> Result<bool, E>,
) -> Result<bool, E>
where
    K1: Equivalent<K2>,
{
    if x.len() != y.len() {
        return Ok(false);
    }
    for (xk, xv) in x.iter_hashed() {
        match y.get_hashed(xk) {
            None => return Ok(false),
            Some(yv) => {
                if !f(xv, yv)? {
                    return Ok(false);
                }
            }
        }
    }
    Ok(true)
}

pub(crate) fn compare_slice<E, X1, X2>(
    xs: &[X1],
    ys: &[X2],
    f: impl Fn(&X1, &X2) -> Result<Ordering, E>,
) -> Result<Ordering, E> {
    for (x, y) in xs.iter().zip(ys) {
        let cmp = f(x, y)?;
        if cmp != Ordering::Equal {
            return Ok(cmp);
        }
    }
    Ok(xs.len().cmp(&ys.len()))
}

pub(crate) fn compare_small_map<E, K, K2: Ord + Hash, V1, V2>(
    x: &SmallMap<K, V1>,
    y: &SmallMap<K, V2>,
    key: impl Fn(&K) -> K2,
    f: impl Fn(&V1, &V2) -> Result<Ordering, E>,
) -> Result<Ordering, E> {
    // TODO(nga): this function is only used to compare structs,
    //   and it compares them incorrectly. This code is supposed to return `False`:
    //   ```
    //   struct(b=1) < struct(a=1, x=1)
    //   ```
    //   It returns `True`.
    let cmp = x.len().cmp(&y.len());
    if cmp != Ordering::Equal {
        return Ok(cmp);
    }
    for ((xk, xv), (yk, yv)) in x
        .iter()
        .sorted_by_key(|(k, _)| key(k))
        .zip(y.iter().sorted_by_key(|(k, _)| key(k)))
    {
        let key_cmp = key(xk).cmp(&key(yk));
        if key_cmp != Ordering::Equal {
            return Ok(key_cmp);
        }
        let value_cmp = f(xv, yv)?;
        if value_cmp != Ordering::Equal {
            return Ok(value_cmp);
        }
    }
    Ok(Ordering::Equal)
}


/*
 * 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::any::TypeId;
use std::marker::PhantomData;

use crate::any::AnyLifetime;
use crate::values::Value;

/// Taken by [`StarlarkValue::provide`](crate::values::StarlarkValue::provide)
/// to provide different data depending on the type.
pub struct Demand<'a, 'v> {
    type_id_of_t: TypeId,
    /// `&'a mut Option<T>`.
    option: *mut (),
    _marker: PhantomData<&'a mut &'v ()>,
}

impl<'a, 'v> Demand<'a, 'v> {
    fn new<T: AnyLifetime<'v>>(option: &mut Option<T>) -> Demand<'a, 'v> {
        Demand {
            type_id_of_t: T::static_type_id(),
            option: option as *mut _ as *mut (),
            _marker: PhantomData,
        }
    }

    /// Provide a value of given type.
    ///
    /// If type matches the type requested from [`Value::request_value`], the value is stored
    /// inside the [`Demand`] and later returned, otherwise the value is discarded.
    pub fn provide_value<T: AnyLifetime<'v>>(&mut self, value: T) {
        if self.type_id_of_t == T::static_type_id() {
            // SAFETY: check checked type.
            unsafe { *(self.option as *mut Option<T>) = Some(value) };
        }
    }
}

pub(crate) fn request_value_impl<'v, T: AnyLifetime<'v>>(value: Value<'v>) -> Option<T> {
    let mut option = None;
    value.get_ref().provide(&mut Demand::new(&mut option));
    option
}

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

    use crate as starlark;
    use crate::any::ProvidesStaticType;
    use crate::starlark_simple_value;
    use crate::values::demand::Demand;
    use crate::values::Heap;
    use crate::values::StarlarkValue;

    trait SomeTrait {
        fn payload(&self) -> u32;
    }

    // TODO(nga): implement derive for this.
    unsafe impl<'v> ProvidesStaticType<'v> for &'v dyn SomeTrait {
        type StaticType = &'static dyn SomeTrait;
    }

    #[derive(
        ProvidesStaticType,
        derive_more::Display,
        Debug,
        NoSerialize,
        Allocative
    )]
    #[display(fmt = "SomeType")]
    struct MyValue {
        payload: u32,
    }

    impl SomeTrait for MyValue {
        fn payload(&self) -> u32 {
            self.payload
        }
    }

    starlark_simple_value!(MyValue);

    #[starlark_value(type = "MyValue")]
    impl<'v> StarlarkValue<'v> for MyValue {
        fn provide(&'v self, demand: &mut Demand<'_, 'v>) {
            demand.provide_value::<&dyn SomeTrait>(self);
        }
    }

    #[test]
    fn test_trait_downcast() {
        let heap = Heap::new();
        let value = heap.alloc_simple(MyValue { payload: 17 });

        assert!(value.request_value::<String>().is_none());

        let some_trait = value.request_value::<&dyn SomeTrait>().unwrap();
        assert_eq!(17, some_trait.payload());
    }
}


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

//! Define a common set of errors.

use thiserror::Error;

use crate::values::StarlarkValue;
use crate::values::Value;

/// Common errors returned by Starlark evaluation.
#[derive(Debug, Error)]
#[allow(missing_docs)] // Self-explanatory.
pub enum ValueError {
    #[error("Operation `{op}` not supported on type `{typ}`")]
    OperationNotSupported { op: String, typ: String },
    #[error("Operation `{op}` not supported for types `{left}` and `{right}`")]
    OperationNotSupportedBinary {
        op: String,
        left: String,
        right: String,
    },
    #[error("Cannot divide by zero")]
    DivisionByZero,
    #[error("Integer overflow")]
    IntegerOverflow,
    #[error("Negative shift count")]
    NegativeShiftCount,
    #[error("Type of parameters mismatch, expected `{0}`, actual `{1}`")]
    IncorrectParameterTypeWithExpected(String, String),
    #[error("Type of parameter `{0}` doesn't match, expected `{1}`, actual `{2}`")]
    IncorrectParameterTypeNamedWithExpected(String, String, String),
    #[error("Type of parameters mismatch")]
    IncorrectParameterType,
    #[error("Type of parameter `{0}` doesn't match")]
    IncorrectParameterTypeNamed(String),
    #[error("Missing this parameter")]
    MissingThis,
    #[error("Missing required parameter `{0}`")]
    MissingRequired(String),
    #[error("Index `{0}` is out of bound")]
    IndexOutOfBound(i32),
    #[error("Key `{0}` was not found")]
    KeyNotFound(String),
    #[error("Immutable")]
    CannotMutateImmutableValue,
    #[error("This operation mutate an iterable for an iterator while iterating.")]
    MutationDuringIteration,
    #[error("Object of type `{0}` has no attribute `{1}`")]
    NoAttr(String, String),
    #[error("Object of type `{0}` has no attribute `{1}`, did you mean `{2}`?")]
    NoAttrDidYouMean(String, String, String),
}

#[derive(Debug, Error)]
pub(crate) enum ControlError {
    #[error("Value of type `{0}` is not hashable")]
    NotHashableValue(String),
    #[error("Too many recursion levels")]
    TooManyRecursionLevel,
}

impl ValueError {
    #[cold]
    pub(crate) fn unsupported_owned<T>(
        left: &str,
        op: &str,
        right: Option<&str>,
    ) -> anyhow::Result<T> {
        match right {
            None => Err(ValueError::OperationNotSupported {
                op: op.to_owned(),
                typ: left.to_owned(),
            }
            .into()),
            Some(right) => Err(ValueError::OperationNotSupportedBinary {
                op: op.to_owned(),
                left: left.to_owned(),
                right: right.to_owned(),
            }
            .into()),
        }
    }

    /// Helper to create an [`OperationNotSupported`](ValueError::OperationNotSupported) error.
    #[cold]
    pub fn unsupported<'v, T, V: StarlarkValue<'v> + ?Sized>(
        _left: &V,
        op: &str,
    ) -> anyhow::Result<T> {
        Self::unsupported_owned(V::TYPE, op, None)
    }

    /// Helper to create an [`OperationNotSupported`](ValueError::OperationNotSupportedBinary) error.
    #[cold]
    pub fn unsupported_with<'v, T, V: StarlarkValue<'v> + ?Sized>(
        _left: &V,
        op: &str,
        right: Value,
    ) -> anyhow::Result<T> {
        Self::unsupported_owned(V::TYPE, op, Some(right.get_type()))
    }
}