|
7 | 7 |
|
8 | 8 | // Note: some code duplication with godot-codegen crate.
|
9 | 9 |
|
| 10 | +use crate::class::FuncDefinition; |
10 | 11 | use crate::ParseResult;
|
11 |
| -use proc_macro2::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree}; |
| 12 | +use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, TokenStream, TokenTree}; |
12 | 13 | use quote::spanned::Spanned;
|
13 | 14 | use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
14 | 15 |
|
@@ -243,8 +244,21 @@ pub(crate) fn extract_cfg_attrs(
|
243 | 244 | attrs: &[venial::Attribute],
|
244 | 245 | ) -> impl IntoIterator<Item = &venial::Attribute> {
|
245 | 246 | attrs.iter().filter(|attr| {
|
246 |
| - attr.get_single_path_segment() |
247 |
| - .is_some_and(|name| name == "cfg") |
| 247 | + let Some(attr_name) = attr.get_single_path_segment() else { |
| 248 | + return false; |
| 249 | + }; |
| 250 | + |
| 251 | + // #[cfg(condition)] |
| 252 | + if attr_name == "cfg" { |
| 253 | + return true; |
| 254 | + } |
| 255 | + |
| 256 | + // #[cfg_attr(condition, attributes...)]. Multiple attributes can be seperated by comma. |
| 257 | + if attr_name == "cfg_attr" && attr.value.to_token_stream().to_string().contains("cfg(") { |
| 258 | + return true; |
| 259 | + } |
| 260 | + |
| 261 | + false |
248 | 262 | })
|
249 | 263 | }
|
250 | 264 |
|
@@ -303,3 +317,97 @@ pub fn venial_parse_meta(
|
303 | 317 |
|
304 | 318 | venial::parse_item(input)
|
305 | 319 | }
|
| 320 | + |
| 321 | +// ---------------------------------------------------------------------------------------------------------------------------------------------- |
| 322 | + |
| 323 | +// util functions for handling #[func]s and #[var(get=f, set=f)] |
| 324 | + |
| 325 | +pub fn make_funcs_collection_constants( |
| 326 | + funcs: &[FuncDefinition], |
| 327 | + class_name: &Ident, |
| 328 | +) -> Vec<TokenStream> { |
| 329 | + funcs |
| 330 | + .iter() |
| 331 | + .map(|func| { |
| 332 | + // The constant needs the same #[cfg] attribute(s) as the function, so that it is only active if the function is also active. |
| 333 | + let cfg_attributes = extract_cfg_attrs(&func.external_attributes) |
| 334 | + .into_iter() |
| 335 | + .collect::<Vec<_>>(); |
| 336 | + |
| 337 | + make_funcs_collection_constant( |
| 338 | + class_name, |
| 339 | + &func.signature_info.method_name, |
| 340 | + func.registered_name.as_ref(), |
| 341 | + &cfg_attributes, |
| 342 | + ) |
| 343 | + }) |
| 344 | + .collect() |
| 345 | +} |
| 346 | + |
| 347 | +/// Returns a `const` declaration for the funcs collection struct. |
| 348 | +/// |
| 349 | +/// User-defined functions can be renamed with `#[func(rename=new_name)]`. To be able to access the renamed function name from another macro, |
| 350 | +/// a constant is used as indirection. |
| 351 | +pub fn make_funcs_collection_constant( |
| 352 | + class_name: &Ident, |
| 353 | + func_name: &Ident, |
| 354 | + registered_name: Option<&String>, |
| 355 | + attributes: &[&venial::Attribute], |
| 356 | +) -> TokenStream { |
| 357 | + let const_name = format_funcs_collection_constant(class_name, func_name); |
| 358 | + let const_value = match ®istered_name { |
| 359 | + Some(renamed) => renamed.to_string(), |
| 360 | + None => func_name.to_string(), |
| 361 | + }; |
| 362 | + |
| 363 | + let doc_comment = |
| 364 | + format!("The Rust function `{func_name}` is registered with Godot as `{const_value}`."); |
| 365 | + |
| 366 | + quote! { |
| 367 | + #(#attributes)* |
| 368 | + #[doc = #doc_comment] |
| 369 | + #[doc(hidden)] |
| 370 | + #[allow(non_upper_case_globals)] |
| 371 | + pub const #const_name: &str = #const_value; |
| 372 | + } |
| 373 | +} |
| 374 | + |
| 375 | +/// Converts `path::class` to `path::new_class`. |
| 376 | +pub fn replace_class_in_path(path: venial::Path, new_class: Ident) -> venial::Path { |
| 377 | + match path.segments.as_slice() { |
| 378 | + // Can't happen, you have at least one segment (the class name). |
| 379 | + [] => unreachable!("empty path"), |
| 380 | + |
| 381 | + [_single] => venial::Path { |
| 382 | + segments: vec![venial::PathSegment { |
| 383 | + ident: new_class, |
| 384 | + generic_args: None, |
| 385 | + tk_separator_colons: None, |
| 386 | + }], |
| 387 | + }, |
| 388 | + |
| 389 | + [path @ .., _last] => { |
| 390 | + let mut segments = vec![]; |
| 391 | + segments.extend(path.iter().cloned()); |
| 392 | + segments.push(venial::PathSegment { |
| 393 | + ident: new_class, |
| 394 | + generic_args: None, |
| 395 | + tk_separator_colons: Some([ |
| 396 | + Punct::new(':', Spacing::Joint), |
| 397 | + Punct::new(':', Spacing::Alone), |
| 398 | + ]), |
| 399 | + }); |
| 400 | + venial::Path { segments } |
| 401 | + } |
| 402 | + } |
| 403 | +} |
| 404 | + |
| 405 | +/// Returns the name of the constant inside the func "collection" struct. |
| 406 | +pub fn format_funcs_collection_constant(_class_name: &Ident, func_name: &Ident) -> Ident { |
| 407 | + format_ident!("{func_name}") |
| 408 | +} |
| 409 | + |
| 410 | +/// Returns the name of the struct used as collection for all function name constants. |
| 411 | +pub fn format_funcs_collection_struct(class_name: &Ident) -> Ident { |
| 412 | + format_ident!("__gdext_{class_name}_Funcs") |
| 413 | +} |
0 commit comments