|
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};
|
| 15 | +use venial::{Attribute, Path, PathSegment}; |
14 | 16 |
|
15 | 17 | mod kv_parser;
|
16 | 18 | mod list_parser;
|
@@ -243,8 +245,18 @@ pub(crate) fn extract_cfg_attrs(
|
243 | 245 | attrs: &[venial::Attribute],
|
244 | 246 | ) -> impl IntoIterator<Item = &venial::Attribute> {
|
245 | 247 | attrs.iter().filter(|attr| {
|
246 |
| - attr.get_single_path_segment() |
247 |
| - .is_some_and(|name| name == "cfg") |
| 248 | + let Some(attr_name) = attr.get_single_path_segment() else { |
| 249 | + return false; |
| 250 | + }; |
| 251 | + // #[cfg(condition)] |
| 252 | + if attr_name == "cfg" { |
| 253 | + return true; |
| 254 | + } |
| 255 | + // #[cfg_attr(condition, attributes...)], note that there can be multiple attributes seperated by comma. |
| 256 | + if attr_name == "cfg_attr" && attr.value.to_token_stream().to_string().contains("cfg(") { |
| 257 | + return true; |
| 258 | + } |
| 259 | + false |
248 | 260 | })
|
249 | 261 | }
|
250 | 262 |
|
@@ -303,3 +315,92 @@ pub fn venial_parse_meta(
|
303 | 315 |
|
304 | 316 | venial::parse_item(input)
|
305 | 317 | }
|
| 318 | + |
| 319 | +// ---------------------------------------------------------------------------------------------------------------------------------------------- |
| 320 | + |
| 321 | +// util functions for handling #[func]s and #[var(get=f, set=f)] |
| 322 | + |
| 323 | +pub fn make_func_name_constants(funcs: &[FuncDefinition], class_name: &Ident) -> Vec<TokenStream> { |
| 324 | + funcs |
| 325 | + .iter() |
| 326 | + .map(|func| { |
| 327 | + // The constant needs the same #[cfg] attribute(s) as the function, so that it is only active if the function is also active. |
| 328 | + let cfg_attributes = extract_cfg_attrs(&func.external_attributes) |
| 329 | + .into_iter() |
| 330 | + .collect::<Vec<_>>(); |
| 331 | + |
| 332 | + make_func_name_constant( |
| 333 | + class_name, |
| 334 | + &func.signature_info.method_name, |
| 335 | + func.registered_name.as_ref(), |
| 336 | + &cfg_attributes, |
| 337 | + ) |
| 338 | + }) |
| 339 | + .collect() |
| 340 | +} |
| 341 | + |
| 342 | +/// Funcs can be renamed with `#[func(rename=new_name) fn f();`. |
| 343 | +/// To be able to access the renamed function name at a later point, it is saved in a string constant. |
| 344 | +pub fn make_func_name_constant( |
| 345 | + class_name: &Ident, |
| 346 | + func_name: &Ident, |
| 347 | + registered_name: Option<&String>, |
| 348 | + attributes: &[&Attribute], |
| 349 | +) -> TokenStream { |
| 350 | + let const_name = format_func_name_constant_name(class_name, func_name); |
| 351 | + let const_value = match ®istered_name { |
| 352 | + Some(renamed) => renamed.to_string(), |
| 353 | + None => func_name.to_string(), |
| 354 | + }; |
| 355 | + |
| 356 | + let doc_comment = |
| 357 | + format!("The Rust function `{func_name}` is registered with Godot as `{const_value}`."); |
| 358 | + |
| 359 | + quote! { |
| 360 | + #(#attributes)* |
| 361 | + #[doc = #doc_comment] |
| 362 | + #[doc(hidden)] |
| 363 | + #[allow(non_upper_case_globals)] |
| 364 | + pub const #const_name: &str = #const_value; |
| 365 | + } |
| 366 | +} |
| 367 | + |
| 368 | +/// Converts "path::class" to "path::new_class". |
| 369 | +pub fn replace_class_in_path(path: Path, new_class: Ident) -> Path { |
| 370 | + match path.segments.as_slice() { |
| 371 | + // Can't happen, you have at least one segment (the class name). |
| 372 | + [] => panic!("unexpected: empty path"), |
| 373 | + |
| 374 | + [_single] => Path { |
| 375 | + segments: vec![PathSegment { |
| 376 | + ident: new_class, |
| 377 | + generic_args: None, |
| 378 | + tk_separator_colons: None, |
| 379 | + }], |
| 380 | + }, |
| 381 | + |
| 382 | + [path @ .., _last] => { |
| 383 | + let mut segments = vec![]; |
| 384 | + segments.extend(path.iter().cloned()); |
| 385 | + segments.push(PathSegment { |
| 386 | + ident: new_class, |
| 387 | + generic_args: None, |
| 388 | + tk_separator_colons: Some([ |
| 389 | + Punct::new(':', Spacing::Joint), |
| 390 | + Punct::new(':', Spacing::Alone), |
| 391 | + ]), |
| 392 | + }); |
| 393 | + Path { segments } |
| 394 | + } |
| 395 | + } |
| 396 | +} |
| 397 | + |
| 398 | +/// Returns the name of the constant that will be autogenerated. |
| 399 | +pub fn format_func_name_constant_name(_class_name: &Ident, func_name: &Ident) -> Ident { |
| 400 | + format_ident!("{func_name}") |
| 401 | +} |
| 402 | + |
| 403 | +/// Returns the name of the dummy struct that's used as container for all function name constants. |
| 404 | +pub fn format_func_name_struct_name(class_name: &Ident) -> Ident { |
| 405 | + format_ident!("__gdext_{class_name}_Functions") |
| 406 | +} |
0 commit comments