Skip to content

Handle generics in NativeScript derive and #[methods] #983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 47 additions & 12 deletions gdnative-derive/src/extend_bounds.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashSet;

use syn::spanned::Spanned;
use syn::visit::{self, Visit};
use syn::{Generics, Ident, Type, TypePath};

Expand Down Expand Up @@ -46,7 +47,12 @@ impl<'ast> BoundsVisitor<'ast> {
}
}

pub fn with_visitor<'ast, F>(generics: Generics, bound: &syn::Path, op: F) -> Generics
pub fn with_visitor<'ast, F>(
generics: Generics,
bound: Option<&syn::Path>,
lifetime: Option<&str>,
op: F,
) -> Generics
where
F: FnOnce(&mut BoundsVisitor<'ast>),
{
Expand All @@ -55,28 +61,57 @@ where
op(&mut visitor);

// where thing: is_trait
fn where_predicate(thing: Type, is_trait: syn::Path) -> syn::WherePredicate {
fn where_predicate(
thing: Type,
bound: Option<&syn::Path>,
lifetime: Option<&str>,
) -> syn::WherePredicate {
let mut bounds = vec![];

if let Some(bound) = bound {
bounds.push(syn::TypeParamBound::Trait(syn::TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path: bound.clone(),
}));
}

if let Some(lifetime) = lifetime {
bounds.push(syn::TypeParamBound::Lifetime(syn::Lifetime::new(
lifetime,
thing.span(),
)));
}

syn::WherePredicate::Type(syn::PredicateType {
lifetimes: None,
bounded_ty: thing,
colon_token: <Token![:]>::default(),
bounds: vec![syn::TypeParamBound::Trait(syn::TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path: is_trait,
})]
.into_iter()
.collect(),
bounds: bounds.into_iter().collect(),
})
}

// place bounds on all used type parameters and associated types
let new_predicates = visitor
let mut new_predicates = visitor
.used
.into_iter()
.cloned()
.map(|bounded_ty| where_predicate(syn::Type::Path(bounded_ty), bound.clone()));
.map(|bounded_ty| where_predicate(syn::Type::Path(bounded_ty), bound, lifetime))
.collect::<Vec<_>>();

// Add lifetime bounds to all type parameters, regardless of usage, due to how
// lifetimes for generic types are determined.
new_predicates.extend(generics.type_params().map(|param| {
where_predicate(
syn::Type::Path(syn::TypePath {
qself: None,
path: param.ident.clone().into(),
}),
None,
lifetime,
)
}));

let mut generics = generics;
generics
Expand Down
53 changes: 51 additions & 2 deletions gdnative-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ extern crate quote;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use syn::{AttributeArgs, DeriveInput, ItemFn, ItemImpl};
use syn::{AttributeArgs, DeriveInput, ItemFn, ItemImpl, ItemType};

mod extend_bounds;
mod methods;
Expand Down Expand Up @@ -455,6 +455,41 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream {
TokenStream::from(derived)
}

/// Wires up necessary internals for a concrete monomorphization of a generic `NativeClass`,
/// represented as a type alias, so it can be registered.
///
/// The monomorphized type will be available to Godot under the name of the type alias,
/// once registered.
///
/// For more context, please refer to [gdnative::derive::NativeClass](NativeClass).
///
/// # Examples
///
/// ```ignore
/// #[derive(NativeClass)]
/// struct Pair<T> {
/// a: T,
/// b: T,
/// }
///
/// #[gdnative::derive::monomorphize]
/// type IntPair = Pair<i32>;
///
/// fn init(handle: InitHandle) {
/// handle.add_class::<IntPair>();
/// }
/// ```
#[proc_macro_attribute]
pub fn monomorphize(meta: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(meta as AttributeArgs);
let item_type = parse_macro_input!(input as ItemType);

match native_script::derive_monomorphize(args, item_type) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}

#[proc_macro_derive(ToVariant, attributes(variant))]
pub fn derive_to_variant(input: TokenStream) -> TokenStream {
match variant::derive_to_variant(variant::ToVariantTrait::ToVariant, input) {
Expand Down Expand Up @@ -493,7 +528,21 @@ pub fn derive_from_variant(input: TokenStream) -> TokenStream {
/// #[opt] baz: Option<Ref<Node>>,
/// }
/// ```
#[proc_macro_derive(FromVarargs, attributes(opt))]
///
/// ## Field attributes
///
/// Attributes can be used to customize behavior of certain fields. All attributes are optional.
///
/// ### `#[opt]`
///
/// Marks an argument as optional. Required arguments must precede all optional arguments.
/// Default values are obtained through `Default::default`.
///
/// ### `#[skip]`
///
/// Instructs the macro to skip a field. Skipped fields do not affect the signature of the
/// argument list. They may be located anywhere. Values are obtained through `Default::default`.
#[proc_macro_derive(FromVarargs, attributes(opt, skip))]
pub fn derive_from_varargs(input: TokenStream) -> TokenStream {
let derive_input = syn::parse_macro_input!(input as syn::DeriveInput);
match varargs::derive_from_varargs(derive_input) {
Expand Down
79 changes: 64 additions & 15 deletions gdnative-derive/src/methods.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use syn::{spanned::Spanned, FnArg, ImplItem, ItemImpl, Pat, PatIdent, Signature, Type};
use syn::{spanned::Spanned, FnArg, Generics, ImplItem, ItemImpl, Pat, PatIdent, Signature, Type};

use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens};
Expand Down Expand Up @@ -330,6 +330,7 @@ pub(crate) struct ExportArgs {
pub(crate) fn derive_methods(item_impl: ItemImpl) -> TokenStream2 {
let derived = crate::automatically_derived();
let (impl_block, export) = impl_gdnative_expose(item_impl);
let (impl_generics, _, where_clause) = impl_block.generics.split_for_impl();

let class_name = export.class_ty;

Expand Down Expand Up @@ -390,7 +391,7 @@ pub(crate) fn derive_methods(item_impl: ItemImpl) -> TokenStream2 {
quote_spanned!(ret_span=>)
};

let method = wrap_method(&class_name, &export_method)
let method = wrap_method(&class_name, &impl_block.generics, &export_method)
.unwrap_or_else(|err| err.to_compile_error());

quote_spanned!( sig_span=>
Expand All @@ -410,7 +411,7 @@ pub(crate) fn derive_methods(item_impl: ItemImpl) -> TokenStream2 {
#impl_block

#derived
impl gdnative::export::NativeClassMethods for #class_name {
impl #impl_generics gdnative::export::NativeClassMethods for #class_name #where_clause {
fn nativeclass_register(#builder: &::gdnative::export::ClassBuilder<Self>) {
use gdnative::export::*;

Expand Down Expand Up @@ -767,11 +768,17 @@ pub(crate) fn expand_godot_wrap_method(
return Err(errors);
}

wrap_method(&class_name, &export_method.expect("ExportMethod is valid")).map_err(|e| vec![e])
wrap_method(
&class_name,
&Generics::default(),
&export_method.expect("ExportMethod is valid"),
)
.map_err(|e| vec![e])
}

fn wrap_method(
class_name: &Type,
generics: &Generics,
export_method: &ExportMethod,
) -> Result<TokenStream2, syn::Error> {
let ExportMethod {
Expand All @@ -783,6 +790,21 @@ fn wrap_method(
let gdnative_core = crate::crate_gdnative_core();
let automatically_derived = crate::automatically_derived();

let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let turbofish_ty_generics = ty_generics.as_turbofish();

let generic_marker_decl = if generics.params.is_empty() {
quote!(())
} else {
quote!(core::marker::PhantomData #ty_generics)
};

let generic_marker_ctor = if generics.params.is_empty() {
quote!(())
} else {
quote!(core::marker::PhantomData)
};

let sig_span = sig.ident.span();
let ret_span = sig.output.span();

Expand Down Expand Up @@ -875,8 +897,8 @@ fn wrap_method(

quote_spanned! { sig_span =>
#automatically_derived
impl #gdnative_async::StaticArgsAsyncMethod<#class_name> for ThisMethod {
type Args = Args;
impl #impl_generics #gdnative_async::StaticArgsAsyncMethod<#class_name> for ThisMethod #ty_generics #where_clause {
type Args = Args #ty_generics;

fn spawn_with(
&self,
Expand All @@ -885,7 +907,7 @@ fn wrap_method(
__spawner.spawn(move |__ctx, __this, __args| {
let __future = __this
.#map_method(move |__rust_val, __base| {
let Args { #(#destructure_arg_list,)* } = __args;
let Args { #(#destructure_arg_list,)* __generic_marker } = __args;

#[allow(unused_unsafe)]
unsafe {
Expand Down Expand Up @@ -916,17 +938,19 @@ fn wrap_method(
}
}

#gdnative_async::Async::new(#gdnative_async::StaticArgs::new(ThisMethod))
#gdnative_async::Async::new(#gdnative_async::StaticArgs::new(ThisMethod #turbofish_ty_generics {
_marker: #generic_marker_ctor,
}))
}
} else {
quote_spanned! { sig_span =>
#automatically_derived
impl #gdnative_core::export::StaticArgsMethod<#class_name> for ThisMethod {
type Args = Args;
impl #impl_generics #gdnative_core::export::StaticArgsMethod<#class_name> for ThisMethod #ty_generics #where_clause {
type Args = Args #ty_generics;
fn call(
&self,
__this: TInstance<'_, #class_name, #gdnative_core::object::ownership::Shared>,
Args { #(#destructure_arg_list,)* }: Args,
Args { #(#destructure_arg_list,)* __generic_marker }: Self::Args,
) -> #gdnative_core::core_types::Variant {
__this
.#map_method(|__rust_val, __base| {
Expand All @@ -950,23 +974,48 @@ fn wrap_method(
}
}

#gdnative_core::export::StaticArgs::new(ThisMethod)
#gdnative_core::export::StaticArgs::new(ThisMethod #turbofish_ty_generics {
_marker: #generic_marker_ctor,
})
}
};

// Necessary standard traits have to be implemented manually because the default derive isn't smart enough.
let output = quote_spanned! { sig_span =>
{
#[derive(Copy, Clone, Default)]
struct ThisMethod;
struct ThisMethod #ty_generics #where_clause {
_marker: #generic_marker_decl,
}

impl #impl_generics Copy for ThisMethod #ty_generics #where_clause {}
impl #impl_generics Clone for ThisMethod #ty_generics #where_clause {
fn clone(&self) -> Self {
*self
}
}

impl #impl_generics Default for ThisMethod #ty_generics #where_clause {
fn default() -> Self {
Self {
_marker: #generic_marker_ctor,
}
}
}

unsafe impl #impl_generics Send for ThisMethod #ty_generics #where_clause {}
unsafe impl #impl_generics Sync for ThisMethod #ty_generics #where_clause {}

use #gdnative_core::export::{NativeClass, OwnerArg};
use #gdnative_core::object::{Instance, TInstance};
use #gdnative_core::derive::FromVarargs;

#[derive(FromVarargs)]
#automatically_derived
struct Args {
struct Args #ty_generics #where_clause {
#(#declare_arg_list,)*

#[skip]
__generic_marker: #generic_marker_decl,
}

#impl_body
Expand Down
Loading