use functions_common as fns;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use crate::generator::functions_common;
use crate::generator::functions_common::{
FnArgExpr, FnCode, FnKind, FnParamDecl, make_arg_expr, make_param_or_field_type,
};
use crate::models::domain::{FnParam, FnQualifier, Function, RustTy, TyName};
use crate::util::{ident, safe_ident};
use crate::{conv, special_cases};
pub fn make_function_definition_with_defaults(
sig: &dyn Function,
code: &FnCode,
full_fn_name: &Ident,
cfg_attributes: &TokenStream,
) -> (TokenStream, TokenStream) {
let (default_fn_params, required_fn_params): (Vec<_>, Vec<_>) = sig
.params()
.iter()
.partition(|arg| arg.default_value.is_some());
let simple_fn_name = safe_ident(sig.name());
let extended_fn_name = format_ident!("{}_ex", simple_fn_name);
let default_parameter_usage = format!(
"To set the default parameters, use [`Self::{extended_fn_name}`] and its builder methods. See [the book](https://godot-rust.github.io/book/godot-api/functions.html#default-parameters) for detailed usage instructions."
);
let vis = functions_common::make_vis(sig.is_private());
let (builder_doc, surround_class_path) = make_extender_doc(sig, &extended_fn_name);
let ExtenderReceiver {
object_fn_param,
object_arg,
} = make_extender_receiver(sig);
let Extender {
builder_ty,
builder_ctor_params,
class_method_required_params,
class_method_required_params_lifetimed,
class_method_required_args,
builder_default_variable_decls,
builder_field_decls,
builder_field_init_exprs,
builder_field_names,
builder_methods,
full_fn_args,
} = make_extender(
sig.name(),
object_fn_param,
&required_fn_params,
&default_fn_params,
);
let return_decl = &sig.return_value().decl;
let (maybe_deprecated, maybe_expect_deprecated) = fns::make_deprecation_attribute(sig);
let receiver_self = &code.receiver.self_prefix;
let simple_receiver_param = &code.receiver.param;
let extended_receiver_param = &code.receiver.param_lifetime_ex;
let builders = quote! {
#[doc = #builder_doc]
#[must_use]
#cfg_attributes
#vis struct #builder_ty<'ex> {
_phantom: std::marker::PhantomData<&'ex ()>,
#( #builder_field_decls, )*
}
#[allow(clippy::wrong_self_convention, clippy::redundant_field_names, clippy::needless_update)]
impl<'ex> #builder_ty<'ex> {
fn new(
#( #builder_ctor_params, )*
) -> Self {
#( #builder_default_variable_decls )*
Self {
_phantom: std::marker::PhantomData,
#( #builder_field_names: #builder_field_init_exprs, )*
}
}
#( #builder_methods )*
#[inline]
#maybe_expect_deprecated
pub fn done(self) #return_decl {
let Self { _phantom, #( #builder_field_names, )* } = self;
#surround_class_path::#full_fn_name(
#( #full_fn_args, )* )
}
}
};
let functions = quote! {
#maybe_deprecated
#maybe_expect_deprecated
#[doc = #default_parameter_usage]
#[inline]
#vis fn #simple_fn_name (
#simple_receiver_param
#( #class_method_required_params, )*
) #return_decl {
#receiver_self #extended_fn_name(
#( #class_method_required_args, )*
).done()
}
#maybe_deprecated
#[inline]
#vis fn #extended_fn_name<'ex> (
#extended_receiver_param
#( #class_method_required_params_lifetimed, )*
) -> #builder_ty<'ex> {
#builder_ty::new(
#object_arg
#( #class_method_required_args, )*
)
}
};
(functions, builders)
}
pub fn function_uses_default_params(sig: &dyn Function) -> bool {
let fn_declares_default_params = sig.params().iter().any(|arg| arg.default_value.is_some())
&& !special_cases::is_method_excluded_from_default_params(
sig.surrounding_class(),
sig.name(),
);
if fn_declares_default_params && sig.is_builtin() {
return sig.is_exposed_outer_builtin();
}
fn_declares_default_params
}
#[derive(Default)]
struct ExtenderReceiver {
object_fn_param: Option<FnParam>,
object_arg: TokenStream,
}
struct Extender {
builder_ty: Ident,
builder_ctor_params: Vec<TokenStream>,
builder_default_variable_decls: Vec<TokenStream>,
builder_field_decls: Vec<TokenStream>,
builder_field_init_exprs: Vec<TokenStream>,
builder_field_names: Vec<Ident>,
builder_methods: Vec<TokenStream>,
full_fn_args: Vec<TokenStream>,
class_method_required_params: Vec<TokenStream>,
class_method_required_params_lifetimed: Vec<TokenStream>,
class_method_required_args: Vec<TokenStream>,
}
fn make_extender_doc(sig: &dyn Function, extended_fn_name: &Ident) -> (String, TokenStream) {
let surround_class_prefix;
let builder_doc;
#[allow(clippy::uninlined_format_args)]
match sig.surrounding_class() {
Some(TyName { rust_ty, .. }) => {
surround_class_prefix = make_qualified_type(sig, rust_ty, true);
let path = if sig.is_builtin() {
format!("crate::builtin::{rust_ty}")
} else {
format!("super::{rust_ty}")
};
builder_doc = format!(
"Default-param extender for [`{class}::{method}`][{path}::{method}].",
class = rust_ty,
method = extended_fn_name,
path = path,
);
}
None => {
surround_class_prefix = TokenStream::new();
builder_doc = format!(
"Default-param extender for [`{function}`][super::{function}].",
function = extended_fn_name
);
}
};
(builder_doc, surround_class_prefix)
}
fn make_extender_receiver(sig: &dyn Function) -> ExtenderReceiver {
let builder_mut = match sig.qualifier() {
FnQualifier::Const | FnQualifier::Static => quote! {},
FnQualifier::Mut => quote! { mut },
FnQualifier::Global => {
unreachable!("default parameters not supported for global methods; {sig}")
}
};
match sig.surrounding_class() {
Some(surrounding_class) if !sig.qualifier().is_static_or_global() => {
let ty = make_qualified_type(sig, &surrounding_class.rust_ty, false);
ExtenderReceiver {
object_fn_param: Some(FnParam {
name: ident("surround_object"),
type_: RustTy::ExtenderReceiver {
tokens: quote! { &'ex #builder_mut #ty },
},
default_value: None,
}),
object_arg: quote! { self, },
}
}
_ => ExtenderReceiver::default(),
}
}
fn make_extender(
fn_name: &str,
receiver_param: Option<FnParam>,
required_params: &[&FnParam],
default_params: &[&FnParam],
) -> Extender {
let all_fn_params = receiver_param
.iter()
.chain(required_params.iter().cloned())
.chain(default_params.iter().cloned());
let param_decl = FnParamDecl::FnPublicLifetime;
let ctor_decl = FnKind::ExBuilderConstructorLifetimed;
let default_len = default_params.len();
let public_required =
fns::make_params_exprs(required_params.iter().cloned(), FnKind::DefaultSimpleOrEx);
let class_method_required_params = public_required.param_decls;
let class_method_required_args = public_required.arg_exprs;
let class_method_required_params_lifetimed = fns::make_params_exprs(
required_params.iter().cloned(),
FnKind::DefaultSimpleOrExLifetimed,
)
.param_decls;
let receiver_and_required_params = receiver_param.iter().chain(required_params.iter().cloned());
let ctor_requireds = fns::make_params_exprs(receiver_and_required_params.clone(), ctor_decl);
let builder_ctor_params = ctor_requireds.param_decls;
let builder_field_names = all_fn_params
.clone()
.map(|param| param.name.clone())
.collect();
let builder_field_init_exprs = {
let mut builder_field_init_exprs = ctor_requireds.arg_exprs;
let ctor_defaults = fns::make_params_exprs(
default_params.iter().cloned(),
FnKind::ExBuilderConstructorDefault,
);
builder_field_init_exprs.extend(ctor_defaults.arg_exprs);
builder_field_init_exprs
};
let mut builder_default_variable_decls = Vec::with_capacity(default_len);
let mut builder_methods = Vec::with_capacity(default_len);
for param in default_params.iter() {
let default_value = param.default_value.as_ref().expect("default value absent");
let FnParam { name, type_, .. } = param;
let variable_decl = quote! {
let #name = #default_value;
};
let mut dummy_lifetime_gen = fns::LifetimeGen::new();
let (param_decl, _param_callsig_ty) =
make_param_or_field_type(name, type_, param_decl, &mut dummy_lifetime_gen);
let arg_expr = make_arg_expr(name, type_, FnArgExpr::StoreInField);
let method = quote! {
#[inline]
pub fn #name(self, #param_decl) -> Self {
Self {
#name: #arg_expr,
..self
}
}
};
builder_default_variable_decls.push(variable_decl);
builder_methods.push(method);
}
let done_fn = fns::make_params_exprs(all_fn_params, FnKind::ExBuilderDone);
let builder_field_decls = done_fn.param_decls;
let full_fn_args = done_fn.arg_exprs;
Extender {
builder_ty: format_ident!("Ex{}", conv::to_pascal_case(fn_name)),
builder_ctor_params,
builder_default_variable_decls,
builder_methods,
builder_field_decls,
builder_field_names,
full_fn_args,
builder_field_init_exprs,
class_method_required_params,
class_method_required_params_lifetimed,
class_method_required_args,
}
}
fn make_qualified_type(
sig: &dyn Function,
class_or_builtin: &Ident,
with_inner_lifetime: bool,
) -> TokenStream {
if sig.is_exposed_outer_builtin() {
quote! { #class_or_builtin }
} else if with_inner_lifetime && sig.is_builtin() {
quote! { re_export::#class_or_builtin<'ex> }
} else {
quote! { re_export::#class_or_builtin }
}
}