gdnative-impl-proc-macros 0.11.3

Internal dependency of the gdnative bindings.
Documentation
use proc_macro2::{Span, TokenStream};
use syn::parse::{Parse, ParseStream};
use syn::{Ident, Type};

#[derive(Debug)]
struct Args {
    trait_name: Ident,
    rust_ty: Type,
    sys_ty: Option<Type>,
    sys_ref_ty: Option<Type>,
    godot_type: String,
}

impl Parse for Args {
    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
        input.parse::<Token![impl]>()?;

        let trait_name = input.parse()?;

        input.parse::<Token![for]>()?;

        let rust_ty = input.parse()?;

        let sys_ty = input
            .parse::<Token![as]>()
            .ok()
            .map(|_| input.parse())
            .transpose()?;

        let sys_ref_ty = input
            .parse::<Token![ref]>()
            .ok()
            .map(|_| input.parse())
            .transpose()?;

        input.parse::<Token![=>]>()?;

        let godot_type = input.parse::<Ident>()?;
        let godot_type = godot_type.to_string();

        let brace_content;
        braced!(brace_content in input);

        brace_content.parse::<Token![..]>()?;

        if !brace_content.is_empty() {
            return Err(brace_content.error("expecting only `..` in braces"));
        }

        if !input.is_empty() {
            return Err(input.error("expecting end of macro input"));
        }

        Ok(Args {
            trait_name,
            rust_ty,
            sys_ty,
            sys_ref_ty,
            godot_type,
        })
    }
}

static METHODS: &[&str] = &[
    "new",
    "new_copy",
    "new_with_array",
    "append",
    "append_array",
    "insert",
    "invert",
    "push_back",
    "remove",
    "resize",
    "read",
    "write",
    "set",
    "get",
    "size",
    "destroy",
    "read_access_copy",
    "read_access_ptr",
    "read_access_operator_assign",
    "read_access_destroy",
    "write_access_copy",
    "write_access_ptr",
    "write_access_operator_assign",
    "write_access_destroy",
];

fn impl_fn_symbol(method: &str) -> Ident {
    Ident::new(&format!("{method}_fn"), Span::call_site())
}

fn fn_symbol(godot_type: &str, method: &str) -> Ident {
    Ident::new(
        &format!("godot_pool_{godot_type}_array_{method}"),
        Span::call_site(),
    )
}

fn fn_ty(method: &str) -> Type {
    match method {
        "new" => parse_quote!(unsafe extern "C" fn(*mut Self::SysArray)),
        "new_copy" => {
            parse_quote!(unsafe extern "C" fn(*mut Self::SysArray, *const Self::SysArray))
        }
        "new_with_array" => {
            parse_quote!(unsafe extern "C" fn(*mut Self::SysArray, *const sys::godot_array))
        }
        "append" => parse_quote!(unsafe extern "C" fn(*mut Self::SysArray, Self::SysRefTy)),
        "append_array" => {
            parse_quote!(unsafe extern "C" fn(*mut Self::SysArray, *const Self::SysArray))
        }
        "insert" => parse_quote!(
            unsafe extern "C" fn(
                *mut Self::SysArray,
                sys::godot_int,
                Self::SysRefTy,
            ) -> sys::godot_error
        ),
        "invert" => parse_quote!(unsafe extern "C" fn(*mut Self::SysArray)),
        "push_back" => parse_quote!(unsafe extern "C" fn(*mut Self::SysArray, Self::SysRefTy)),
        "remove" => parse_quote!(unsafe extern "C" fn(*mut Self::SysArray, sys::godot_int)),
        "resize" => parse_quote!(unsafe extern "C" fn(*mut Self::SysArray, sys::godot_int)),
        "read" => {
            parse_quote!(unsafe extern "C" fn(*const Self::SysArray) -> *mut Self::SysReadAccess)
        }
        "write" => {
            parse_quote!(unsafe extern "C" fn(*mut Self::SysArray) -> *mut Self::SysWriteAccess)
        }
        "set" => {
            parse_quote!(unsafe extern "C" fn(*mut Self::SysArray, sys::godot_int, Self::SysRefTy))
        }
        "get" => {
            parse_quote!(unsafe extern "C" fn(*const Self::SysArray, sys::godot_int) -> Self::SysTy)
        }
        "size" => parse_quote!(unsafe extern "C" fn(*const Self::SysArray) -> sys::godot_int),
        "destroy" => parse_quote!(unsafe extern "C" fn(*mut Self::SysArray)),
        "read_access_copy" => parse_quote!(
            unsafe extern "C" fn(*const Self::SysReadAccess) -> *mut Self::SysReadAccess
        ),
        "read_access_ptr" => {
            parse_quote!(unsafe extern "C" fn(*const Self::SysReadAccess) -> *const Self::SysTy)
        }
        "read_access_operator_assign" => {
            parse_quote!(unsafe extern "C" fn(*mut Self::SysReadAccess, *mut Self::SysReadAccess))
        }
        "read_access_destroy" => parse_quote!(unsafe extern "C" fn(*mut Self::SysReadAccess)),
        "write_access_copy" => parse_quote!(
            unsafe extern "C" fn(*const Self::SysWriteAccess) -> *mut Self::SysWriteAccess
        ),
        "write_access_ptr" => {
            parse_quote!(unsafe extern "C" fn(*const Self::SysWriteAccess) -> *mut Self::SysTy)
        }
        "write_access_operator_assign" => {
            parse_quote!(unsafe extern "C" fn(*mut Self::SysWriteAccess, *mut Self::SysWriteAccess))
        }
        "write_access_destroy" => parse_quote!(unsafe extern "C" fn(*mut Self::SysWriteAccess)),
        _ => panic!("unknown method: {method}"),
    }
}

#[allow(clippy::unnecessary_wraps)]
fn expand(input: Args) -> Result<TokenStream, syn::Error> {
    let Args {
        trait_name,
        rust_ty,
        sys_ty,
        sys_ref_ty,
        godot_type,
    } = input;

    let (to_sys_fn, from_sys_fn) = if sys_ty.is_none() {
        let to_sys_fn = quote! {
            #[inline(always)]
            fn element_to_sys(self) -> Self::SysTy {
                self
            }
        };

        let from_sys_fn = quote! {
            #[inline(always)]
            fn element_from_sys(sys: Self::SysTy) -> Self {
                sys
            }
        };

        (to_sys_fn, from_sys_fn)
    } else {
        let to_sys_fn = quote! {
            #[inline(always)]
            fn element_to_sys(self) -> Self::SysTy {
                self.to_sys()
            }
        };

        let from_sys_fn = quote! {
            #[inline(always)]
            fn element_from_sys(sys: Self::SysTy) -> Self {
                Self::from_sys(sys)
            }
        };

        (to_sys_fn, from_sys_fn)
    };

    let sys_ty = sys_ty.unwrap_or_else(|| rust_ty.clone());

    let (to_sys_ref_fn, from_sys_ref_fn) = if sys_ref_ty.is_none() {
        let to_sys_ref_fn = quote! {
            #[inline(always)]
            fn element_to_sys_ref(&self) -> Self::SysRefTy {
                self.element_to_sys()
            }
        };

        let from_sys_ref_fn = quote! {
            #[inline(always)]
            unsafe fn element_from_sys_ref(sys: Self::SysRefTy) -> Self {
                Self::element_from_sys(sys)
            }
        };

        (to_sys_ref_fn, from_sys_ref_fn)
    } else {
        let to_sys_ref_fn = quote! {
            #[inline(always)]
            fn element_to_sys_ref(&self) -> Self::SysRefTy {
                self.sys()
            }
        };

        let from_sys_ref_fn = quote! {
            #[inline(always)]
            unsafe fn element_from_sys_ref(sys: Self::SysRefTy) -> Self {
                Self::from_sys(*sys)
            }
        };

        (to_sys_ref_fn, from_sys_ref_fn)
    };

    let sys_ref_ty = sys_ref_ty.unwrap_or_else(|| sys_ty.clone());

    let sys_array = Ident::new(
        &format!("godot_pool_{}_array", &godot_type),
        Span::call_site(),
    );
    let sys_array: Type = parse_quote!(sys::#sys_array);
    let sys_read_access = Ident::new(
        &format!("godot_pool_{}_array_read_access", &godot_type),
        Span::call_site(),
    );
    let sys_read_access: Type = parse_quote!(sys::#sys_read_access);
    let sys_write_access = Ident::new(
        &format!("godot_pool_{}_array_write_access", &godot_type),
        Span::call_site(),
    );
    let sys_write_access: Type = parse_quote!(sys::#sys_write_access);

    let array_to_variant_fn_symbol = Ident::new(
        &format!("godot_variant_new_pool_{}_array", &godot_type),
        Span::call_site(),
    );

    let array_from_variant_fn_symbol = Ident::new(
        &format!("godot_variant_as_pool_{}_array", &godot_type),
        Span::call_site(),
    );

    let sys_variant_type_symbol = Ident::new(
        &format!(
            "godot_variant_type_GODOT_VARIANT_TYPE_POOL_{}_ARRAY",
            godot_type.to_ascii_uppercase()
        ),
        Span::call_site(),
    );

    let functions = METHODS.iter().map(|method| {
        let fn_symbol = fn_symbol(&godot_type, method);
        let fn_ty = fn_ty(method);
        let impl_fn_symbol = impl_fn_symbol(method);

        quote! {
            #[inline(always)]
            fn #impl_fn_symbol (api: &sys::GodotApi) -> #fn_ty {
                api.#fn_symbol
            }
        }
    });

    Ok(quote! {
        impl private::Sealed for #rust_ty {}
        #[allow(unused_unsafe)]
        impl #trait_name for #rust_ty {
            type SysArray = #sys_array;
            type SysReadAccess = #sys_read_access;
            type SysWriteAccess = #sys_write_access;
            type SysTy = #sys_ty;
            type SysRefTy = #sys_ref_ty;

            const SYS_VARIANT_TYPE: sys::godot_variant_type = sys::#sys_variant_type_symbol;

            #to_sys_fn
            #from_sys_fn
            #to_sys_ref_fn
            #from_sys_ref_fn

            #[inline(always)]
            fn array_to_variant_fn(
                api: &sys::GodotApi,
            ) -> unsafe extern "C" fn (*mut sys::godot_variant, *const Self::SysArray) {
                api.#array_to_variant_fn_symbol
            }

            #[inline(always)]
            fn array_from_variant_fn(
                api: &sys::GodotApi,
            ) -> unsafe extern "C" fn (*const sys::godot_variant) -> Self::SysArray {
                api.#array_from_variant_fn_symbol
            }

            #(#functions)*
        }
    })
}

pub fn impl_element(input: proc_macro::TokenStream) -> Result<TokenStream, syn::Error> {
    syn::parse(input).and_then(expand)
}

struct DeclArgs {
    attrs: Vec<syn::Attribute>,
    trait_name: Ident,
}

impl Parse for DeclArgs {
    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
        let attrs = input.call(syn::Attribute::parse_outer)?;

        input.parse::<Token![pub]>()?;
        input.parse::<Token![trait]>()?;

        let trait_name = input.parse()?;

        input.parse::<Token![:]>()?;

        if (input.parse::<Ident>()?) != "private" {
            return Err(input.error("expecting a `private::Sealed` supertrait"));
        }

        input.parse::<Token![::]>()?;

        if (input.parse::<Ident>()?) != "Sealed" {
            return Err(input.error("expecting a `private::Sealed` supertrait"));
        }

        let brace_content;
        braced!(brace_content in input);

        brace_content.parse::<Token![..]>()?;

        if !brace_content.is_empty() {
            return Err(brace_content.error("expecting only `..` in braces"));
        }

        if !input.is_empty() {
            return Err(input.error("expecting end of macro input"));
        }

        Ok(DeclArgs { attrs, trait_name })
    }
}

pub fn decl_element(input: proc_macro::TokenStream) -> Result<TokenStream, syn::Error> {
    let DeclArgs { attrs, trait_name } = syn::parse(input)?;

    let functions = METHODS.iter().map(|method| {
        let fn_ty = fn_ty(method);
        let impl_fn_symbol = impl_fn_symbol(method);

        quote! {
            #[doc(hidden)]
            fn #impl_fn_symbol (api: &sys::GodotApi) -> #fn_ty;
        }
    });

    Ok(quote! {
        #(#attrs)*
        pub trait #trait_name: private::Sealed {
            #[doc(hidden)]
            type SysArray: std::default::Default;
            #[doc(hidden)]
            type SysReadAccess;
            #[doc(hidden)]
            type SysWriteAccess;
            #[doc(hidden)]
            type SysTy: std::default::Default;
            #[doc(hidden)]
            type SysRefTy;

            #[doc(hidden)]
            const SYS_VARIANT_TYPE: sys::godot_variant_type;

            #[doc(hidden)]
            fn element_to_sys(self) -> Self::SysTy;

            #[doc(hidden)]
            fn element_to_sys_ref(&self) -> Self::SysRefTy;

            #[doc(hidden)]
            fn element_from_sys(sys: Self::SysTy) -> Self;

            #[doc(hidden)]
            unsafe fn element_from_sys_ref(sys: Self::SysRefTy) -> Self;

            #[doc(hidden)]
            fn array_to_variant_fn(
                api: &sys::GodotApi,
            ) -> unsafe extern "C" fn (*mut sys::godot_variant, *const Self::SysArray);

            #[doc(hidden)]
            fn array_from_variant_fn(
                api: &sys::GodotApi,
            ) -> unsafe extern "C" fn (*const sys::godot_variant) -> Self::SysArray;

            #(#functions)*
        }
    })
}