Skip to content

Commit 81e1bff

Browse files
committed
Handle generics in NativeScript derive and #[methods]
The derive macros are now aware of both lifetime and type parameters. `'static` bounds are added to both kinds, which improves the error message shown when a user tries to put a lifetime into a NativeClass. This behavior is chosen because unlike type parameters, lifetime parameters aren't very useful in this context: only `'static` would be supported anyway, and any attempted usage is hightly likely to have originated from user error.
1 parent c58a87e commit 81e1bff

File tree

12 files changed

+329
-51
lines changed

12 files changed

+329
-51
lines changed

gdnative-derive/src/extend_bounds.rs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::HashSet;
22

3+
use syn::spanned::Spanned;
34
use syn::visit::{self, Visit};
45
use syn::{Generics, Ident, Type, TypePath};
56

@@ -46,7 +47,12 @@ impl<'ast> BoundsVisitor<'ast> {
4647
}
4748
}
4849

49-
pub fn with_visitor<'ast, F>(generics: Generics, bound: &syn::Path, op: F) -> Generics
50+
pub fn with_visitor<'ast, F>(
51+
generics: Generics,
52+
bound: Option<&syn::Path>,
53+
lifetime: Option<&str>,
54+
op: F,
55+
) -> Generics
5056
where
5157
F: FnOnce(&mut BoundsVisitor<'ast>),
5258
{
@@ -55,28 +61,57 @@ where
5561
op(&mut visitor);
5662

5763
// where thing: is_trait
58-
fn where_predicate(thing: Type, is_trait: syn::Path) -> syn::WherePredicate {
64+
fn where_predicate(
65+
thing: Type,
66+
bound: Option<&syn::Path>,
67+
lifetime: Option<&str>,
68+
) -> syn::WherePredicate {
69+
let mut bounds = vec![];
70+
71+
if let Some(bound) = bound {
72+
bounds.push(syn::TypeParamBound::Trait(syn::TraitBound {
73+
paren_token: None,
74+
modifier: syn::TraitBoundModifier::None,
75+
lifetimes: None,
76+
path: bound.clone(),
77+
}));
78+
}
79+
80+
if let Some(lifetime) = lifetime {
81+
bounds.push(syn::TypeParamBound::Lifetime(syn::Lifetime::new(
82+
lifetime,
83+
thing.span(),
84+
)));
85+
}
86+
5987
syn::WherePredicate::Type(syn::PredicateType {
6088
lifetimes: None,
6189
bounded_ty: thing,
6290
colon_token: <Token![:]>::default(),
63-
bounds: vec![syn::TypeParamBound::Trait(syn::TraitBound {
64-
paren_token: None,
65-
modifier: syn::TraitBoundModifier::None,
66-
lifetimes: None,
67-
path: is_trait,
68-
})]
69-
.into_iter()
70-
.collect(),
91+
bounds: bounds.into_iter().collect(),
7192
})
7293
}
7394

7495
// place bounds on all used type parameters and associated types
75-
let new_predicates = visitor
96+
let mut new_predicates = visitor
7697
.used
7798
.into_iter()
7899
.cloned()
79-
.map(|bounded_ty| where_predicate(syn::Type::Path(bounded_ty), bound.clone()));
100+
.map(|bounded_ty| where_predicate(syn::Type::Path(bounded_ty), bound, lifetime))
101+
.collect::<Vec<_>>();
102+
103+
// Add lifetime bounds to all type parameters, regardless of usage, due to how
104+
// lifetimes for generic types are determined.
105+
new_predicates.extend(generics.type_params().map(|param| {
106+
where_predicate(
107+
syn::Type::Path(syn::TypePath {
108+
qself: None,
109+
path: param.ident.clone().into(),
110+
}),
111+
None,
112+
lifetime,
113+
)
114+
}));
80115

81116
let mut generics = generics;
82117
generics

gdnative-derive/src/lib.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ extern crate quote;
88
use proc_macro::TokenStream;
99
use proc_macro2::TokenStream as TokenStream2;
1010
use quote::ToTokens;
11-
use syn::{AttributeArgs, DeriveInput, ItemFn, ItemImpl};
11+
use syn::{AttributeArgs, DeriveInput, ItemFn, ItemImpl, ItemType};
1212

1313
mod extend_bounds;
1414
mod methods;
@@ -455,6 +455,41 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream {
455455
TokenStream::from(derived)
456456
}
457457

458+
/// Wires up necessary internals for a concrete monomorphization of a generic `NativeClass`,
459+
/// represented as a type alias, so it can be registered.
460+
///
461+
/// The monomorphized type will be available to Godot under the name of the type alias,
462+
/// once registered.
463+
///
464+
/// For more context, please refer to [gdnative::derive::NativeClass](NativeClass).
465+
///
466+
/// # Examples
467+
///
468+
/// ```ignore
469+
/// #[derive(NativeClass)]
470+
/// struct Pair<T> {
471+
/// a: T,
472+
/// b: T,
473+
/// }
474+
///
475+
/// #[gdnative::derive::monomorphize]
476+
/// type IntPair = Pair<i32>;
477+
///
478+
/// fn init(handle: InitHandle) {
479+
/// handle.add_class::<IntPair>();
480+
/// }
481+
/// ```
482+
#[proc_macro_attribute]
483+
pub fn monomorphize(meta: TokenStream, input: TokenStream) -> TokenStream {
484+
let args = parse_macro_input!(meta as AttributeArgs);
485+
let item_type = parse_macro_input!(input as ItemType);
486+
487+
match native_script::derive_monomorphize(args, item_type) {
488+
Ok(tokens) => tokens.into(),
489+
Err(err) => err.to_compile_error().into(),
490+
}
491+
}
492+
458493
#[proc_macro_derive(ToVariant, attributes(variant))]
459494
pub fn derive_to_variant(input: TokenStream) -> TokenStream {
460495
match variant::derive_to_variant(variant::ToVariantTrait::ToVariant, input) {

gdnative-derive/src/methods.rs

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use syn::{spanned::Spanned, FnArg, ImplItem, ItemImpl, Pat, PatIdent, Signature, Type};
1+
use syn::{spanned::Spanned, FnArg, Generics, ImplItem, ItemImpl, Pat, PatIdent, Signature, Type};
22

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

334335
let class_name = export.class_ty;
335336

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

393-
let method = wrap_method(&class_name, &export_method)
394+
let method = wrap_method(&class_name, &impl_block.generics, &export_method)
394395
.unwrap_or_else(|err| err.to_compile_error());
395396

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

412413
#derived
413-
impl gdnative::export::NativeClassMethods for #class_name {
414+
impl #impl_generics gdnative::export::NativeClassMethods for #class_name #where_clause {
414415
fn nativeclass_register(#builder: &::gdnative::export::ClassBuilder<Self>) {
415416
use gdnative::export::*;
416417

@@ -767,11 +768,17 @@ pub(crate) fn expand_godot_wrap_method(
767768
return Err(errors);
768769
}
769770

770-
wrap_method(&class_name, &export_method.expect("ExportMethod is valid")).map_err(|e| vec![e])
771+
wrap_method(
772+
&class_name,
773+
&Generics::default(),
774+
&export_method.expect("ExportMethod is valid"),
775+
)
776+
.map_err(|e| vec![e])
771777
}
772778

773779
fn wrap_method(
774780
class_name: &Type,
781+
generics: &Generics,
775782
export_method: &ExportMethod,
776783
) -> Result<TokenStream2, syn::Error> {
777784
let ExportMethod {
@@ -783,6 +790,21 @@ fn wrap_method(
783790
let gdnative_core = crate::crate_gdnative_core();
784791
let automatically_derived = crate::automatically_derived();
785792

793+
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
794+
let turbofish_ty_generics = ty_generics.as_turbofish();
795+
796+
let generic_marker_decl = if generics.params.is_empty() {
797+
quote!(())
798+
} else {
799+
quote!(core::marker::PhantomData #ty_generics)
800+
};
801+
802+
let generic_marker_ctor = if generics.params.is_empty() {
803+
quote!(())
804+
} else {
805+
quote!(core::marker::PhantomData)
806+
};
807+
786808
let sig_span = sig.ident.span();
787809
let ret_span = sig.output.span();
788810

@@ -875,8 +897,8 @@ fn wrap_method(
875897

876898
quote_spanned! { sig_span =>
877899
#automatically_derived
878-
impl #gdnative_async::StaticArgsAsyncMethod<#class_name> for ThisMethod {
879-
type Args = Args;
900+
impl #impl_generics #gdnative_async::StaticArgsAsyncMethod<#class_name> for ThisMethod #ty_generics #where_clause {
901+
type Args = Args #ty_generics;
880902

881903
fn spawn_with(
882904
&self,
@@ -885,7 +907,7 @@ fn wrap_method(
885907
__spawner.spawn(move |__ctx, __this, __args| {
886908
let __future = __this
887909
.#map_method(move |__rust_val, __base| {
888-
let Args { #(#destructure_arg_list,)* } = __args;
910+
let Args { #(#destructure_arg_list,)* __generic_marker } = __args;
889911

890912
#[allow(unused_unsafe)]
891913
unsafe {
@@ -916,17 +938,19 @@ fn wrap_method(
916938
}
917939
}
918940

919-
#gdnative_async::Async::new(#gdnative_async::StaticArgs::new(ThisMethod))
941+
#gdnative_async::Async::new(#gdnative_async::StaticArgs::new(ThisMethod #turbofish_ty_generics {
942+
_marker: #generic_marker_ctor,
943+
}))
920944
}
921945
} else {
922946
quote_spanned! { sig_span =>
923947
#automatically_derived
924-
impl #gdnative_core::export::StaticArgsMethod<#class_name> for ThisMethod {
925-
type Args = Args;
948+
impl #impl_generics #gdnative_core::export::StaticArgsMethod<#class_name> for ThisMethod #ty_generics #where_clause {
949+
type Args = Args #ty_generics;
926950
fn call(
927951
&self,
928952
__this: TInstance<'_, #class_name, #gdnative_core::object::ownership::Shared>,
929-
Args { #(#destructure_arg_list,)* }: Args,
953+
Args { #(#destructure_arg_list,)* __generic_marker }: Self::Args,
930954
) -> #gdnative_core::core_types::Variant {
931955
__this
932956
.#map_method(|__rust_val, __base| {
@@ -950,23 +974,48 @@ fn wrap_method(
950974
}
951975
}
952976

953-
#gdnative_core::export::StaticArgs::new(ThisMethod)
977+
#gdnative_core::export::StaticArgs::new(ThisMethod #turbofish_ty_generics {
978+
_marker: #generic_marker_ctor,
979+
})
954980
}
955981
};
956982

983+
// Necessary standard traits have to be implemented manually because the default derive isn't smart enough.
957984
let output = quote_spanned! { sig_span =>
958985
{
959-
#[derive(Copy, Clone, Default)]
960-
struct ThisMethod;
986+
struct ThisMethod #ty_generics #where_clause {
987+
_marker: #generic_marker_decl,
988+
}
989+
990+
impl #impl_generics Copy for ThisMethod #ty_generics #where_clause {}
991+
impl #impl_generics Clone for ThisMethod #ty_generics #where_clause {
992+
fn clone(&self) -> Self {
993+
*self
994+
}
995+
}
996+
997+
impl #impl_generics Default for ThisMethod #ty_generics #where_clause {
998+
fn default() -> Self {
999+
Self {
1000+
_marker: #generic_marker_ctor,
1001+
}
1002+
}
1003+
}
1004+
1005+
unsafe impl #impl_generics Send for ThisMethod #ty_generics #where_clause {}
1006+
unsafe impl #impl_generics Sync for ThisMethod #ty_generics #where_clause {}
9611007

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

9661012
#[derive(FromVarargs)]
9671013
#automatically_derived
968-
struct Args {
1014+
struct Args #ty_generics #where_clause {
9691015
#(#declare_arg_list,)*
1016+
1017+
#[skip]
1018+
__generic_marker: #generic_marker_decl,
9701019
}
9711020

9721021
#impl_body

0 commit comments

Comments
 (0)