Skip to content

Commit 501596c

Browse files
authored
Cast from Gd<T> to a script (#45)
`Gd<T>` can now be cast to a `RsRef<T>` to call methods on the attached script.
1 parent 6b38c9e commit 501596c

File tree

10 files changed

+324
-10
lines changed

10 files changed

+324
-10
lines changed

Cargo.lock

Lines changed: 25 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ proc-macro2 = "1.0.68"
1919
quote = "1.0.33"
2020
syn = "2.0.38"
2121
const-str = "0.5.6"
22+
thiserror = "1"
2223

2324
godot-rust-script-derive = { path = "derive" }
2425
tests-scripts-lib = { path = "tests-scripts-lib" }

derive/src/impl_attribute.rs

Lines changed: 159 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66

77
use proc_macro2::TokenStream;
88
use quote::{quote, quote_spanned, ToTokens};
9-
use syn::{parse_macro_input, spanned::Spanned, FnArg, ImplItem, ItemImpl, ReturnType, Type};
9+
use syn::{
10+
parse2, parse_macro_input, spanned::Spanned, FnArg, Ident, ImplItem, ImplItemFn, ItemImpl,
11+
PatIdent, PatType, ReturnType, Token, Type, Visibility,
12+
};
1013

1114
use crate::{
12-
is_context_type, rust_to_variant_type,
15+
compile_error, is_context_type, rust_to_variant_type,
1316
type_paths::{godot_types, property_hints, string_name_ty, variant_ty},
1417
};
1518

@@ -173,12 +176,166 @@ pub fn godot_script_impl(
173176
);
174177
};
175178

179+
let pub_interface = generate_public_interface(&body);
180+
176181
quote! {
177182
#body
178183

179184
#trait_impl
180185

186+
#pub_interface
187+
181188
#metadata
182189
}
183190
.into()
184191
}
192+
193+
fn extract_script_name_from_type(impl_target: &syn::Type) -> Result<Ident, TokenStream> {
194+
match impl_target {
195+
Type::Array(_) => Err(compile_error("Arrays are not supported!", impl_target)),
196+
Type::BareFn(_) => Err(compile_error(
197+
"Bare functions are not supported!",
198+
impl_target,
199+
)),
200+
Type::Group(_) => Err(compile_error("Groups are not supported!", impl_target)),
201+
Type::ImplTrait(_) => Err(compile_error("Impl traits are not suppored!", impl_target)),
202+
Type::Infer(_) => Err(compile_error("Infer is not supported!", impl_target)),
203+
Type::Macro(_) => Err(compile_error("Macro types are not supported!", impl_target)),
204+
Type::Never(_) => Err(compile_error("Never type is not supported!", impl_target)),
205+
Type::Paren(_) => Err(compile_error("Unsupported type!", impl_target)),
206+
Type::Path(ref path) => Ok(path.path.segments.last().unwrap().ident.clone()),
207+
Type::Ptr(_) => Err(compile_error(
208+
"Pointer types are not supported!",
209+
impl_target,
210+
)),
211+
Type::Reference(_) => Err(compile_error("References are not supported!", impl_target)),
212+
Type::Slice(_) => Err(compile_error("Slices are not supported!", impl_target)),
213+
Type::TraitObject(_) => Err(compile_error(
214+
"Trait objects are not supported!",
215+
impl_target,
216+
)),
217+
Type::Tuple(_) => Err(compile_error("Tuples are not supported!", impl_target)),
218+
Type::Verbatim(_) => Err(compile_error("Verbatim is not supported!", impl_target)),
219+
_ => Err(compile_error("Unsupported type!", impl_target)),
220+
}
221+
}
222+
223+
fn sanitize_trait_fn_arg(arg: FnArg) -> FnArg {
224+
match arg {
225+
FnArg::Receiver(mut rec) => {
226+
rec.mutability = Some(Token![mut](rec.span()));
227+
rec.ty = parse2(quote!(&mut Self)).unwrap();
228+
229+
FnArg::Receiver(rec)
230+
}
231+
FnArg::Typed(ty) => FnArg::Typed(PatType {
232+
attrs: ty.attrs,
233+
pat: match *ty.pat {
234+
syn::Pat::Const(_)
235+
| syn::Pat::Lit(_)
236+
| syn::Pat::Macro(_)
237+
| syn::Pat::Or(_)
238+
| syn::Pat::Paren(_)
239+
| syn::Pat::Path(_)
240+
| syn::Pat::Range(_)
241+
| syn::Pat::Reference(_)
242+
| syn::Pat::Rest(_)
243+
| syn::Pat::Slice(_)
244+
| syn::Pat::Struct(_)
245+
| syn::Pat::Tuple(_)
246+
| syn::Pat::TupleStruct(_)
247+
| syn::Pat::Type(_)
248+
| syn::Pat::Verbatim(_)
249+
| syn::Pat::Wild(_) => ty.pat,
250+
syn::Pat::Ident(ident_pat) => Box::new(syn::Pat::Ident(PatIdent {
251+
attrs: ident_pat.attrs,
252+
by_ref: None,
253+
mutability: None,
254+
ident: ident_pat.ident,
255+
subpat: None,
256+
})),
257+
_ => ty.pat,
258+
},
259+
colon_token: ty.colon_token,
260+
ty: ty.ty,
261+
}),
262+
}
263+
}
264+
265+
fn generate_public_interface(impl_body: &ItemImpl) -> TokenStream {
266+
let impl_target = impl_body.self_ty.as_ref();
267+
let script_name = match extract_script_name_from_type(impl_target) {
268+
Ok(target) => target,
269+
Err(err) => return err,
270+
};
271+
272+
let trait_name = Ident::new(&format!("I{}", script_name), script_name.span());
273+
274+
let functions: Vec<_> = impl_body
275+
.items
276+
.iter()
277+
.filter_map(|func| match func {
278+
ImplItem::Fn(func @ ImplItemFn{ vis: Visibility::Public(_), .. }) => Some(func),
279+
_ => None,
280+
})
281+
.map(|func| {
282+
let mut sig = func.sig.clone();
283+
284+
sig.inputs = sig
285+
.inputs
286+
.into_iter()
287+
.filter(|arg| {
288+
!matches!(arg, FnArg::Typed(PatType { attrs: _, pat: _, colon_token: _, ty }) if matches!(ty.as_ref(), Type::Path(path) if path.path.segments.last().unwrap().ident == "Context"))
289+
})
290+
.map(sanitize_trait_fn_arg)
291+
.collect();
292+
sig
293+
})
294+
.collect();
295+
296+
let function_defs: TokenStream = functions
297+
.iter()
298+
.map(|func| quote_spanned! { func.span() => #func; })
299+
.collect();
300+
let function_impls: TokenStream = functions
301+
.iter()
302+
.map(|func| {
303+
let func_name = func.ident.to_string();
304+
let args: TokenStream = func
305+
.inputs
306+
.iter()
307+
.filter_map(|arg| match arg {
308+
FnArg::Receiver(_) => None,
309+
FnArg::Typed(arg) => Some(arg),
310+
})
311+
.map(|arg| {
312+
let pat = arg.pat.clone();
313+
314+
quote_spanned! { pat.span() =>
315+
::godot::meta::ToGodot::to_variant(&#pat),
316+
}
317+
})
318+
.collect();
319+
320+
quote_spanned! { func.span() =>
321+
#func {
322+
(*self).call(#func_name.into(), &[#args]).to()
323+
}
324+
}
325+
})
326+
.collect();
327+
328+
quote! {
329+
#[automatically_derived]
330+
#[allow(dead_code)]
331+
pub trait #trait_name {
332+
#function_defs
333+
}
334+
335+
#[automatically_derived]
336+
#[allow(dead_code)]
337+
impl #trait_name for ::godot_rust_script::RsRef<#impl_target> {
338+
#function_impls
339+
}
340+
}
341+
}

derive/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
3434
.unwrap_or_else(|| quote!(::godot_rust_script::godot::prelude::RefCounted));
3535

3636
let script_type_ident = opts.ident;
37+
let class_name = script_type_ident.to_string();
3738
let fields = opts.data.take_struct().unwrap().fields;
3839

3940
let public_fields = fields.iter().filter(|field| {
@@ -129,6 +130,8 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
129130
let output = quote! {
130131
impl ::godot_rust_script::GodotScript for #script_type_ident {
131132
type Base = #base_class;
133+
134+
const CLASS_NAME: &'static str = #class_name;
132135

133136
#get_fields_impl
134137

@@ -383,3 +386,7 @@ pub fn godot_script_impl(
383386
) -> proc_macro::TokenStream {
384387
impl_attribute::godot_script_impl(args, body)
385388
}
389+
390+
fn compile_error(message: &str, tokens: impl ToTokens) -> TokenStream {
391+
syn::Error::new_spanned(tokens, message).into_compile_error()
392+
}

rust-script/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rand = { workspace = true, optional = true }
1515
godot-rust-script-derive = { workspace = true, optional = true }
1616
once_cell = "1.19.0"
1717
const-str.workspace = true
18+
thiserror.workspace = true
1819

1920
[dev-dependencies]
2021
tests-scripts-lib = { path = "../tests-scripts-lib" }

rust-script/src/library.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ use godot::{
1313
sys::VariantType,
1414
};
1515

16+
pub use crate::script_registry::{
17+
CastToScript, GodotScript, GodotScriptImpl, RsRef, RustScriptMetaData, RustScriptMethodInfo,
18+
};
1619
use crate::script_registry::{
1720
CreateScriptInstanceData, GodotScriptObject, RustScriptPropertyInfo, RustScriptSignalInfo,
1821
};
19-
pub use crate::script_registry::{
20-
GodotScript, GodotScriptImpl, RustScriptMetaData, RustScriptMethodInfo,
21-
};
2222
pub use signals::{ScriptSignal, Signal, SignalArguments};
2323

2424
mod signals;

rust-script/src/runtime/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::{
2929

3030
use self::rust_script_language::RustScriptLanguage;
3131

32+
pub(crate) use rust_script::RustScript;
3233
pub use rust_script_instance::{Context, GenericContext};
3334

3435
#[macro_export]

rust-script/src/runtime/rust_script.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const NOTIFICATION_EXTENSION_RELOADED: i32 = 2;
3333

3434
#[derive(GodotClass)]
3535
#[class(base = ScriptExtension, tool)]
36-
pub(super) struct RustScript {
36+
pub(crate) struct RustScript {
3737
#[var(get = get_class_name, set = set_class_name, usage_flags = [STORAGE])]
3838
class_name: GString,
3939

0 commit comments

Comments
 (0)