/*
 * 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 proc_macro2::TokenStream;
use quote::quote;
use quote::quote_spanned;
use syn::parse::ParseStream;
use syn::parse_macro_input;
use syn::spanned::Spanned;
use syn::FnArg;
use syn::ItemTrait;
use syn::Pat;
use syn::ReturnType;
use syn::TraitItem;
use syn::TraitItemFn;

struct Gen {
    starlark_value: ItemTrait,
}

struct VTableEntry {
    field: TokenStream,
    init: TokenStream,
    init_for_black_hole: TokenStream,
}

#[derive(Debug, Default)]
struct StarlarkInternalVTableAttrs {
    skip: bool,
}

impl syn::parse::Parse for StarlarkInternalVTableAttrs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let ident = input.parse::<syn::Ident>()?;
        if ident == "skip" {
            Ok(StarlarkInternalVTableAttrs { skip: true })
        } else {
            Err(syn::Error::new(ident.span(), "unknown attribute"))
        }
    }
}

impl Gen {
    fn vtable_entry(&self, method: &TraitItemFn) -> syn::Result<VTableEntry> {
        let fn_name = &method.sig.ident;
        let fn_ret_type = &method.sig.output;
        let mut field_fn_param_types = Vec::new();
        let mut field_params_names = Vec::new();
        let mut field_init_args = Vec::new();
        for param in &method.sig.inputs {
            match param {
                FnArg::Receiver(_) => {
                    field_fn_param_types.push(quote_spanned! {method.sig.span()=>
                        crate::values::layout::vtable::StarlarkValueRawPtr
                    });
                    field_params_names.push(quote_spanned! {method.sig.span()=>
                        this
                    });
                    field_init_args.push(quote_spanned! {method.sig.span()=>
                        this.value_ref::<T>()
                    });
                }
                FnArg::Typed(p) => {
                    let name = match &*p.pat {
                        Pat::Ident(p) => p.ident.clone(),
                        _ => return Err(syn::Error::new(p.span(), "parameter must be identifier")),
                    };
                    let ty = &p.ty;
                    field_fn_param_types.push(quote_spanned! {method.sig.span()=>
                        #ty
                    });
                    field_params_names.push(quote_spanned! {method.sig.span()=>
                        #name
                    });
                    field_init_args.push(quote_spanned! {method.sig.span()=>
                        // We do `transmute` to get rid of lifetimes, see below.
                        std::mem::transmute(#name)
                    });
                }
            }
        }
        let field_init_param_pairs: Vec<TokenStream> = field_fn_param_types
            .iter()
            .zip(field_params_names.iter())
            .map(|(ty, name)| {
                quote_spanned! {method.sig.span()=>
                    #name: #ty
                }
            })
            .collect();
        let ret = match &method.sig.output {
            ReturnType::Default => quote! {},
            ReturnType::Type(_, ty) => {
                quote_spanned! {method.sig.span()=>
                    -> #ty
                }
            }
        };
        let field = quote_spanned! {method.sig.span()=>
            pub(crate) #fn_name: for<'a, 'v> fn(
                #(#field_fn_param_types),*
            ) #ret
        };
        let init = quote_spanned! {method.sig.span()=>
            #fn_name: {
                // It is important to put vtable entry into named function
                // instead of anonymous callback so function name is meaningful in profiler output.
                fn #fn_name<'a, 'v, 'v2, T: StarlarkValue<'v2>>(#(#field_init_param_pairs),*) #fn_ret_type {
                    unsafe {
                        // The problem is that it is concrete `'v` in
                        // ```
                        // struct StarlarkValueVTableGet<'v, T: StarlarkValue<'v>
                        // ```
                        // but we must generate vtable entry `for<'v>`.
                        //
                        // If Rust supported something like:
                        // ```
                        // struct StarlarkValueVTableGet<for<'v> T<'v>: StarlarkValue<'v>>
                        // ```
                        // it would be possible. But it doesn't. Hence all the transmutes.
                        std::mem::transmute(
                            T::#fn_name(#(#field_init_args),*)
                        )
                    }
                }
                #fn_name::<T>
            }
        };
        let init_for_black_hole = quote_spanned! {method.sig.span()=>
            #fn_name: |#(#field_params_names),*| {
                panic!("BlackHole")
            }
        };
        Ok(VTableEntry {
            field,
            init,
            init_for_black_hole,
        })
    }

    fn process_item_attrs(
        &self,
        attrs: &[syn::Attribute],
    ) -> syn::Result<(StarlarkInternalVTableAttrs, Vec<syn::Attribute>)> {
        let mut new_attrs = Vec::new();
        let mut item_attrs: Option<StarlarkInternalVTableAttrs> = None;
        for attr in attrs {
            if attr.path().is_ident("starlark_internal_vtable") {
                if item_attrs.is_some() {
                    return Err(syn::Error::new(attr.span(), "duplicate attribute"));
                }
                item_attrs = Some(attr.parse_args()?);
            } else {
                new_attrs.push(attr.clone());
            }
        }
        Ok((item_attrs.unwrap_or_default(), new_attrs))
    }

    fn gen_starlark_value_vtable(&self) -> syn::Result<TokenStream> {
        let mut fields = Vec::new();
        let mut inits = Vec::new();
        let mut init_black_holes = Vec::new();
        let mut starlark_value = self.starlark_value.clone();
        for item in &mut starlark_value.items {
            let m = match item {
                TraitItem::Fn(m) => m,
                _ => continue,
            };

            let (item_attrs, new_attrs) = self.process_item_attrs(&m.attrs)?;
            m.attrs = new_attrs;
            if item_attrs.skip {
                continue;
            }

            let VTableEntry {
                field,
                init,
                init_for_black_hole,
            } = self.vtable_entry(m)?;
            fields.push(field);
            inits.push(init);
            init_black_holes.push(init_for_black_hole);
        }

        Ok(quote_spanned! {
            self.starlark_value.span() =>

            #starlark_value

            pub(crate) struct StarlarkValueVTable {
                #(#fields),*
            }

            #[allow(clippy::all)]
            #[allow(unused_variables)]
            impl StarlarkValueVTable {
                pub(crate) const BLACK_HOLE: StarlarkValueVTable = StarlarkValueVTable {
                    #(#init_black_holes),*
                };
            }

            pub(crate) struct StarlarkValueVTableGet<'v, T: StarlarkValue<'v>>(std::marker::PhantomData<&'v T>);

            #[allow(clippy::all)]
            #[allow(unused_variables)]
            impl<'v, T: StarlarkValue<'v>> StarlarkValueVTableGet<'v, T> {
                pub(crate) const VTABLE: StarlarkValueVTable = StarlarkValueVTable {
                    #(#inits),*
                };
            }
        })
    }
}

/// Generates the `StarlarkValueVTable` and friends.
/// At the moment of writing this macro generated: P499428269,
/// output of `cargo expand -p starlark --lib values::traits`.
pub(crate) fn starlark_internal_vtable(
    _attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let starlark_value = parse_macro_input!(input as ItemTrait);

    let gen = Gen { starlark_value };
    let generated = match gen.gen_starlark_value_vtable() {
        Ok(generated) => generated,
        Err(error) => {
            return error.to_compile_error().into();
        }
    };

    proc_macro::TokenStream::from(quote! {
        #generated
    })
}


/*
 * 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 parse;
mod render;
mod typ;
mod util;

use proc_macro::TokenStream;
use syn::parse_macro_input;
use syn::ItemFn;

pub(crate) fn starlark_module(attr: TokenStream, input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as ItemFn);

    fn starlark_module_impl(attr: TokenStream, input: ItemFn) -> syn::Result<TokenStream> {
        assert!(attr.is_empty());
        let x = parse::parse(input)?;
        Ok(render::render(x)?.into())
    }

    match starlark_module_impl(attr, input) {
        Ok(x) => x,
        Err(e) => e.to_compile_error().into(),
    }
}


/*
 * 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 dupe::Dupe;
use proc_macro2::Ident;
use proc_macro2::Span;
use syn::spanned::Spanned;
use syn::Attribute;
use syn::Block;
use syn::Expr;
use syn::Type;
use syn::Visibility;

use crate::module::parse::ModuleKind;
use crate::module::util::is_type_name;
use crate::module::util::unpack_option;

#[derive(Debug)]
pub(crate) struct StarModule {
    pub module_kind: ModuleKind,
    pub visibility: Visibility,
    // We reuse the users globals_builder to make sure `use` statements etc
    // make sense
    pub globals_builder: Type,
    pub name: Ident,
    pub attrs: Vec<Attribute>,
    pub docstring: Option<String>,
    pub stmts: Vec<StarStmt>,
}

impl StarModule {
    pub(crate) fn span(&self) -> Span {
        let mut span = self.name.span();
        for stmt in &self.stmts {
            if let Some(new_span) = span.join(stmt.span()) {
                span = new_span;
            }
        }
        span
    }
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub(crate) enum StarStmt {
    Const(StarConst),
    Fun(StarFun),
    Attr(StarAttr),
}

impl StarStmt {
    pub(crate) fn span(&self) -> Span {
        match self {
            StarStmt::Const(c) => c.span(),
            StarStmt::Fun(c) => c.span(),
            StarStmt::Attr(c) => c.span(),
        }
    }
}

#[derive(Debug)]
pub(crate) struct StarConst {
    pub name: Ident,
    pub ty: Type,
    pub value: Expr,
}

impl StarConst {
    pub(crate) fn span(&self) -> Span {
        self.name
            .span()
            .join(self.value.span())
            .unwrap_or_else(|| self.name.span())
    }
}

#[derive(Debug)]
pub(crate) struct SpecialParam {
    pub(crate) ident: Ident,
    pub(crate) ty: Type,
}

#[derive(Debug)]
pub(crate) struct StarFun {
    pub name: Ident,
    pub as_type: Option<syn::Path>,
    pub attrs: Vec<Attribute>,
    pub args: Vec<StarArg>,
    /// Has `&Heap` parameter.
    pub heap: Option<SpecialParam>,
    /// Has `&mut Evaluator` parameter.
    pub eval: Option<SpecialParam>,
    /// `anyhow::Result<T>`.
    pub return_type: Type,
    pub starlark_ty_custom_function: Option<Expr>,
    pub special_builtin_function: Option<Expr>,
    pub speculative_exec_safe: bool,
    pub body: Block,
    pub source: StarFunSource,
    pub docstring: Option<String>,
}

impl StarFun {
    /// Is this function a method? (I. e. has `this` as first parameter).
    pub(crate) fn is_method(&self) -> bool {
        match self.args.first() {
            Some(first) => {
                assert!(first.source != StarArgSource::Unknown, "not yet resolved");
                first.source == StarArgSource::This
            }
            None => false,
        }
    }

    pub(crate) fn span(&self) -> Span {
        self.name
            .span()
            .join(self.body.span())
            .unwrap_or_else(|| self.name.span())
    }

    pub(crate) fn args_span(&self) -> Span {
        self.args
            .iter()
            .map(|a| a.span)
            .reduce(|a, b| a.join(b).unwrap_or(a))
            .unwrap_or_else(|| self.name.span())
    }
}

#[derive(Debug)]
pub(crate) struct StarAttr {
    pub name: Ident,
    pub arg: Type,
    /// Has `&Heap` parameter.
    pub heap: Option<SpecialParam>,
    pub attrs: Vec<Attribute>,
    /// `anyhow::Result<T>`.
    pub return_type: Type,
    /// `T`.
    pub return_type_arg: Type,
    pub speculative_exec_safe: bool,
    pub body: Block,
    pub docstring: Option<String>,
}

impl StarAttr {
    pub(crate) fn span(&self) -> Span {
        self.name
            .span()
            .join(self.body.span())
            .unwrap_or_else(|| self.name.span())
    }
}

#[derive(Debug, PartialEq, Copy, Clone, Dupe)]
pub(crate) enum StarArgPassStyle {
    /// Receiver.
    This,
    /// Parameter can be filled only positionally.
    PosOnly,
    /// Parameter can filled both positionally and by name.
    PosOrNamed,
    /// Parameter can be filled by name.
    NamedOnly,
    /// `*args`.
    Args,
    /// `**kwargs`.
    Kwargs,
    /// `&Arguments`.
    Arguments,
}

#[derive(Debug)]
pub(crate) struct StarArg {
    pub span: Span,
    pub attrs: Vec<Attribute>,
    pub mutable: bool,
    pub pass_style: StarArgPassStyle,
    pub name: Ident,
    pub ty: Type,
    pub default: Option<Expr>,
    pub source: StarArgSource,
}

#[derive(Debug, PartialEq)]
pub(crate) enum StarArgSource {
    Unknown,
    This,
    Parameters,
    Argument(usize),
    Required(usize),
    Optional(usize),
}

#[derive(Debug)]
pub(crate) enum StarFunSource {
    /// Function signature is single `Arguments` parameter.
    Arguments,
    /// Function signature is `this` parameter followed by `Arguments` parameter.
    ThisArguments,
    /// Normal function which uses a signature and parameters parser.
    Signature { count: usize },
    /// Fast-path function of some required parameters, followed by some optional parameters.
    /// No named parameters or `*args`/`**kwargs`.
    Positional { required: usize, optional: usize },
}

impl StarArg {
    pub fn is_option(&self) -> bool {
        is_type_name(&self.ty, "Option")
    }

    /// Remove the `Option` if it exists, otherwise return the real type.
    pub fn without_option(&self) -> &Type {
        unpack_option(&self.ty).unwrap_or(&self.ty)
    }

    pub fn is_value(&self) -> bool {
        is_type_name(&self.ty, "Value")
    }

    pub fn requires_signature(&self) -> bool {
        // We need to use a signature if something has a name
        // There are *args or **kwargs
        // There is a default that needs promoting to a Value (since the signature stores that value)
        self.pass_style != StarArgPassStyle::PosOnly
            || self.pass_style == StarArgPassStyle::Args
            || self.pass_style == StarArgPassStyle::Kwargs
            || (self.is_value() && self.default.is_some())
    }
}


/*
 * 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 proc_macro2::TokenStream;
use quote::quote;
use syn::*;

/// Is a type matching a given name
pub(crate) fn is_type_name(x: &Type, name: &str) -> bool {
    if let Type::Path(TypePath {
        path: Path { segments, .. },
        ..
    }) = x
    {
        if let Some(seg1) = segments.last() {
            return seg1.ident == name;
        }
    }
    false
}

/// If the type is `Option`, return the type inside it.
pub(crate) fn unpack_option(x: &Type) -> Option<&Type> {
    if let Type::Path(TypePath {
        path: Path { segments, .. },
        ..
    }) = x
    {
        if let Some(seg1) = segments.last() {
            if seg1.ident == "Option" {
                if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
                    args, ..
                }) = &seg1.arguments
                {
                    if let Some(GenericArgument::Type(x)) = args.first() {
                        return Some(x);
                    }
                }
            }
        }
    }
    None
}

pub(crate) fn ident_string(x: &Ident) -> String {
    let x = x.to_string();
    match x.strip_prefix("r#") {
        Some(x) => x.to_owned(),
        None => x,
    }
}

pub(crate) fn mut_token(x: bool) -> TokenStream {
    if x {
        quote! {mut}
    } else {
        quote! {}
    }
}


/*
 * 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 proc_macro2::Ident;
use proc_macro2::Span;
use syn::parse::ParseStream;
use syn::spanned::Spanned;
use syn::visit::Visit;
use syn::Attribute;
use syn::Expr;
use syn::FnArg;
use syn::GenericArgument;
use syn::GenericParam;
use syn::Generics;
use syn::ItemFn;
use syn::Lifetime;
use syn::Pat;
use syn::PatType;
use syn::PathArguments;
use syn::ReturnType;
use syn::Token;
use syn::Type;

use crate::module::parse::is_attribute_docstring;
use crate::module::parse::is_mut_something;
use crate::module::parse::is_ref_something;
use crate::module::parse::parse_visibility;
use crate::module::parse::ModuleKind;
use crate::module::typ::SpecialParam;
use crate::module::typ::StarArg;
use crate::module::typ::StarArgPassStyle;
use crate::module::typ::StarArgSource;
use crate::module::typ::StarAttr;
use crate::module::typ::StarFun;
use crate::module::typ::StarFunSource;
use crate::module::typ::StarStmt;

#[derive(Default)]
struct FnAttrs {
    is_attribute: bool,
    as_type: Option<syn::Path>,
    starlark_ty_custom_function: Option<Expr>,
    special_builtin_function: Option<Expr>,
    speculative_exec_safe: bool,
    docstring: Option<String>,
    /// Rest attributes
    attrs: Vec<Attribute>,
}

#[derive(Default)]
struct FnParamAttrs {
    default: Option<Expr>,
    this: bool,
    pos_only: bool,
    named_only: bool,
    args: bool,
    kwargs: bool,
    unused_attrs: Vec<Attribute>,
}

/// Parse `#[starlark(...)]` fn param attribute.
fn parse_starlark_fn_param_attr(
    tokens: &Attribute,
    param_attrs: &mut FnParamAttrs,
) -> syn::Result<()> {
    assert!(tokens.path().is_ident("starlark"));
    let parse = |parser: ParseStream| -> syn::Result<()> {
        let mut first = true;
        while !parser.is_empty() {
            if !first {
                parser.parse::<Token![,]>()?;
                // Allow trailing comma.
                if parser.is_empty() {
                    break;
                }
            }
            first = false;

            let ident = parser.parse::<Ident>()?;
            if ident == "default" {
                parser.parse::<Token![=]>()?;
                param_attrs.default = Some(parser.parse::<Expr>()?);
                continue;
            } else if ident == "this" {
                param_attrs.this = true;
                continue;
            } else if ident == "args" {
                param_attrs.args = true;
                continue;
            } else if ident == "kwargs" {
                param_attrs.kwargs = true;
                continue;
            } else if ident == "require" {
                parser.parse::<Token!(=)>()?;
                let require = parser.parse::<Ident>()?;
                if require == "pos" {
                    param_attrs.pos_only = true;
                    continue;
                } else if require == "named" {
                    param_attrs.named_only = true;
                    continue;
                }
            }

            return Err(syn::Error::new(
                ident.span(),
                "Expecting \
                `#[starlark(default = expr)]`, \
                `#[starlark(require = pos)]`, \
                `#[starlark(require = named)]`, \
                `#[starlark(this)]` attribute",
            ));
        }
        Ok(())
    };
    tokens.parse_args_with(parse)
}

/// Parse fn param attributes: parse `#[starlark(...)]` and take others as is.
fn parse_fn_param_attrs(attrs: Vec<Attribute>) -> syn::Result<FnParamAttrs> {
    let mut param_attrs = FnParamAttrs::default();
    for attr in attrs {
        if attr.path().is_ident("starlark") {
            parse_starlark_fn_param_attr(&attr, &mut param_attrs)?;
        } else {
            param_attrs.unused_attrs.push(attr);
        }
    }
    Ok(param_attrs)
}

/// Parse `#[starlark(...)]` fn attribute.
fn parse_starlark_fn_attr(tokens: &Attribute, attrs: &mut FnAttrs) -> syn::Result<()> {
    assert!(tokens.path().is_ident("starlark"));
    let parse = |parser: ParseStream| -> syn::Result<()> {
        let mut first = true;
        while !parser.is_empty() {
            if !first {
                parser.parse::<Token![,]>()?;
                // Allow trailing comma.
                if parser.is_empty() {
                    break;
                }
            }
            first = false;

            let ident = parser.parse::<Ident>()?;
            if ident == "as_type" {
                parser.parse::<Token![=]>()?;
                attrs.as_type = Some(parser.parse::<syn::Path>()?);
                continue;
            } else if ident == "attribute" {
                attrs.is_attribute = true;
                continue;
            } else if ident == "speculative_exec_safe" {
                attrs.speculative_exec_safe = true;
                continue;
            } else if ident == "ty_custom_function" {
                parser.parse::<Token![=]>()?;
                attrs.starlark_ty_custom_function = Some(parser.parse::<Expr>()?);
                continue;
            } else if ident == "special_builtin_function" {
                parser.parse::<Token![=]>()?;
                attrs.special_builtin_function = Some(parser.parse::<Expr>()?);
                continue;
            }
            return Err(syn::Error::new(
                ident.span(),
                "Expecting \
                    `#[starlark(as_type = ImplStarlarkValue)]`, \
                    `#[starlark(ty_custom_function = MyTy)]`, \
                    `#[starlark(attribute)]`, \
                    `#[starlark(speculative_exec_safe)]` attribute",
            ));
        }

        Ok(())
    };
    tokens.parse_args_with(parse)
}

/// Parse `#[starlark(...)]` attribute.
fn parse_fn_attrs(span: Span, xs: Vec<Attribute>) -> syn::Result<FnAttrs> {
    let mut res = FnAttrs::default();
    for x in xs {
        if x.path().is_ident("starlark") {
            parse_starlark_fn_attr(&x, &mut res)?;
        } else if let Some(ds) = is_attribute_docstring(&x) {
            match &mut res.docstring {
                None => res.docstring = Some(ds),
                Some(docstring) => {
                    docstring.push('\n');
                    docstring.push_str(&ds);
                }
            }
            // Important the attributes remain tagged to the function, so the test annotations
            // are present, and thus the doc test works properly.
            res.attrs.push(x);
        } else {
            res.attrs.push(x);
        }
    }
    if res.is_attribute && res.as_type.is_some() {
        return Err(syn::Error::new(span, "Can't be an attribute with a .type"));
    }
    Ok(res)
}

/// Check if given type is `anyhow::Result<T>`, and if it is, return `T`.
fn is_anyhow_result(t: &Type) -> Option<Type> {
    let path = match t {
        Type::Path(p) => p,
        _ => return None,
    };
    if path.qself.is_some() {
        return None;
    }
    let mut segments = path.path.segments.iter();
    match segments.next() {
        None => return None,
        Some(s) if s.ident != "anyhow" => return None,
        _ => {}
    };
    let result = match segments.next() {
        None => return None,
        Some(s) if s.ident != "Result" => return None,
        Some(result) => result,
    };
    if segments.next().is_some() {
        return None;
    }
    let result_arguments = match &result.arguments {
        PathArguments::AngleBracketed(args) => args,
        _ => return None,
    };
    let mut result_arguments = result_arguments.args.iter();
    let t = match result_arguments.next() {
        None => return None,
        Some(t) => t,
    };
    match t {
        GenericArgument::Type(t) => Some(t.clone()),
        _ => None,
    }
}

// Add a function to the `GlobalsModule` named `globals_builder`.
pub(crate) fn parse_fun(func: ItemFn, module_kind: ModuleKind) -> syn::Result<StarStmt> {
    parse_visibility(&func.vis)?;

    let sig_span = func.sig.span();

    let FnAttrs {
        is_attribute,
        as_type,
        speculative_exec_safe,
        docstring,
        starlark_ty_custom_function,
        special_builtin_function,
        attrs,
    } = parse_fn_attrs(func.span(), func.attrs)?;

    let has_v = parse_fn_generics(&func.sig.generics)?;

    let (return_type, return_type_arg) = parse_fn_output(&func.sig.output, func.sig.span(), has_v)?;

    let mut eval = None;
    let mut heap = None;

    let mut seen_star_args = false;
    let mut args = Vec::new();
    for (i, arg) in func.sig.inputs.into_iter().enumerate() {
        let span = arg.span();
        let parsed_arg = parse_arg(arg, has_v, seen_star_args, module_kind, i)?;
        match parsed_arg {
            StarArgOrSpecial::Heap(special) => {
                if heap.is_some() {
                    return Err(syn::Error::new(span, "Repeated `&Heap` parameter"));
                }
                heap = Some(special);
            }
            StarArgOrSpecial::Eval(special) => {
                if eval.is_some() {
                    return Err(syn::Error::new(span, "Repeated `&mut Evaluator` parameter"));
                }
                eval = Some(special);
            }
            StarArgOrSpecial::StarArg(arg) => {
                if arg.pass_style == StarArgPassStyle::Args {
                    seen_star_args = true;
                }
                args.push(arg);
            }
        }
    }

    if eval.is_some() && heap.is_some() {
        return Err(syn::Error::new(
            sig_span,
            "Can't have both `&mut Evaluator` and `&Heap` parameters",
        ));
    }

    if is_attribute {
        if eval.is_some() {
            return Err(syn::Error::new(
                sig_span,
                "Attributes cannot have `&mut Evaluator` parameter",
            ));
        }

        if args.len() != 1 {
            return Err(syn::Error::new(
                sig_span,
                "Attribute function must have single parameter",
            ));
        }
        let arg = args.pop().unwrap();
        if arg.pass_style != StarArgPassStyle::This {
            return Err(syn::Error::new(
                sig_span,
                "Attribute function must have `this` as the only parameter",
            ));
        }
        if arg.default.is_some() {
            return Err(syn::Error::new(
                sig_span,
                "Attribute function `this` parameter have no default value",
            ));
        }
        if starlark_ty_custom_function.is_some() {
            return Err(syn::Error::new(
                sig_span,
                "Attribute function cannot types are not implemented",
            ));
        }
        Ok(StarStmt::Attr(StarAttr {
            name: func.sig.ident,
            arg: arg.ty,
            heap,
            attrs,
            return_type,
            return_type_arg,
            speculative_exec_safe,
            body: *func.block,
            docstring,
        }))
    } else {
        let is_method = !args.is_empty() && args[0].pass_style == StarArgPassStyle::This;
        if is_method != (module_kind == ModuleKind::Methods) {
            return Err(syn::Error::new(
                sig_span,
                "Methods can only be defined in methods module",
            ));
        }

        if is_method && starlark_ty_custom_function.is_some() {
            return Err(syn::Error::new(
                sig_span,
                "Custom types are not implemented for methods",
            ));
        }

        let source = resolve_args(&mut args)?;

        let fun = StarFun {
            name: func.sig.ident,
            as_type,
            attrs,
            args,
            heap,
            eval,
            return_type,
            starlark_ty_custom_function,
            special_builtin_function,
            speculative_exec_safe,
            body: *func.block,
            source,
            docstring,
        };
        Ok(StarStmt::Fun(fun))
    }
}

#[allow(clippy::branches_sharing_code)] // False positive
fn resolve_args(args: &mut [StarArg]) -> syn::Result<StarFunSource> {
    if args.len() == 1 && args[0].pass_style == StarArgPassStyle::Arguments {
        args[0].source = StarArgSource::Parameters;
        Ok(StarFunSource::Arguments)
    } else if args.len() == 2
        && args[0].pass_style == StarArgPassStyle::This
        && args[1].pass_style == StarArgPassStyle::Arguments
    {
        args[0].source = StarArgSource::This;
        args[1].source = StarArgSource::Parameters;
        Ok(StarFunSource::ThisArguments)
    } else {
        let use_arguments = args
            .iter()
            .filter(|x| x.pass_style != StarArgPassStyle::This)
            .any(|x| x.requires_signature());
        if use_arguments {
            let mut count = 0;
            for x in args.iter_mut() {
                if x.pass_style == StarArgPassStyle::This {
                    x.source = StarArgSource::This;
                } else {
                    x.source = StarArgSource::Argument(count);
                    count += 1;
                }
            }
            Ok(StarFunSource::Signature { count })
        } else {
            let mut required = 0;
            let mut optional = 0;
            for x in args.iter_mut() {
                if x.pass_style == StarArgPassStyle::This {
                    x.source = StarArgSource::This;
                    continue;
                }
                if optional == 0 && x.default.is_none() && !x.is_option() {
                    x.source = StarArgSource::Required(required);
                    required += 1;
                } else {
                    x.source = StarArgSource::Optional(optional);
                    optional += 1;
                }
            }
            Ok(StarFunSource::Positional { required, optional })
        }
    }
}

/// Check there are no undeclared lifetiems in return type.
fn check_lifetimes_in_return_type(return_type: &ReturnType, has_v: bool) -> syn::Result<()> {
    match return_type {
        ReturnType::Default => Ok(()),
        ReturnType::Type(_, ty) => check_lifetimes_in_type(ty, has_v),
    }
}

/// Check there are no undeclared lifetiems in type.
fn check_lifetimes_in_type(ty: &Type, has_v: bool) -> syn::Result<()> {
    struct VisitImpl {
        has_v: bool,
        result: syn::Result<()>,
    }

    impl<'ast> Visit<'ast> for VisitImpl {
        #[allow(clippy::collapsible_if)]
        fn visit_lifetime(&mut self, lifetime: &'ast Lifetime) {
            if self.result.is_ok() {
                if lifetime.ident != "_" {
                    if lifetime.ident != "v" {
                        self.result = Err(syn::Error::new(
                            lifetime.span(),
                            "Only 'v lifetime is allowed",
                        ));
                    }
                    if !self.has_v {
                        self.result = Err(syn::Error::new(lifetime.span(), "Undeclared lifetime"));
                    }
                }
            }
        }
    }

    let mut visit = VisitImpl {
        has_v,
        result: Ok(()),
    };

    visit.visit_type(ty);

    visit.result
}

fn parse_fn_output(return_type: &ReturnType, span: Span, has_v: bool) -> syn::Result<(Type, Type)> {
    check_lifetimes_in_return_type(return_type, has_v)?;
    match return_type {
        ReturnType::Default => Err(syn::Error::new(span, "Function must have a return type")),
        ReturnType::Type(_, x) => match is_anyhow_result(x) {
            Some(return_arg_type) => Ok(((**x).clone(), return_arg_type)),
            None => Err(syn::Error::new(
                return_type.span(),
                "Function return type must be precisely `anyhow::Result<...>`",
            )),
        },
    }
}

fn parse_fn_generics(generics: &Generics) -> syn::Result<bool> {
    let mut seen_v = false;
    for param in &generics.params {
        match param {
            GenericParam::Type(..) => {
                return Err(syn::Error::new(
                    param.span(),
                    "Function cannot have type parameters",
                ));
            }
            GenericParam::Const(..) => {
                return Err(syn::Error::new(
                    param.span(),
                    "Function cannot have const parameters",
                ));
            }
            GenericParam::Lifetime(lifetime) => {
                if lifetime.lifetime.ident != "v" {
                    return Err(syn::Error::new(
                        lifetime.lifetime.span(),
                        "Function cannot have lifetime parameters other than `v",
                    ));
                }
                if !lifetime.bounds.is_empty() {
                    return Err(syn::Error::new(
                        lifetime.span(),
                        "Function lifetime params must not have bounds",
                    ));
                }
                if seen_v {
                    return Err(syn::Error::new(lifetime.span(), "Duplicate `v parameters"));
                }
                seen_v = true;
            }
        }
    }
    Ok(seen_v)
}

#[allow(clippy::large_enum_variant)]
enum StarArgOrSpecial {
    StarArg(StarArg),
    /// `&mut Evaluator`.
    Eval(SpecialParam),
    /// `&Heap`.
    Heap(SpecialParam),
}

/// Function parameter is `eval: &mut Evaluator`.
fn is_eval(ident: &Ident, ty: &Type) -> syn::Result<Option<SpecialParam>> {
    if is_mut_something(ty, "Evaluator") {
        Ok(Some(SpecialParam {
            ident: ident.clone(),
            ty: ty.clone(),
        }))
    } else {
        Ok(None)
    }
}

/// Function parameter is `heap: &Heap`.
fn is_heap(ident: &Ident, ty: &Type) -> syn::Result<Option<SpecialParam>> {
    if is_ref_something(ty, "Heap") {
        Ok(Some(SpecialParam {
            ident: ident.clone(),
            ty: ty.clone(),
        }))
    } else {
        Ok(None)
    }
}

#[allow(clippy::collapsible_else_if)]
fn parse_arg(
    x: FnArg,
    has_v: bool,
    seen_star_args: bool,
    module_kind: ModuleKind,
    param_index: usize,
) -> syn::Result<StarArgOrSpecial> {
    let this = module_kind == ModuleKind::Methods && param_index == 0;

    let span = x.span();
    match x {
        FnArg::Typed(PatType { attrs, pat, ty, .. }) => {
            let ident = if let Pat::Ident(ident) = *pat {
                ident
            } else {
                return Err(syn::Error::new(
                    pat.span(),
                    "Function parameter pattern must be identifier",
                ));
            };
            if let Some(heap) = is_heap(&ident.ident, &ty)? {
                if this {
                    return Err(syn::Error::new(
                        span,
                        "Receiver parameter cannot be `&Heap`",
                    ));
                }
                return Ok(StarArgOrSpecial::Heap(heap));
            } else if let Some(eval) = is_eval(&ident.ident, &ty)? {
                if this {
                    return Err(syn::Error::new(
                        span,
                        "Receiver parameter cannot be `&mut Evaluator`",
                    ));
                }
                return Ok(StarArgOrSpecial::Eval(eval));
            }

            if ident.subpat.is_some() {
                return Err(syn::Error::new(
                    ident.span(),
                    "Function arguments cannot use patterns",
                ));
            }

            if ident.by_ref.is_some() {
                return Err(syn::Error::new(
                    ident.span(),
                    "Function arguments cannot have `ref` modifier",
                ));
            }

            check_lifetimes_in_type(&ty, has_v)?;
            let param_attrs = parse_fn_param_attrs(attrs)?;

            if this {
                if !param_attrs.this && ident.ident != "this" {
                    return Err(syn::Error::new(
                        span,
                        "Receiver parameter must be named `this` \
                            or have `#[starlark(this)]` annotation",
                    ));
                }
            } else {
                if param_attrs.this {
                    return Err(syn::Error::new(
                        span,
                        "Receiver parameter can be only first",
                    ));
                }
            }

            let arguments = is_ref_something(&ty, "Arguments");

            let pass_style = match (
                this,
                param_attrs.args,
                param_attrs.kwargs,
                seen_star_args,
                param_attrs.pos_only,
                param_attrs.named_only,
                arguments,
            ) {
                (true, _, _, _, _, _, false) => StarArgPassStyle::This,
                (false, true, _, _, _, _, false) => StarArgPassStyle::Args,
                (false, _, true, _, _, _, false) => StarArgPassStyle::Kwargs,
                (false, _, _, true, true, _, false) => {
                    return Err(syn::Error::new(
                        span,
                        "Positional-only arguments cannot follow *args",
                    ));
                }
                (false, false, false, true, false, _, false) => StarArgPassStyle::NamedOnly,
                (false, false, false, false, false, false, false) => StarArgPassStyle::PosOrNamed,
                (false, false, false, false, true, false, false) => StarArgPassStyle::PosOnly,
                (false, false, false, false, false, true, false) => StarArgPassStyle::NamedOnly,
                (false, false, false, false, true, true, false) => {
                    return Err(syn::Error::new(
                        span,
                        "Function parameter cannot be both positional-only and named-only",
                    ));
                }
                (false, false, false, _, false, false, true) => StarArgPassStyle::Arguments,
                (_, _, _, _, _, _, true) => {
                    return Err(syn::Error::new(
                        span,
                        "`&Arguments` parameter type is incompatible with annotations",
                    ));
                }
            };
            Ok(StarArgOrSpecial::StarArg(StarArg {
                span,
                attrs: param_attrs.unused_attrs,
                mutable: ident.mutability.is_some(),
                name: ident.ident,
                pass_style,
                ty: *ty,
                default: param_attrs.default,
                source: StarArgSource::Unknown,
            }))
        }
        FnArg::Receiver(..) => Err(syn::Error::new(
            span,
            "Function cannot have `self` parameters",
        )),
    }
}


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

mod fun;

use dupe::Dupe;
use syn::spanned::Spanned;
use syn::Attribute;
use syn::Expr;
use syn::ExprLit;
use syn::FnArg;
use syn::Item;
use syn::ItemConst;
use syn::ItemFn;
use syn::Meta;
use syn::MetaNameValue;
use syn::PatType;
use syn::Stmt;
use syn::Type;
use syn::TypeReference;
use syn::Visibility;

use crate::module::parse::fun::parse_fun;
use crate::module::typ::StarConst;
use crate::module::typ::StarModule;
use crate::module::typ::StarStmt;
use crate::module::util::is_type_name;

#[derive(Debug, Copy, Clone, Dupe, PartialEq, Eq)]
pub(crate) enum ModuleKind {
    Globals,
    Methods,
}

impl ModuleKind {
    pub(crate) fn statics_type_name(self) -> &'static str {
        match self {
            ModuleKind::Globals => "GlobalsStatic",
            ModuleKind::Methods => "MethodsStatic",
        }
    }
}

pub(crate) fn parse(mut input: ItemFn) -> syn::Result<StarModule> {
    let (module_docstring, attrs) = parse_module_attributes(&input);
    let visibility = input.vis;
    let sig_span = input.sig.span();
    let name = input.sig.ident;

    if input.sig.inputs.len() != 1 {
        return Err(syn::Error::new(
            sig_span,
            "function must have exactly one argument",
        ));
    }
    let arg = input.sig.inputs.pop().unwrap();
    let arg_span = arg.span();

    let (ty, module_kind) = match arg.into_value() {
        FnArg::Typed(PatType { ty, .. }) if is_mut_globals_builder(&ty) => {
            (ty, ModuleKind::Globals)
        }
        FnArg::Typed(PatType { ty, .. }) if is_mut_methods_builder(&ty) => {
            (ty, ModuleKind::Methods)
        }
        _ => {
            return Err(syn::Error::new(
                arg_span,
                "Expected a mutable globals or methods builder",
            ));
        }
    };
    Ok(StarModule {
        module_kind,
        visibility,
        globals_builder: *ty,
        name,
        attrs,
        docstring: module_docstring,
        stmts: input
            .block
            .stmts
            .into_iter()
            .map(|stmt| parse_stmt(stmt, module_kind))
            .collect::<syn::Result<_>>()?,
    })
}

fn is_attribute_docstring(x: &Attribute) -> Option<String> {
    if x.path().is_ident("doc") {
        if let Meta::NameValue(MetaNameValue {
            value:
                Expr::Lit(ExprLit {
                    lit: syn::Lit::Str(s),
                    ..
                }),
            ..
        }) = &x.meta
        {
            return Some(s.value());
        }
    }
    None
}

/// Return (docstring, other attributes)
fn parse_module_attributes(input: &ItemFn) -> (Option<String>, Vec<Attribute>) {
    let mut doc_attrs = Vec::new();
    let mut attrs = Vec::new();
    for attr in &input.attrs {
        if let Some(ds) = is_attribute_docstring(attr) {
            doc_attrs.push(ds);
        } else {
            attrs.push(attr.clone());
        }
    }
    let docs = if doc_attrs.is_empty() {
        None
    } else {
        Some(doc_attrs.join("\n"))
    };
    (docs, attrs)
}

fn parse_stmt(stmt: Stmt, module_kind: ModuleKind) -> syn::Result<StarStmt> {
    match stmt {
        Stmt::Item(Item::Fn(x)) => parse_fun(x, module_kind),
        Stmt::Item(Item::Const(x)) => Ok(StarStmt::Const(parse_const(x)?)),
        s => Err(syn::Error::new(
            s.span(),
            "Can only put constants and functions inside a #[starlark_module]",
        )),
    }
}

fn parse_visibility(vis: &Visibility) -> syn::Result<()> {
    match vis {
        Visibility::Inherited => Ok(()),
        _ => Err(syn::Error::new(
            vis.span(),
            "Visibility modifiers are not allowed inside a `#[starlark_module]`",
        )),
    }
}

fn parse_const(x: ItemConst) -> syn::Result<StarConst> {
    parse_visibility(&x.vis)?;

    Ok(StarConst {
        name: x.ident,
        ty: *x.ty,
        value: *x.expr,
    })
}

fn is_mut_something(x: &Type, smth: &str) -> bool {
    match x {
        Type::Reference(TypeReference {
            mutability: Some(_),
            elem: x,
            ..
        }) => is_type_name(x, smth),
        _ => false,
    }
}

pub(crate) fn is_ref_something(x: &Type, smth: &str) -> bool {
    match x {
        Type::Reference(TypeReference {
            mutability: None,
            elem: x,
            ..
        }) => is_type_name(x, smth),
        _ => false,
    }
}

// Is the type `&mut GlobalsBuilder`
fn is_mut_globals_builder(x: &Type) -> bool {
    is_mut_something(x, "GlobalsBuilder")
}

// Is the type `&mut MethodsBuilder`
fn is_mut_methods_builder(x: &Type) -> bool {
    is_mut_something(x, "MethodsBuilder")
}


/*
 * 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 proc_macro2::Ident;
use proc_macro2::TokenStream;
use quote::format_ident;
use quote::quote;
use quote::quote_spanned;
use syn::spanned::Spanned;
use syn::Attribute;
use syn::Expr;
use syn::ExprLit;
use syn::Lit;
use syn::Type;

use crate::module::render::render_starlark_return_type;
use crate::module::render::render_starlark_type;
use crate::module::typ::SpecialParam;
use crate::module::typ::StarArg;
use crate::module::typ::StarArgPassStyle;
use crate::module::typ::StarArgSource;
use crate::module::typ::StarFun;
use crate::module::typ::StarFunSource;
use crate::module::util::ident_string;
use crate::module::util::mut_token;

impl StarFun {
    fn type_expr(&self) -> TokenStream {
        match &self.as_type {
            Some(x) => quote_spanned! {
                self.span()=>
                    std::option::Option::Some(
                        <#x as starlark::values::StarlarkValue>::get_type_value_static(),
                    )
            },
            None => quote_spanned! {
                self.span()=>
                std::option::Option::None
            },
        }
    }

    fn ty_custom_expr(&self) -> TokenStream {
        match &self.starlark_ty_custom_function {
            Some(x) => quote_spanned! {
                self.span()=>
                std::option::Option::Some(starlark::typing::Ty::custom_function(#x))
            },
            None => {
                quote_spanned! {
                    self.span()=>
                    std::option::Option::None
                }
            }
        }
    }

    fn special_builtin_function_expr(&self) -> TokenStream {
        match &self.special_builtin_function {
            Some(x) => quote_spanned! {
                self.span()=>
                std::option::Option::Some(#x)
            },
            None => {
                quote_spanned! {
                    self.span()=>
                    std::option::Option::None
                }
            }
        }
    }

    fn type_str(&self) -> TokenStream {
        match &self.as_type {
            Some(x) => quote_spanned! {
                self.span()=>
                    std::option::Option::Some(
                        <#x as starlark::values::StarlarkValue>::get_type_value_static().as_str(),
                    )
            },
            None => quote_spanned! {
                self.span()=>
                std::option::Option::None
            },
        }
    }

    /// Evaluator function parameter and call argument.
    fn eval_param_arg(
        &self,
    ) -> (
        Option<TokenStream>,
        Option<TokenStream>,
        Option<TokenStream>,
    ) {
        if let Some(SpecialParam { ident, ty }) = &self.eval {
            (
                Some(quote_spanned! {self.span()=>
                    #ident: #ty,
                }),
                Some(quote_spanned! {self.span()=>
                    #ty,
                }),
                Some(quote_spanned! {self.span()=>
                    eval,
                }),
            )
        } else {
            (None, None, None)
        }
    }

    /// Heap function parameter and call argument.
    fn heap_param_arg(
        &self,
    ) -> (
        Option<TokenStream>,
        Option<TokenStream>,
        Option<TokenStream>,
    ) {
        if let Some(SpecialParam { ident, ty }) = &self.heap {
            (
                Some(quote_spanned! {self.span()=>
                    #ident: #ty,
                }),
                Some(quote_spanned! {self.span()=>
                    #ty,
                }),
                Some(quote_spanned! {self.span()=>
                    eval.heap(),
                }),
            )
        } else {
            (None, None, None)
        }
    }

    /// `this` param if needed and call argument.
    fn this_param_arg(
        &self,
    ) -> (
        Option<TokenStream>,
        Option<TokenStream>,
        Option<TokenStream>,
    ) {
        if self.is_method() {
            (
                Some(quote_spanned! {self.span()=> __this: starlark::values::Value<'v>, }),
                Some(quote_spanned! {self.span()=> starlark::values::Value<'v>, }),
                Some(quote_spanned! {self.span()=> __this, }),
            )
        } else {
            (None, None, None)
        }
    }

    /// Non-special params.
    fn binding_params_arg(
        &self,
    ) -> (
        Vec<TokenStream>,
        Vec<TokenStream>,
        TokenStream,
        Vec<TokenStream>,
    ) {
        let Bindings { prepare, bindings } = render_binding(self);
        let binding_params: Vec<_> = bindings.iter().map(|b| b.render_param()).collect();
        let binding_param_types: Vec<_> = bindings.iter().map(|b| b.render_param_type()).collect();
        let binding_args: Vec<_> = bindings.iter().map(|b| b.render_arg()).collect();
        (binding_params, binding_param_types, prepare, binding_args)
    }

    fn trait_name(&self) -> TokenStream {
        if self.is_method() {
            quote_spanned! {self.span()=> starlark::values::function::NativeMeth }
        } else {
            quote_spanned! {self.span()=> starlark::values::function::NativeFunc }
        }
    }

    fn name_str(&self) -> String {
        ident_string(&self.name)
    }

    pub(crate) fn struct_name(&self) -> Ident {
        format_ident!("Impl_{}", self.name_str())
    }

    /// Fields and field initializers for the struct implementing the trait.
    fn struct_fields(&self) -> syn::Result<(TokenStream, TokenStream)> {
        let signature = if let StarFunSource::Signature { .. } = self.source {
            Some(render_signature(self, Purpose::Parsing)?)
        } else {
            None
        };
        if let Some(signature) = signature {
            Ok((
                quote_spanned! { self.span()=>
                    signature: starlark::eval::ParametersSpec<starlark::values::FrozenValue>,
                },
                quote_spanned! { self.span()=>
                    signature: #signature,
                },
            ))
        } else {
            Ok((
                quote_spanned! { self.span()=> },
                quote_spanned! { self.span()=> },
            ))
        }
    }

    /// Globals builder call to register the function.
    fn builder_set(
        &self,
        documentation_var: &Ident,
        struct_fields_init: TokenStream,
    ) -> syn::Result<TokenStream> {
        let name_str = self.name_str();
        let speculative_exec_safe = self.speculative_exec_safe;
        let typ = self.type_expr();
        let ty_custom = self.ty_custom_expr();
        let struct_name = self.struct_name();
        let special_builtin_function = self.special_builtin_function_expr();

        if self.is_method() {
            Ok(quote_spanned! {self.span()=>
                #[allow(clippy::redundant_closure)]
                globals_builder.set_method(
                    #name_str,
                    #speculative_exec_safe,
                    #documentation_var,
                    #typ,
                    #struct_name {
                        #struct_fields_init
                    },
                );
            })
        } else {
            Ok(quote_spanned! {self.span()=>
                #[allow(clippy::redundant_closure)]
                globals_builder.set_function(
                    #name_str,
                    #speculative_exec_safe,
                    #documentation_var,
                    #typ,
                    #ty_custom,
                    #special_builtin_function,
                    #struct_name {
                        #struct_fields_init
                    },
                );
            })
        }
    }
}

pub(crate) fn render_fun(x: StarFun) -> syn::Result<TokenStream> {
    let span = x.span();

    let (documentation_var, documentation) = render_documentation(&x)?;

    let (this_param, this_param_type, this_arg) = x.this_param_arg();
    let (eval_param, eval_param_type, eval_arg) = x.eval_param_arg();
    let (heap_param, heap_param_type, heap_arg) = x.heap_param_arg();
    let (binding_params, binding_param_types, prepare, binding_args) = x.binding_params_arg();

    let trait_name = x.trait_name();
    let (struct_fields, struct_fields_init) = x.struct_fields()?;

    let struct_name = x.struct_name();

    let builder_set = x.builder_set(&documentation_var, struct_fields_init)?;

    let StarFun {
        attrs,
        return_type,
        body,
        ..
    } = x;

    Ok(quote_spanned! {
        span=>
        struct #struct_name {
            #struct_fields
        }

        impl #struct_name {
            // TODO(nga): copy lifetime parameter from declaration,
            //   so the warning would be precise.
            #[allow(clippy::extra_unused_lifetimes)]
            #( #attrs )*
            fn invoke_impl<'v>(
                #this_param
                #( #binding_params, )*
                #eval_param
                #heap_param
            ) -> #return_type {
                #body
            }

            // When function signature declares return type as `anyhow::Result<impl AllocValue>`,
            // we cannot call `T::starlark_type_repr` to render documentation, because there's no T.
            // Future Rust will provide syntax `type ReturnType = impl AllocValue`:
            // https://github.com/rust-lang/rfcs/pull/2515
            // Until then we use this hack as a workaround.
            #[allow(dead_code)] // Function is not used when return type is specified explicitly.
            fn return_type_starlark_type_repr() -> starlark::typing::Ty {
                fn get_impl<'v, T: starlark::values::AllocValue<'v>>(
                    _f: fn(
                        #this_param_type
                        #( #binding_param_types, )*
                        #eval_param_type
                        #heap_param_type
                    ) -> anyhow::Result<T>,
                ) -> starlark::typing::Ty {
                    <T as starlark::values::type_repr::StarlarkTypeRepr>::starlark_type_repr()
                }
                get_impl(Self::invoke_impl)
            }
        }

        impl #trait_name for #struct_name {
            #[allow(non_snake_case)] // Starlark doesn't have this convention
            fn invoke<'v>(
                &self,
                eval: &mut starlark::eval::Evaluator<'v, '_>,
                #this_param
                parameters: &starlark::eval::Arguments<'v, '_>,
            ) -> anyhow::Result<starlark::values::Value<'v>> {
                #prepare
                match Self::invoke_impl(#this_arg #( #binding_args, )* #eval_arg #heap_arg) {
                    Ok(v) => Ok(eval.heap().alloc(v)),
                    Err(e) => Err(e),
                }
            }
        }

        {
            #documentation
            #builder_set
        }
    })
}

struct Bindings {
    prepare: TokenStream,
    bindings: Vec<BindingArg>,
}

// Given __args and __signature (if render_signature was Some)
// create bindings for all the arguments
fn render_binding(x: &StarFun) -> Bindings {
    let span = x.args_span();
    match x.source {
        StarFunSource::Arguments => {
            let StarArg {
                span,
                attrs,
                name,
                ty,
                ..
            } = &x.args[0];
            let span = *span;
            Bindings {
                prepare: quote_spanned! { span=> },
                bindings: vec![BindingArg {
                    name: name.to_owned(),
                    ty: ty.to_owned(),
                    attrs: attrs.clone(),
                    mutability: quote_spanned! { span=> },
                    expr: quote_spanned! { span=> parameters },
                }],
            }
        }
        StarFunSource::ThisArguments => {
            let StarArg {
                span,
                attrs,
                name,
                ty,
                ..
            } = &x.args[1];
            let span = *span;
            let this = render_binding_arg(&x.args[0]);
            Bindings {
                prepare: quote_spanned! { span=> },
                bindings: vec![
                    this,
                    BindingArg {
                        name: name.to_owned(),
                        ty: ty.to_owned(),
                        attrs: attrs.clone(),
                        mutability: quote_spanned! { span=> },
                        expr: quote_spanned! { span=> parameters },
                    },
                ],
            }
        }
        StarFunSource::Signature { count } => {
            let bind_args: Vec<BindingArg> = x.args.iter().map(render_binding_arg).collect();
            Bindings {
                prepare: quote_spanned! { span=>
                    let __args: [_; #count] = self.signature.collect_into(parameters, eval.heap())?;
                },
                bindings: bind_args,
            }
        }
        StarFunSource::Positional { required, optional } => {
            let bind_args = x.args.iter().map(render_binding_arg).collect();
            if optional == 0 {
                Bindings {
                    prepare: quote_spanned! {
                        span=>
                        parameters.no_named_args()?;
                        let __required: [_; #required] = parameters.positional(eval.heap())?;
                    },
                    bindings: bind_args,
                }
            } else {
                Bindings {
                    prepare: quote_spanned! {
                        span=>
                        parameters.no_named_args()?;
                        let (__required, __optional): ([_; #required], [_; #optional]) = parameters.optional(eval.heap())?;
                    },
                    bindings: bind_args,
                }
            }
        }
    }
}

struct BindingArg {
    expr: TokenStream,

    attrs: Vec<Attribute>,
    mutability: TokenStream,
    name: Ident,
    ty: Type,
}

impl BindingArg {
    fn render_param_type(&self) -> TokenStream {
        let BindingArg { ty, .. } = self;
        quote_spanned! { ty.span()=>
            #ty
        }
    }

    fn render_param(&self) -> TokenStream {
        let mutability = &self.mutability;
        let name = &self.name;
        let ty = self.render_param_type();
        let attrs = &self.attrs;
        quote_spanned! {
            self.name.span()=>
            #( #attrs )*
            #mutability #name: #ty
        }
    }

    fn render_arg(&self) -> TokenStream {
        self.expr.clone()
    }
}

// Create a binding for an argument given. If it requires an index, take from the index
fn render_binding_arg(arg: &StarArg) -> BindingArg {
    let span = arg.span;
    let name = &arg.name;
    let name_str = ident_string(name);

    let source = match arg.source {
        StarArgSource::This => quote_spanned! {span=> __this},
        StarArgSource::Argument(i) => quote_spanned! {span=> __args[#i].get()},
        StarArgSource::Required(i) => quote_spanned! {span=> Some(__required[#i])},
        StarArgSource::Optional(i) => quote_spanned! {span=> __optional[#i]},
        ref s => unreachable!("unknown source: {:?}", s),
    };

    // Rust doesn't have powerful enough nested if yet
    let next = if arg.pass_style == StarArgPassStyle::This {
        quote_spanned! { span=> starlark::eval::Arguments::check_this(#source)? }
    } else if arg.is_option() {
        assert!(
            arg.default.is_none(),
            "Can't have Option argument with a default, for `{}`",
            name_str
        );
        quote_spanned! { span=> starlark::eval::Arguments::check_optional(#name_str, #source)? }
    } else if !arg.is_value() && arg.default.is_some() {
        let default = arg
            .default
            .as_ref()
            .unwrap_or_else(|| unreachable!("Checked on the line above"));
        quote_spanned! { span=>
            {
                // Combo
                #[allow(clippy::manual_unwrap_or)]
                #[allow(clippy::unnecessary_lazy_evaluations)]
                #[allow(clippy::redundant_closure)]
                let x = starlark::eval::Arguments::check_optional(#name_str, #source)?.unwrap_or_else(|| #default);
                x
            }
        }
    } else {
        quote_spanned! { span=> starlark::eval::Arguments::check_required(#name_str, #source)? }
    };

    let mutability = mut_token(arg.mutable);

    BindingArg {
        expr: next,
        attrs: arg.attrs.clone(),
        mutability,
        name: arg.name.to_owned(),
        ty: arg.ty.clone(),
    }
}

#[derive(Copy, Clone, Eq, PartialEq)]
enum Purpose {
    Documentation,
    Parsing,
}

// Given the arguments, create a variable `signature` with a `ParametersSpec` object.
// Or return None if you don't need a signature
fn render_signature(x: &StarFun, purpose: Purpose) -> syn::Result<TokenStream> {
    let span = x.args_span();
    let name_str = ident_string(&x.name);
    let signature_var = format_ident!("__signature");
    let sig_args = render_signature_args(&x.args, &signature_var, purpose)?;
    Ok(quote_spanned! {
        span=> {
            #[allow(unused_mut)]
            let mut #signature_var = starlark::eval::ParametersSpec::new(#name_str.to_owned());
            #sig_args
            #signature_var.finish()
        }
    })
}

fn render_documentation(x: &StarFun) -> syn::Result<(Ident, TokenStream)> {
    let span = x.args_span();

    // A signature is not needed to invoke positional-only functions, but we still want
    // information like names, order, type, etc to be available to call '.documentation()' on.
    let name_str = ident_string(&x.name);
    let need_render_signature = match &x.source {
        StarFunSource::Signature { .. } | StarFunSource::Positional { .. } => true,
        StarFunSource::Arguments | StarFunSource::ThisArguments => false,
    };
    let documentation_signature = if need_render_signature {
        render_signature(x, Purpose::Documentation)?
    } else {
        // An Arguments can take anything, so give the most generic documentation signature
        quote_spanned! {
            span=> {
                let mut __signature = starlark::eval::ParametersSpec::<starlark::values::FrozenValue>::new(#name_str.to_owned());
                __signature.args();
                __signature.kwargs();
                __signature.finish()
            }
        }
    };

    let docs = match x.docstring.as_ref() {
        Some(d) => quote_spanned!(span=> Some(#d)),
        None => quote_spanned!(span=> None),
    };
    let parameter_types: Vec<_> = x
        .args
        .iter()
        .filter(|a| {
            // "this" gets ignored when creating the signature, so make sure the indexes match up.
            // Arguments doesn't correspond to a parameter type, since it is many parameters
            a.pass_style != StarArgPassStyle::This && a.pass_style != StarArgPassStyle::Arguments
        })
        .enumerate()
        .filter(|(_, a)| a.pass_style != StarArgPassStyle::Args) // these aren't coerced according to their type (Vec vs tuple)
        .map(|(i, arg)| {
            let typ_str = render_starlark_type(span, arg.without_option());
            quote_spanned!(span=> (#i, starlark::docs::DocType { raw_type: #typ_str }) )
        })
        .collect();

    let return_type_str = render_starlark_return_type(x);
    let var_name = format_ident!("__documentation");
    let dot_type: TokenStream = x.type_str();
    let documentation = quote_spanned!(span=>
        let #var_name = {
            let parameter_types = std::collections::HashMap::from([#(#parameter_types),*]);
            let return_type = Some(
                starlark::docs::DocType {
                    raw_type: #return_type_str
                }
            );
            starlark::values::function::NativeCallableRawDocs {
                rust_docstring: #docs,
                signature: #documentation_signature,
                parameter_types,
                return_type,
                dot_type: #dot_type,
            }
        };
    );
    Ok((var_name, documentation))
}

fn render_signature_args(
    args: &[StarArg],
    signature_var: &Ident,
    purpose: Purpose,
) -> syn::Result<TokenStream> {
    #[derive(PartialEq, Eq, PartialOrd, Ord)]
    enum CurrentParamStyle {
        PosOnly,
        PosOrNamed,
        NamedOnly,
        NoMore,
    }

    let mut sig_args = TokenStream::new();
    let mut last_param_style = CurrentParamStyle::PosOnly;
    for arg in args {
        match arg.pass_style {
            StarArgPassStyle::This => {}
            StarArgPassStyle::Args => {
                if last_param_style >= CurrentParamStyle::NamedOnly {
                    return Err(syn::Error::new(
                        arg.span,
                        "`args` cannot follow named-only parameters",
                    ));
                }
                last_param_style = CurrentParamStyle::NamedOnly;
            }
            StarArgPassStyle::Kwargs => {
                if last_param_style == CurrentParamStyle::NoMore {
                    return Err(syn::Error::new(
                        arg.span,
                        "Cannot have more than one `kwargs` parameter",
                    ));
                }
                last_param_style = CurrentParamStyle::NoMore;
            }
            StarArgPassStyle::PosOnly => {
                if last_param_style > CurrentParamStyle::PosOnly {
                    return Err(syn::Error::new(
                        arg.span,
                        "Positional-only parameter after non-positional-only",
                    ));
                }
                last_param_style = CurrentParamStyle::PosOnly;
            }
            StarArgPassStyle::PosOrNamed => {
                if last_param_style == CurrentParamStyle::PosOnly {
                    sig_args.extend(quote_spanned! { arg.span=>
                        #signature_var.no_more_positional_only_args();
                    });
                }
                last_param_style = CurrentParamStyle::PosOrNamed;
            }
            StarArgPassStyle::NamedOnly => {
                if last_param_style < CurrentParamStyle::NamedOnly {
                    sig_args.extend(quote_spanned! { arg.span=>
                        #signature_var.no_more_positional_args();
                    });
                }
                last_param_style = CurrentParamStyle::NamedOnly;
            }
            StarArgPassStyle::Arguments => {
                return Err(syn::Error::new(
                    arg.span,
                    "unreachable: signature is not meant to be created for `&Arguments`",
                ));
            }
        }
        sig_args.extend(render_signature_arg(arg, signature_var, purpose)?);
    }
    Ok(sig_args)
}

// Generate a statement that modifies signature to add a new argument in.
fn render_signature_arg(
    arg: &StarArg,
    signature_var: &Ident,
    purpose: Purpose,
) -> syn::Result<TokenStream> {
    let span = arg.span;

    let name_str = ident_string(&arg.name);

    if arg.pass_style == StarArgPassStyle::Args {
        assert!(arg.default.is_none(), "Can't have *args with a default");
        Ok(quote_spanned! { span=> #signature_var.args();})
    } else if arg.pass_style == StarArgPassStyle::Kwargs {
        assert!(arg.default.is_none(), "Can't have **kwargs with a default");
        Ok(quote_spanned! { span=> #signature_var.kwargs();})
    } else if arg.pass_style == StarArgPassStyle::This {
        Ok(quote_spanned! { span=> })
    } else if arg.is_option() {
        Ok(quote_spanned! { span=> #signature_var.optional(#name_str);})
    } else if let Some(default) = &arg.default {
        // For things that are type Value, we put them on the frozen heap.
        // For things that aren't type value, use optional and then next_opt/unwrap
        // to avoid the to/from value conversion.
        if arg.is_value() {
            Ok(quote_spanned! { span=>
                #signature_var.defaulted(#name_str, globals_builder.alloc(#default));
            })
        } else if purpose == Purpose::Documentation
            && render_default_as_frozen_value(default).is_some()
        {
            // We want the repr of the default arugment to show up, so pass it along
            let frozen = render_default_as_frozen_value(default).unwrap();
            Ok(quote_spanned! { span=>
                #signature_var.defaulted(#name_str, #frozen);
            })
        } else {
            Ok(quote_spanned! { span=>
                #signature_var.optional(#name_str);
            })
        }
    } else {
        Ok(quote_spanned! { span=>
            #signature_var.required(#name_str);
        })
    }
}

/// We have an argument that the user wants to use as a default.
/// That _might_ have a valid `FrozenValue` representation, if so, it would be great to use for documentation.
/// Try and synthesise it if we can.
fn render_default_as_frozen_value(default: &Expr) -> Option<TokenStream> {
    let x = quote!(#default).to_string();
    if let Ok(x) = x.trim_end_matches("i32").parse::<i32>() {
        Some(quote! { globals_builder.alloc(#x) })
    } else if let Ok(x) = x.parse::<bool>() {
        Some(quote! { starlark::values::FrozenValue::new_bool(#x) })
    } else if x == "NoneOr :: None" {
        Some(quote! { starlark::values::FrozenValue::new_none() })
    } else if matches!(
        default,
        Expr::Lit(ExprLit {
            lit: Lit::Str(_),
            ..
        })
    ) {
        // Make sure we don't splice in `x` again, or we double quote the string
        Some(quote! { globals_builder.alloc(#default) })
    } else if x == "Vec :: new()" {
        Some(quote! { globals_builder.alloc(starlark::values::list::AllocList::EMPTY) })
    } else if x == "SmallMap :: new()" {
        Some(quote! { globals_builder.alloc(starlark::values::dict::AllocDict::EMPTY) })
    } else {
        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.
 */

mod fun;

use std::collections::HashSet;

use proc_macro2::TokenStream;
use quote::format_ident;
use quote::quote_spanned;

use crate::module::render::fun::render_fun;
use crate::module::typ::SpecialParam;
use crate::module::typ::StarAttr;
use crate::module::typ::StarConst;
use crate::module::typ::StarFun;
use crate::module::typ::StarModule;
use crate::module::typ::StarStmt;
use crate::module::util::ident_string;

pub(crate) fn render(x: StarModule) -> syn::Result<TokenStream> {
    let span = x.span();
    let StarModule {
        name,
        globals_builder,
        visibility,
        attrs,
        docstring,
        stmts,
        module_kind,
    } = x;
    let statics = format_ident!("{}", module_kind.statics_type_name());
    let stmts: Vec<_> = stmts
        .into_iter()
        .map(render_stmt)
        .collect::<syn::Result<_>>()?;
    let set_docstring =
        docstring.map(|ds| quote_spanned!(span=> globals_builder.set_docstring(#ds);));
    Ok(quote_spanned! {
        span=>
        #( #attrs )*
        #visibility fn #name(globals_builder: #globals_builder) {
            fn build(globals_builder: #globals_builder) {
                #set_docstring
                #( #stmts )*
                // Mute warning if stmts is empty.
                let _ = globals_builder;
            }
            static RES: starlark::environment::#statics = starlark::environment::#statics::new();
            RES.populate(build, globals_builder);
        }
    })
}

fn render_stmt(x: StarStmt) -> syn::Result<TokenStream> {
    match x {
        StarStmt::Const(x) => Ok(render_const(x)),
        StarStmt::Attr(x) => Ok(render_attr(x)),
        StarStmt::Fun(x) => render_fun(x),
    }
}

fn render_const(x: StarConst) -> TokenStream {
    let StarConst { name, ty, value } = x;
    let span = name.span();
    let name = ident_string(&name);
    quote_spanned! {
        span=>
        globals_builder.set::<#ty>(#name, #value);
    }
}

fn render_attr(x: StarAttr) -> TokenStream {
    let span = x.span();
    let StarAttr {
        name,
        arg,
        heap,
        attrs,
        return_type,
        return_type_arg,
        speculative_exec_safe,
        body,
        docstring,
    } = x;
    let name_str = ident_string(&name);
    let docstring = match docstring {
        Some(d) => quote_spanned!(span=> Some(#d.to_owned())),
        None => quote_spanned!(span=> None),
    };

    let let_heap = if let Some(SpecialParam { ident, ty }) = heap {
        Some(quote_spanned! { span=> let #ident: #ty = __heap; })
    } else {
        None
    };

    let return_type_str = render_starlark_type(span, &return_type_arg);

    quote_spanned! {
        span=>
        #( #attrs )*
        #[allow(non_snake_case)] // Starlark doesn't have this convention
        fn #name<'v>(
            #[allow(unused_variables)]
            this: starlark::values::Value<'v>,
            heap: &'v starlark::values::Heap,
        ) -> anyhow::Result<starlark::values::Value<'v>> {
             fn inner<'v>(
                this: starlark::values::Value<'v>,
                #[allow(unused_variables)]
                __heap: &'v starlark::values::Heap,
            ) -> #return_type {
                #[allow(unused_variables)]
                let this: #arg = match starlark::values::UnpackValue::unpack_value(this) {
                    None => return Err(starlark::values::ValueError::IncorrectParameterTypeNamedWithExpected(
                        "this".to_owned(),
                        <#arg as starlark::values::UnpackValue>::expected(),
                        this.get_type().to_owned(),
                    ).into()),
                    Some(v) => v,
                };
                #let_heap
                #body
            }
            Ok(heap.alloc(inner(this, heap)?))
        }

        globals_builder.set_attribute_fn(#name_str, #speculative_exec_safe, #docstring, #return_type_str, #name);
    }
}

/// Get the lifetimes that are mentioned in a given type and its nested generics.
fn get_lifetimes_inner<'a>(ret: &mut HashSet<&'a syn::Lifetime>, typ: &'a syn::Type) {
    match typ {
        syn::Type::Path(path) => {
            if let Some(segment) = path.path.segments.last() {
                match &segment.arguments {
                    syn::PathArguments::None => {}
                    syn::PathArguments::AngleBracketed(args) => {
                        for arg in &args.args {
                            match arg {
                                syn::GenericArgument::Lifetime(l) => {
                                    ret.insert(l);
                                }
                                syn::GenericArgument::Type(t) => get_lifetimes_inner(ret, t),
                                _ => {}
                            };
                        }
                    }
                    syn::PathArguments::Parenthesized(args) => {
                        for t in &args.inputs {
                            get_lifetimes_inner(ret, t);
                        }
                        match &args.output {
                            syn::ReturnType::Default => {}
                            syn::ReturnType::Type(_, t) => get_lifetimes_inner(ret, t),
                        };
                    }
                };
            }
        }
        syn::Type::Group(g) => get_lifetimes_inner(ret, &g.elem),
        syn::Type::Paren(p) => get_lifetimes_inner(ret, &p.elem),
        syn::Type::Ptr(p) => get_lifetimes_inner(ret, &p.elem),
        syn::Type::Reference(r) => {
            if let Some(l) = &r.lifetime {
                ret.insert(l);
            };
            get_lifetimes_inner(ret, &r.elem);
        }
        syn::Type::Tuple(t) => {
            for t in &t.elems {
                get_lifetimes_inner(ret, t);
            }
        }
        _ => {}
    };
}

/// Get the lifetime specifications to use with a function based on the lifetimes mentioned in `typ`.
///
/// e.g. `i32` would return ``, `Vec<(&'a str, &'b str)>` would return `<'a, 'b>`
fn get_lifetimes(span: proc_macro2::Span, typ: &syn::Type) -> TokenStream {
    let mut ret = HashSet::new();
    get_lifetimes_inner(&mut ret, typ);
    if ret.is_empty() {
        TokenStream::new()
    } else {
        let mut ret: Vec<_> = ret.into_iter().filter(|l| l.ident != "_").collect();
        ret.sort_by(|l, r| l.ident.cmp(&r.ident));
        quote_spanned!(span=> <#(#ret),*>)
    }
}

pub(crate) fn render_starlark_type(span: proc_macro2::Span, typ: &syn::Type) -> TokenStream {
    let lifetimes = get_lifetimes(span, typ);
    quote_spanned! {span=>
        {
            #[allow(clippy::extra_unused_lifetimes)]
            fn get_type_string #lifetimes() -> starlark::typing::Ty {
                <#typ as starlark::values::type_repr::StarlarkTypeRepr>::starlark_type_repr()
            }
            get_type_string()
        }
    }
}

pub(crate) fn render_starlark_return_type(fun: &StarFun) -> TokenStream {
    let struct_name = fun.struct_name();
    quote_spanned! {fun.span()=>
        #struct_name::return_type_starlark_type_repr()
    }
}


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

fn main() {
    rust_nightly();
}

fn rust_nightly() {
    let rustc = std::env::var("RUSTC").unwrap();
    let version = std::process::Command::new(rustc)
        .arg("--version")
        .output()
        .unwrap();

    assert!(version.status.success());

    // Nightly output:
    // rustc 1.64.0-nightly (affe0d3a0 2022-08-05)
    // Stable output:
    // rustc 1.64.0 (a55dd71d5 2022-09-19)
    // Meta internal output:
    // rustc 1.64.0-dev

    let stdout = String::from_utf8(version.stdout).unwrap();
    assert!(stdout.contains("rustc"), "Sanity check");
    let nightly = stdout.contains("nightly") || stdout.contains("dev");
    if nightly {
        println!("cargo:rustc-cfg=rust_nightly");
    }
}


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

/// Values are equivalent for hashmap: hashes are equal and values are logically equal.
pub trait Equivalent<K: ?Sized> {
    /// Returns true if the values are logically equivalent.
    fn equivalent(&self, key: &K) -> bool;
}

impl<Q: ?Sized, K: ?Sized> Equivalent<K> for Q
where
    Q: Eq,
    K: Borrow<Q>,
{
    #[inline]
    fn equivalent(&self, key: &K) -> bool {
        *self == *key.borrow()
    }
}


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

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

use crate::equivalent::Equivalent;
use crate::hash_value::StarlarkHashValue;

/// A key and its hash.
#[derive(PartialEq, Eq, Debug, Clone, Copy, Dupe, Allocative)]
pub struct Hashed<K> {
    hash: StarlarkHashValue,
    key: K,
}

// We deliberately know that this is a hash and value, so our Eq/Hash are fine
#[allow(clippy::derived_hash_with_manual_eq)]
impl<K> Hash for Hashed<K> {
    fn hash<S: Hasher>(&self, state: &mut S) {
        // Only hash the hash, not the key.
        self.hash.hash(state)
    }
}

impl<K> Deref for Hashed<K> {
    type Target = K;

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

impl<K: Display> Display for Hashed<K> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.key.fmt(f)
    }
}

impl<K: PartialOrd> PartialOrd for Hashed<K> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.key.partial_cmp(&other.key)
    }
}

impl<K: Ord> Ord for Hashed<K> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.key.cmp(&other.key)
    }
}

impl<K> Hashed<K> {
    /// Create a new [`Hashed`] value using the [`Hash`] of the key.
    pub fn new(key: K) -> Self
    where
        K: Hash,
    {
        Hashed::new_unchecked(StarlarkHashValue::new(&key), key)
    }

    /// Directly create a new [`Hashed`] using a given hash value.
    /// If the hash does not correspond to the key, its will cause issues.
    pub fn new_unchecked(hash: StarlarkHashValue, key: K) -> Self {
        Hashed { hash, key }
    }

    /// Get the underlying key.
    pub fn key(&self) -> &K {
        &self.key
    }

    /// Get the underlying key, as mutable.
    pub fn key_mut(&mut self) -> &mut K {
        &mut self.key
    }

    /// Get the underlying key taking ownership.
    pub fn into_key(self) -> K {
        self.key
    }

    /// Get the underlying hash.
    pub fn hash(&self) -> StarlarkHashValue {
        self.hash
    }

    /// Convert `Hashed<K>` to `Hashed<&K>`.
    pub fn as_ref(&self) -> Hashed<&K> {
        Hashed::new_unchecked(self.hash, &self.key)
    }
}

impl<'a, K> Hashed<&'a K> {
    /// Make `Hashed<K>` from `Hashed<&K>`.
    pub fn copied(self) -> Hashed<K>
    where
        K: Copy,
    {
        Hashed::new_unchecked(self.hash, *self.key)
    }

    /// Make `Hashed<K>` from `Hashed<&K>`, where `K` is `Clone`.
    pub fn cloned(self) -> Hashed<K>
    where
        K: Clone,
    {
        Hashed::new_unchecked(self.hash, self.key.clone())
    }
}

impl<'a, K: ?Sized> Hashed<&'a K> {
    /// Make `Hashed<K>` from `Hashed<&K>`, where `T` is the owned form of `K`.
    pub fn owned<T>(self) -> Hashed<T>
    where
        K: Equivalent<T>,
        K: ToOwned<Owned = T>,
    {
        Hashed::new_unchecked(self.hash, self.key.to_owned())
    }
}


/*
 * 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::hash::BuildHasher;
use std::hash::Hasher;

use dupe::Dupe;
use fnv::FnvHasher;

use crate::hash_value::StarlarkHashValue;

/// A hasher used by Starlark implementation.
///
/// Starlark relies on stable hashing, and this is the hasher.
#[derive(Default)]
pub struct StarlarkHasher(FnvHasher);

impl StarlarkHasher {
    /// Creates a new hasher.
    #[inline]
    pub fn new() -> StarlarkHasher {
        StarlarkHasher::default()
    }

    /// Finish the hash computation and return the result.
    #[inline]
    pub fn finish_small(self) -> StarlarkHashValue {
        // NOTE: Here we throw away half the key material we are given,
        // taking only the lower 32 bits.
        // Not a problem because `DefaultHasher` produces well-swizzled bits.
        StarlarkHashValue::new_unchecked(self.finish() as u32)
    }
}

impl Hasher for StarlarkHasher {
    #[inline]
    fn finish(&self) -> u64 {
        self.0.finish()
    }

    #[inline]
    fn write(&mut self, bytes: &[u8]) {
        self.0.write(bytes)
    }
}

/// [`BuildHasher`] implementation which produces [`StarlarkHasher`].
#[derive(Default, Debug, Clone, Copy, Dupe)]
pub struct StarlarkHasherBuilder;

impl BuildHasher for StarlarkHasherBuilder {
    type Hasher = StarlarkHasher;

    #[inline]
    fn build_hasher(&self) -> StarlarkHasher {
        StarlarkHasher::default()
    }
}


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

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

use crate::hasher::StarlarkHasher;
use crate::mix_u32::mix_u32;

/// A hash value.
#[derive(Clone, Copy, Dupe, PartialEq, Eq, Hash, Debug, Allocative)]
pub struct StarlarkHashValue(u32);

impl StarlarkHashValue {
    /// Create a new [`StarlarkHashValue`] using the [`Hash`] trait
    /// for given key.
    #[inline]
    pub fn new<K: Hash + ?Sized>(key: &K) -> Self {
        let mut hasher = StarlarkHasher::default();
        key.hash(&mut hasher);
        hasher.finish_small()
    }

    /// Directly create a new [`StarlarkHashValue`] using a hash.
    /// The expectation is that the key will be well-swizzled,
    /// or there may be many hash collisions.
    #[inline]
    pub const fn new_unchecked(hash: u32) -> Self {
        StarlarkHashValue(hash)
    }

    /// Hash 64-bit integer.
    ///
    /// Input can also be a non-well swizzled hash to create better hash.
    #[inline]
    pub const fn hash_64(h: u64) -> Self {
        // `fmix64` function from MurMur3 hash (which is in public domain).
        // https://github.com/aappleby/smhasher/blob/61a0530f28277f2e850bfc39600ce61d02b518de/src/MurmurHash3.cpp#L81

        let h = h ^ (h >> 33);
        let h = h.wrapping_mul(0xff51afd7ed558ccd);
        let h = h ^ (h >> 33);
        let h = h.wrapping_mul(0xc4ceb9fe1a85ec53);
        let h = h ^ (h >> 33);

        StarlarkHashValue::new_unchecked(h as u32)
    }

    /// Get the integer hash value.
    #[inline]
    pub const fn get(self) -> u32 {
        self.0
    }

    /// Make u64 hash from this hash.
    ///
    /// The resulting hash should be good enough to be used in hashbrown hashtable.
    #[inline]
    pub fn promote(self) -> u64 {
        mix_u32(self.0)
    }
}


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

// We define a lot of iterators on top of other iterators
// so define a helper macro for that
macro_rules! def_iter {
    () => {
        #[inline]
        fn next(&mut self) -> Option<Self::Item> {
            self.iter.next().map(Self::map)
        }

        #[inline]
        fn nth(&mut self, n: usize) -> Option<Self::Item> {
            self.iter.nth(n).map(Self::map)
        }

        #[inline]
        fn last(self) -> Option<Self::Item> {
            self.iter.last().map(Self::map)
        }

        #[inline]
        fn size_hint(&self) -> (usize, Option<usize>) {
            self.iter.size_hint()
        }

        #[inline]
        fn count(self) -> usize {
            self.iter.count()
        }

        #[inline]
        fn collect<C>(self) -> C
        where
            C: std::iter::FromIterator<Self::Item>,
        {
            self.iter.map(Self::map).collect()
        }
    };
}

macro_rules! def_double_ended_iter {
    () => {
        #[inline]
        fn next_back(&mut self) -> Option<Self::Item> {
            self.iter.next_back().map(Self::map)
        }
    };
}

pub(crate) use def_double_ended_iter;
pub(crate) use def_iter;


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

//! Ordered map optimized for starlark-rust use cases.

// Hints we disagree with
#![allow(clippy::missing_safety_doc)]
#![deny(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![cfg_attr(rust_nightly, feature(core_intrinsics))]
#![cfg_attr(rust_nightly, feature(portable_simd))]

mod equivalent;
mod hash_value;
mod hashed;
mod hasher;
mod iter;
mod mix_u32;
pub mod small_map;
pub mod small_set;
pub(crate) mod sorting;
pub mod vec2;
pub(crate) mod vec_map;

pub use equivalent::Equivalent;
pub use hash_value::StarlarkHashValue;
pub use hashed::Hashed;
pub use hasher::StarlarkHasher;
pub use hasher::StarlarkHasherBuilder;


/*
 * 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 is important when used with hashbrown, HashMap and IndexMap,
// which uses the upper 7 bits of the hash for a tag, then compares 16 tags in
// parallel with a SIMD instruction. Without the mix, typical low-valued u32 ids
// would all have tag 0.
#[inline(always)]
pub(crate) fn mix_u32(n: u32) -> u64 {
    (n as u64).wrapping_mul(0x9e3779b97f4a7c15)
}