use std::path::Path;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use crate::context::Context;
use crate::generator::functions_common::{FnCode, FnDefinition, FnDefinitions};
use crate::generator::method_tables::MethodTableKey;
use crate::generator::{enums, functions_common};
use crate::models::domain::{
BuiltinClass, BuiltinMethod, ClassLike, ExtensionApi, FlowDirection, FnDirection, Function,
ModName, RustTy, TyName,
};
use crate::{SubmitFn, conv, util};
pub struct GeneratedBuiltin {
pub code: TokenStream,
pub has_sidecar_module: bool,
}
pub struct GeneratedBuiltinModule {
pub outer_builtin: Ident,
pub inner_builtin: Ident,
pub module_name: ModName,
pub is_pub_sidecar: bool,
}
pub fn generate_builtin_class_files(
api: &ExtensionApi,
ctx: &mut Context,
gen_path: &Path,
submit_fn: &mut SubmitFn,
) {
let _ = std::fs::remove_dir_all(gen_path);
std::fs::create_dir_all(gen_path).expect("create classes directory");
let mut modules = vec![];
for variant in api.builtins.iter() {
let Some(class) = variant.builtin_class.as_ref() else {
continue;
};
let module_name = class.mod_name();
let variant_shout_name = util::ident(variant.godot_shout_name());
let generated_builtin = make_builtin_class(class, &variant_shout_name, ctx);
let file_contents = generated_builtin.code;
let out_path = gen_path.join(format!("{}.rs", module_name.rust_mod));
submit_fn(out_path, file_contents);
modules.push(GeneratedBuiltinModule {
outer_builtin: class.name().rust_ty.clone(),
inner_builtin: class.inner_name().clone(),
module_name: module_name.clone(),
is_pub_sidecar: generated_builtin.has_sidecar_module,
});
}
let out_path = gen_path.join("mod.rs");
let mod_contents = make_builtin_module_file(modules);
submit_fn(out_path, mod_contents);
}
pub fn make_builtin_module_file(classes_and_modules: Vec<GeneratedBuiltinModule>) -> TokenStream {
let decls = classes_and_modules.iter().map(|m| {
let GeneratedBuiltinModule {
outer_builtin,
inner_builtin,
module_name,
is_pub_sidecar,
} = m;
let vis = is_pub_sidecar.then_some(quote! { pub });
let doc = format!("Default extenders for builtin type [`{outer_builtin}`][crate::builtin::{outer_builtin}].");
quote! {
#[doc = #doc]
#vis mod #module_name;
pub use #module_name::#inner_builtin;
}
});
quote! {
#( #decls )*
}
}
fn make_builtin_class(
class: &BuiltinClass,
variant_shout_name: &Ident,
ctx: &mut Context,
) -> GeneratedBuiltin {
let godot_name = &class.name().godot_ty;
let flow = FlowDirection::GodotToRust;
let RustTy::BuiltinIdent {
ty: outer_class, ..
} = conv::to_rust_type(godot_name, None, Some(flow), ctx)
else {
panic!("Rust type `{godot_name}` categorized wrong")
};
let inner_builtin = class.inner_name();
#[rustfmt::skip]
let (
FnDefinitions { functions: inner_methods, builders: inner_builders },
FnDefinitions { functions: outer_methods, builders: outer_builders },
) = make_builtin_methods(class, variant_shout_name, &class.methods, ctx);
let imports = util::make_imports();
let enums = enums::make_enums(&class.enums, &TokenStream::new());
let special_constructors = make_special_builtin_methods(class.name(), ctx);
let code = quote! {
#imports
pub(super) mod re_export {
use super::*;
#[doc(hidden)]
#[repr(transparent)]
pub struct #inner_builtin<'inner> {
pub(super) _outer_lifetime: std::marker::PhantomData<&'inner ()>,
pub(super) sys_ptr: sys::GDExtensionTypePtr,
}
}
impl<'inner> re_export::#inner_builtin<'inner> {
pub fn from_outer(outer: &#outer_class) -> Self {
Self {
_outer_lifetime: std::marker::PhantomData,
sys_ptr: sys::SysPtr::force_mut(outer.sys()),
}
}
#special_constructors
#inner_methods
}
pub use re_export::#inner_builtin;
impl #outer_class {
#outer_methods
}
#inner_builders
#outer_builders
#enums
};
let has_sidecar_module = !outer_builders.is_empty();
GeneratedBuiltin {
code,
has_sidecar_module,
}
}
fn make_builtin_methods(
builtin_class: &BuiltinClass,
variant_shout_name: &Ident,
methods: &[BuiltinMethod],
ctx: &mut Context,
) -> (FnDefinitions, FnDefinitions) {
let inner_defs = methods
.iter()
.filter(|&method| !method.is_exposed_in_outer)
.map(|method| {
make_builtin_method_definition(builtin_class, variant_shout_name, method, ctx)
});
let inner_defs = FnDefinitions::expand(inner_defs);
let outer_defs = methods
.iter()
.filter(|&method| method.is_exposed_in_outer)
.map(|method| {
make_builtin_method_definition(builtin_class, variant_shout_name, method, ctx)
});
let outer_defs = FnDefinitions::expand(outer_defs);
(inner_defs, outer_defs)
}
fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStream {
if class_name.godot_ty == "Array" {
quote! {
pub fn from_outer_typed<T>(outer: &Array<T>) -> Self
where
T: crate::meta::Element
{
Self {
_outer_lifetime: std::marker::PhantomData,
sys_ptr: sys::SysPtr::force_mut(outer.sys()),
}
}
}
} else if class_name.godot_ty == "Dictionary" {
quote! {
pub fn from_outer_typed<K, V>(outer: &Dictionary<K, V>) -> Self
where
K: crate::meta::Element,
V: crate::meta::Element,
{
Self {
_outer_lifetime: std::marker::PhantomData,
sys_ptr: sys::SysPtr::force_mut(outer.sys()),
}
}
}
} else {
TokenStream::new()
}
}
fn make_builtin_method_definition(
builtin_class: &BuiltinClass,
variant_shout_name: &Ident,
method: &BuiltinMethod,
ctx: &mut Context,
) -> FnDefinition {
let FnDirection::Outbound { hash } = method.direction() else {
unreachable!("builtin methods are never virtual")
};
let builtin_name = builtin_class.name();
let builtin_name_str = builtin_name.rust_ty.to_string();
let method_name_str = method.godot_name();
let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") {
let variant_type = quote! { sys::VariantType::#variant_shout_name };
let variant_type_str = &builtin_name.godot_ty;
quote! {
fptr_by_key(sys::lazy_keys::BuiltinMethodKey {
variant_type: #variant_type,
variant_type_str: #variant_type_str,
method_name: #method_name_str,
hash: #hash,
})
}
} else {
let table_index = ctx.get_table_index(&MethodTableKey::from_builtin(builtin_class, method));
quote! { fptr_by_index(#table_index) }
};
let ffi_arg_in = if method.is_exposed_in_outer {
quote! { sys::SysPtr::force_mut(self.sys()) }
} else {
quote! { self.sys_ptr }
};
let receiver = functions_common::make_receiver(method.qualifier(), ffi_arg_in);
let object_ptr = &receiver.ffi_arg;
let ptrcall_invocation = quote! {
let method_bind = sys::builtin_method_table().#fptr_access;
Signature::<CallParams, CallRet>::out_builtin_ptrcall(
method_bind,
#builtin_name_str,
#method_name_str,
#object_ptr,
args
)
};
let varcall_invocation = quote! {
let method_bind = sys::builtin_method_table().#fptr_access;
Signature::<CallParams, CallRet>::out_builtin_ptrcall_varargs(
method_bind,
#builtin_name_str,
#method_name_str,
#object_ptr,
args,
varargs
)
};
functions_common::make_function_definition(
method,
&FnCode {
receiver,
varcall_invocation,
ptrcall_invocation,
is_virtual_required: false,
is_varcall_fallible: false,
},
&TokenStream::new(),
)
}