|
6 | 6 |
|
7 | 7 | use proc_macro2::TokenStream;
|
8 | 8 | 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 | +}; |
10 | 13 |
|
11 | 14 | use crate::{
|
12 |
| - is_context_type, rust_to_variant_type, |
| 15 | + compile_error, is_context_type, rust_to_variant_type, |
13 | 16 | type_paths::{godot_types, property_hints, string_name_ty, variant_ty},
|
14 | 17 | };
|
15 | 18 |
|
@@ -173,12 +176,166 @@ pub fn godot_script_impl(
|
173 | 176 | );
|
174 | 177 | };
|
175 | 178 |
|
| 179 | + let pub_interface = generate_public_interface(&body); |
| 180 | + |
176 | 181 | quote! {
|
177 | 182 | #body
|
178 | 183 |
|
179 | 184 | #trait_impl
|
180 | 185 |
|
| 186 | + #pub_interface |
| 187 | + |
181 | 188 | #metadata
|
182 | 189 | }
|
183 | 190 | .into()
|
184 | 191 | }
|
| 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)); |
| 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 | +} |
0 commit comments