godot-macros 0.5.2

Internal crate used by godot-rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
/*
 * Copyright (c) godot-rust; Bromeon and contributors.
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
use quote::spanned::Spanned;
use quote::{ToTokens, format_ident, quote};

use crate::class::data_models::func;
use crate::class::{
    ConstDefinition, FuncDefinition, RpcAttr, RpcMode, SignalDefinition, SignatureInfo,
    TransferMode, into_signature_info, make_constant_registration, make_method_registration,
    make_signal_registrations,
};
use crate::util::{
    KvParser, bail, c_str, format_funcs_collection_struct, ident, make_funcs_collection_constants,
    replace_class_in_path, require_api_version,
};
use crate::{ParseResult, handle_mutually_exclusive_keys, util};

/// Attribute for user-declared function.
enum ItemAttrType {
    Func(FuncAttr, Option<RpcAttr>),
    Signal(SignalAttr, venial::AttributeValue),
    Const(#[allow(dead_code)] venial::AttributeValue),
}

struct ItemAttr {
    attr_name: Ident,
    ty: ItemAttrType,
}

enum AttrParseResult {
    Func(FuncAttr),
    Rpc(RpcAttr),
    FuncRpc(FuncAttr, RpcAttr),
    Signal(SignalAttr, venial::AttributeValue),
    Constant(#[allow(dead_code)] venial::AttributeValue),
}

impl AttrParseResult {
    fn into_attr_ty(self) -> ItemAttrType {
        match self {
            AttrParseResult::Func(func) => ItemAttrType::Func(func, None),
            // If only `#[rpc]` is present, we assume #[func] with default values.
            AttrParseResult::Rpc(rpc) => ItemAttrType::Func(FuncAttr::default(), Some(rpc)),
            AttrParseResult::FuncRpc(func, rpc) => ItemAttrType::Func(func, Some(rpc)),
            AttrParseResult::Signal(signal, attr_val) => ItemAttrType::Signal(signal, attr_val),
            AttrParseResult::Constant(constant) => ItemAttrType::Const(constant),
        }
    }
}

#[derive(Default)]
struct FuncAttr {
    pub rename: Option<String>,
    pub is_virtual: bool,
    pub has_gd_self: bool,
}

#[derive(Default)]
struct SignalAttr {
    pub no_builder: bool,
    pub internal: bool,
}

pub(crate) struct InherentImplAttr {
    /// For implementation reasons, there can be a single 'primary' impl block and 0 or more 'secondary' impl blocks.
    /// For now, this is controlled by a key in the 'godot_api' attribute.
    pub secondary: bool,

    /// When typed signal generation is explicitly disabled by the user.
    pub no_typed_signals: bool,
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

/// Codegen for `#[godot_api] impl MyType`
pub fn transform_inherent_impl(
    meta: InherentImplAttr,
    mut impl_block: venial::Impl,
    self_path: venial::Path,
) -> ParseResult<TokenStream> {
    let class_name = util::validate_impl(&impl_block, None, "godot_api")?;
    let class_name_obj = util::class_name_obj(&class_name);
    let prv = quote! { ::godot::private };

    // Can add extra functions to the end of the impl block.
    let (funcs, signals) = process_godot_fns(&class_name, &mut impl_block, meta.secondary)?;
    let consts = process_godot_constants(&mut impl_block)?;

    let inherent_impl_docs =
        crate::docs::make_trait_docs_registration(&funcs, &consts, &signals, &class_name, &prv);

    // Generate constants for the funcs collection struct (only primary impl).
    //
    // Today, secondary impl blocks don't add to the funcs collection (access from #[var(get = ...)] etc. not supported).
    // Could potentially be added. What was tried:
    //     trait WithFuncs { type FuncCollection; }
    // instead of the collection being a struct with a "conventional" name. However, it's not possible to
    //     impl <MyClass as WithFuncs>::FuncCollection { ... }
    // because Rust requires a nominal type for `impl` blocks. Even type aliases to associated types don't qualify. Compile error:
    //     error[E0118]: no nominal type found for inherent implementation
    //
    // One alternative would be to add func name constants directly on the class itself as `impl Class { const __func_... }`, however
    // this would slightly pollute the user-facing namespace. Another option is to pass the MyStruct path explicitly as an attribute argument,
    // e.g. `#[godot_api(secondary_from = crate::my_mod)]`).
    let funcs_collection_impl = if meta.secondary {
        TokenStream::new()
    } else {
        let func_consts = make_funcs_collection_constants(&funcs, &class_name);
        if func_consts.is_empty() {
            TokenStream::new()
        } else {
            let struct_name = format_funcs_collection_struct(&class_name);
            let funcs_path = replace_class_in_path(self_path, struct_name);

            quote! {
                impl #funcs_path {
                    #(#func_consts)*
                }
            }
        }
    };

    let (signal_registrations, signal_symbol_types) = make_signal_registrations(
        &signals,
        &class_name,
        &class_name_obj,
        meta.no_typed_signals,
    )?;

    #[cfg(feature = "codegen-full")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-full")))]
    let rpc_registrations = crate::class::make_rpc_registrations_fn(&class_name, &funcs);
    #[cfg(not(feature = "codegen-full"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-full"))))]
    let rpc_registrations = TokenStream::new();

    let method_registrations: Vec<TokenStream> = funcs
        .into_iter()
        .map(|func_def| make_method_registration(&class_name, func_def, None))
        .collect::<ParseResult<Vec<TokenStream>>>()?;

    let constant_registration = make_constant_registration(consts, &class_name, &class_name_obj)?;

    let fill_storage = {
        quote! {
            ::godot::sys::plugin_execute_pre_main!({
                let mut guard = #class_name::__registration_storage().lock().unwrap();

                guard.0.push(|| {
                    #( #method_registrations )*
                    #( #signal_registrations )*
                });

                guard.1.push(|| {
                    #constant_registration
                });
            });
        }
    };

    if !meta.secondary {
        // We are the primary `impl` block.

        // Storage for registration functions from all `#[godot_api]` impl blocks (primary + secondary).
        // Accessed through the class type so secondary blocks in other modules can find it.
        // Tuple: (method+signal registrations, constant registrations).
        let storage = quote! {
            impl #class_name {
                #[doc(hidden)]
                pub fn __registration_storage() -> &'static std::sync::Mutex<(Vec<fn()>, Vec<fn()>)> {
                    static STORAGE: std::sync::Mutex<(Vec<fn()>, Vec<fn()>)>
                        = std::sync::Mutex::new((Vec::new(), Vec::new()));
                    &STORAGE
                }
            }
        };

        let trait_impl = quote! {
            impl ::godot::obj::cap::ImplementsGodotApi for #class_name {
                fn __register_methods() {
                    let guard = #class_name::__registration_storage().lock().unwrap();
                    for f in guard.0.iter() {
                        f();
                    }
                }

                fn __register_constants() {
                    let guard = #class_name::__registration_storage().lock().unwrap();
                    for f in guard.1.iter() {
                        f();
                    }
                }

                #rpc_registrations
            }
        };

        let class_registration = quote! {
            ::godot::sys::plugin_add!(#prv::__GODOT_PLUGIN_REGISTRY; #prv::ClassPlugin::new::<#class_name>(
                #prv::PluginItem::InherentImpl(#prv::InherentImpl::new::<#class_name>())
            ));
        };

        let result = quote! {
            #impl_block
            #funcs_collection_impl
            #storage
            #trait_impl
            #fill_storage
            #class_registration
            #signal_symbol_types
            #inherent_impl_docs
        };

        Ok(result)
    } else {
        // We are in a secondary `impl` block, so most of the work has already been done,
        // and we just need to add our registration functions in the storage defined by the primary `impl` block.

        let result = quote! {
            #impl_block
            #funcs_collection_impl
            #fill_storage
            #inherent_impl_docs
        };

        Ok(result)
    }
}

/* Re-enable if we allow controlling declarative macros for signals (base_field_macro, visibility_macros).
fn extract_hint_attribute(impl_block: &mut venial:: Impl) -> ParseResult<GodotApiHints> {
    // #[hint(has_base_field = BOOL)]
    let has_base_field;
    if let Some(mut hints) = KvParser::parse_remove(&mut impl_block.attributes, "hint")? {
        has_base_field = hints.handle_bool("has_base_field")?;
    } else {
        has_base_field = None;
    }

    // #[hint(class_visibility = pub(crate))]
    // ...

    Ok(GodotApiHints { has_base_field })
}
*/

fn process_godot_fns(
    class_name: &Ident,
    impl_block: &mut venial::Impl,
    is_secondary_impl: bool,
) -> ParseResult<(Vec<FuncDefinition>, Vec<SignalDefinition>)> {
    let mut func_definitions = vec![];
    let mut signal_definitions = vec![];
    let mut virtual_functions = vec![];

    let mut removed_indexes = vec![];
    for (index, item) in impl_block.body_items.iter_mut().enumerate() {
        let venial::ImplMember::AssocFunction(function) = item else {
            continue;
        };

        let Some(attr) = parse_attributes(function)? else {
            continue;
        };

        if function.qualifiers.tk_default.is_some()
            || function.qualifiers.tk_const.is_some()
            || function.qualifiers.tk_async.is_some()
            || function.qualifiers.tk_unsafe.is_some()
            || function.qualifiers.tk_extern.is_some()
            || function.qualifiers.extern_abi.is_some()
        {
            return bail!(
                &function.qualifiers,
                "#[func]: fn qualifiers are not allowed"
            );
        }

        if function.generic_params.is_some() {
            return bail!(
                &function.generic_params,
                "#[func]: generic fn parameters are not supported"
            );
        }

        validate_no_references(function)?;

        match attr.ty {
            ItemAttrType::Func(func, rpc_info) => {
                if rpc_info.is_some() && is_secondary_impl {
                    return bail!(
                        &function,
                        "#[rpc] is currently not supported in secondary impl blocks",
                    )?;
                }

                let external_attributes = function.attributes.clone();

                // Transforms the following.
                //   from function:     #[attr] pub fn foo(&self, a: i32) -> i32 { ... }
                //   into signature:    fn foo(&self, a: i32) -> i32
                let mut signature = util::reduce_to_signature(function);
                let gd_self_parameter = func::validate_receiver_extract_gdself(
                    &mut signature,
                    func.has_gd_self,
                    &attr.attr_name,
                )?;

                // Clone might not strictly be necessary, but the 2 other callers of into_signature_info() are better off with pass-by-value.
                let mut signature_info =
                    into_signature_info(signature.clone(), class_name, gd_self_parameter.is_some());

                // Default value expressions from `#[opt(default = EXPR)]`; None for required parameters.
                let all_param_maybe_defaults = parse_default_expressions(&mut function.params)?;
                signature_info.optional_param_default_exprs =
                    validate_default_exprs(all_param_maybe_defaults, &signature_info.param_idents)?;

                // For virtual methods, rename/mangle existing user method and create a new method with the original name,
                // which performs a dynamic dispatch.
                let registered_name = if func.is_virtual {
                    let registered_name = add_virtual_script_call(
                        &mut virtual_functions,
                        function,
                        &signature_info,
                        class_name,
                        &func.rename,
                        gd_self_parameter,
                    );

                    Some(registered_name)
                } else {
                    func.rename
                };

                func_definitions.push(FuncDefinition {
                    signature_info,
                    external_attributes,
                    registered_name,
                    is_script_virtual: func.is_virtual,
                    rpc_info,
                    is_generated_accessor: false,
                });
            }

            ItemAttrType::Signal(ref signal, ref _attr_val) => {
                if is_secondary_impl {
                    return bail!(
                        function,
                        "#[signal] is currently not supported in secondary impl blocks",
                    );
                }
                if function.return_ty.is_some() {
                    return bail!(
                        &function.return_ty,
                        "#[signal] does not support return types"
                    );
                }
                if function.body.is_some() {
                    return bail!(
                        &function.body,
                        "#[signal] must not have a body; declare the function with a semicolon"
                    );
                }

                let external_attributes = function.attributes.clone();

                let mut fn_signature = util::reduce_to_signature(function);
                fn_signature.vis_marker = function.vis_marker.clone();

                signal_definitions.push(SignalDefinition {
                    fn_signature,
                    external_attributes,
                    has_builder: !signal.no_builder,
                    is_internal: signal.internal,
                });

                removed_indexes.push(index);
            }

            ItemAttrType::Const(_) => {
                return bail!(
                    function,
                    "#[constant] can only be used on associated constant",
                );
            }
        }
    }

    // Remove some elements (e.g. signals) from impl.
    // O(n^2); alternative: retain(), but elements themselves don't have the necessary information.
    for index in removed_indexes.into_iter().rev() {
        impl_block.body_items.remove(index);
    }

    // Add script-virtual extra functions at the end of same impl block (subject to same attributes).
    for f in virtual_functions.into_iter() {
        let member = venial::ImplMember::AssocFunction(f);
        impl_block.body_items.push(member);
    }

    Ok((func_definitions, signal_definitions))
}

fn process_godot_constants(decl: &mut venial::Impl) -> ParseResult<Vec<ConstDefinition>> {
    let mut constant_signatures = vec![];

    for item in decl.body_items.iter_mut() {
        let venial::ImplMember::AssocConstant(constant) = item else {
            continue;
        };

        if let Some(attr) = parse_attributes(constant)? {
            match attr.ty {
                ItemAttrType::Func(_, _) => {
                    return bail!(constant, "#[func] and #[rpc] can only be used on functions");
                }
                ItemAttrType::Signal(_, _) => {
                    return bail!(constant, "#[signal] can only be used on functions");
                }
                ItemAttrType::Const(_) => {
                    if constant.initializer.is_none() {
                        return bail!(constant, "exported constant must have initializer");
                    }

                    let definition = ConstDefinition {
                        raw_constant: constant.clone(),
                    };

                    constant_signatures.push(definition);
                }
            }
        }
    }

    Ok(constant_signatures)
}

/// Replaces the body of `function` with custom code that performs virtual dispatch.
///
/// Appends the virtual function to `virtual_functions`.
///
/// Returns the Godot-registered name of the virtual function, usually `_<name>` (but overridable with `#[func(rename = ...)]`).
fn add_virtual_script_call(
    virtual_functions: &mut Vec<venial::Function>,
    function: &mut venial::Function,
    signature_info: &SignatureInfo,
    class_name: &Ident,
    rename: &Option<String>,
    gd_self_parameter: Option<Ident>,
) -> String {
    #[allow(clippy::assertions_on_constants)]
    {
        // Without braces, clippy removes the #[allow] for some reason...
        assert!(cfg!(since_api = "4.3"));
    }

    // Update parameter names, so they can be forwarded (e.g. a "_" declared by the user cannot).
    let is_params = function.params.iter_mut().skip(1); // skip receiver.
    let should_param_names = signature_info.param_idents.iter();
    is_params
        .zip(should_param_names)
        .for_each(|(param, should_param_name)| {
            if let venial::FnParam::Typed(param) = &mut param.0 {
                param.name = should_param_name.clone();
            }
        });

    let class_name_str = class_name.to_string();
    let early_bound_name = format_ident!(
        "__earlybound_{}",
        function.name,
        span = function.name.span()
    );

    let method_name_str = match rename {
        Some(rename) => rename.clone(),
        None => format!("_{}", function.name),
    };
    let method_name_cstr = c_str(&method_name_str);

    let call_params = signature_info.params_type();
    let call_ret = &signature_info.return_type;
    let arg_names = &signature_info.param_idents;

    let (object_ptr, receiver);
    if let Some(gd_self_parameter) = gd_self_parameter {
        object_ptr = quote! { #gd_self_parameter.obj_sys() };
        receiver = gd_self_parameter;
    } else {
        object_ptr = quote! { <Self as ::godot::obj::WithBaseField>::base_field(self).obj_sys() };
        receiver = ident("self");
    };

    let code = quote! {
        let object_ptr = #object_ptr;
        let method_sname = ::godot::builtin::StringName::__cstr(#method_name_cstr);
        let method_sname_ptr = method_sname.string_sys();
        let has_virtual_override = unsafe { ::godot::private::has_virtual_script_method(object_ptr, method_sname_ptr) };

        if has_virtual_override {
            // Dynamic dispatch.
            type CallParams = #call_params;
            type CallRet = #call_ret;
            let args = (#( #arg_names, )*);
            unsafe {
                ::godot::private::Signature::<CallParams, CallRet>::out_script_virtual_call(
                    #class_name_str,
                    #method_name_str,
                    method_sname_ptr,
                    object_ptr,
                    args,
                )
            }
        } else {
            // Fall back to default implementation.
            Self::#early_bound_name(#receiver, #( #arg_names ),*)
        }
    };

    let mut early_bound_function = venial::Function {
        name: early_bound_name,
        body: Some(Group::new(Delimiter::Brace, code)),
        ..function.clone()
    };

    std::mem::swap(&mut function.body, &mut early_bound_function.body);
    virtual_functions.push(early_bound_function);

    method_name_str
}

/// Validates that a function uses no reference types (`&T`, `&mut T`) in parameters or return type.
///
/// References cannot cross Godot FFI. Without this check, users get a confusing error, see <https://github.com/godot-rust/gdext/pull/1542>.
fn validate_no_references(function: &venial::Function) -> ParseResult<()> {
    for (param, _) in function.params.inner.iter() {
        if let venial::FnParam::Typed(arg) = param
            && let Some(TokenTree::Punct(p)) = arg.ty.tokens.first()
            && p.as_char() == '&'
        {
            return bail!(
                &arg.ty,
                "#[func] does not support reference parameters \
                (`&T` or `&mut T`); use a value type instead"
            );
        }
    }

    if let Some(ref ret_ty) = function.return_ty
        && let Some(TokenTree::Punct(p)) = ret_ty.tokens.first()
        && p.as_char() == '&'
    {
        return bail!(
            ret_ty,
            "#[func] does not support reference return types \
            (`&T` or `&mut T`); use a value type instead"
        );
    }

    Ok(())
}

/// Parses an entire item (`fn`, `const`) inside an `impl` block and returns a domain representation.
///
/// See also [`parse_attributes_inner`].
fn parse_attributes<T: ImplItem>(item: &mut T) -> ParseResult<Option<ItemAttr>> {
    let span = util::span_of(item);
    parse_attributes_inner(item.attributes_mut(), span)
}

/// Non-generic version of [`parse_attributes`].
///
/// `attributes` are all `#[...]` attributes of the item, including foreign (non-godot-rust) ones.
/// `full_item_span` is the span of the entire item (attributes + `fn`/...), for error messages.
fn parse_attributes_inner(
    attributes: &mut Vec<venial::Attribute>,
    full_item_span: Span,
) -> ParseResult<Option<ItemAttr>> {
    // Option<(attr_name: Ident, attr: ParsedAttr)>
    let mut found = None;
    let mut index = 0;

    while let Some(attr) = attributes.get(index) {
        index += 1;

        let Some(attr_name) = attr.get_single_path_segment() else {
            // Attribute of the form #[segmented::path] can't be what we are looking for.
            continue;
        };

        let parsed_attr = match attr_name {
            name if name == "func" => parse_func_attr(attributes)?,
            name if name == "rpc" => parse_rpc_attr(attributes)?,
            name if name == "signal" => parse_signal_attr(attributes, attr)?,
            name if name == "constant" => parse_constant_attr(attributes, attr)?,

            // Ignore unknown attributes.
            _ => continue,
        };

        let attr_name = attr_name.clone();

        // Remaining code no longer has attribute -- rest stays.
        attributes.remove(index - 1); // -1 because we bumped the index at the beginning of the loop.
        index -= 1;

        let (new_name, new_attr) = match (found, parsed_attr) {
            // First attribute.
            (None, parsed) => (attr_name, parsed),

            // Regardless of the order, if we found both `#[func]` and `#[rpc]`, we can just merge them.
            (Some((found_name, AttrParseResult::Func(func))), AttrParseResult::Rpc(rpc))
            | (Some((found_name, AttrParseResult::Rpc(rpc))), AttrParseResult::Func(func)) => (
                ident(&format!("{found_name}_{attr_name}")),
                AttrParseResult::FuncRpc(func, rpc),
            ),

            // We found two incompatible attributes.
            (Some((found_name, _)), _) => {
                return bail!(
                    full_item_span,
                    "attributes `{found_name}` and `{attr_name}` cannot be used in the same declaration"
                );
            }
        };

        found = Some((new_name, new_attr));
    }

    Ok(found.map(|(attr_name, attr)| ItemAttr {
        attr_name,
        ty: attr.into_attr_ty(),
    }))
}

/// `#[func]` attribute.
fn parse_func_attr(attributes: &[venial::Attribute]) -> ParseResult<AttrParseResult> {
    // Safe unwrap, since #[func] must be present if we got to this point.
    let mut parser = KvParser::parse(attributes, "func")?.unwrap();

    // #[func(rename = MyClass)]
    let rename = parser.handle_expr("rename")?.map(|ts| ts.to_string());

    // #[func(virtual)]
    let is_virtual = if let Some(span) = parser.handle_alone_with_span("virtual")? {
        require_api_version!("4.3", span, "#[func(virtual)]")?;
        true
    } else {
        false
    };

    // #[func(gd_self)]
    let has_gd_self = parser.handle_alone("gd_self")?;

    parser.finish()?;

    Ok(AttrParseResult::Func(FuncAttr {
        rename,
        is_virtual,
        has_gd_self,
    }))
}

/// `#[rpc]` attribute.
fn parse_rpc_attr(attributes: &[venial::Attribute]) -> ParseResult<AttrParseResult> {
    // Safe unwrap, since #[rpc] must be present if we got to this point.
    let mut parser = KvParser::parse(attributes, "rpc")?.unwrap();

    let rpc_mode =
        handle_mutually_exclusive_keys(&mut parser, "#[rpc]", &["any_peer", "authority"])?
            .map(|idx| RpcMode::from_usize(idx).unwrap());

    let transfer_mode = handle_mutually_exclusive_keys(
        &mut parser,
        "#[rpc]",
        &["reliable", "unreliable", "unreliable_ordered"],
    )?
    .map(|idx| TransferMode::from_usize(idx).unwrap());

    let call_local =
        handle_mutually_exclusive_keys(&mut parser, "#[rpc]", &["call_local", "call_remote"])?
            .map(|idx| idx == 0);

    let channel = parser.handle_usize("channel")?.map(|x| x as u32);

    let config_expr = parser.handle_expr("config")?;

    let item_span = parser.span();
    parser.finish()?;

    let rpc_attr = match (
        config_expr,
        (&rpc_mode, &transfer_mode, &call_local, &channel),
    ) {
        // Ok: Only `config = [expr]` is present.
        (Some(expr), (None, None, None, None)) => RpcAttr::Expression(expr),

        // Err: `config = [expr]` is present along other parameters, which is not allowed.
        (Some(_), _) => {
            return bail!(
                item_span,
                "`#[rpc(config = ...)]` is mutually exclusive with any other parameters(`any_peer`, `reliable`, `call_local`, `channel = 0`)"
            );
        }

        // Ok: `config` is not present, any combination of the other parameters is allowed.
        _ => RpcAttr::SeparatedArgs {
            rpc_mode,
            transfer_mode,
            call_local,
            channel,
        },
    };

    Ok(AttrParseResult::Rpc(rpc_attr))
}

/// `#[signal]` attribute.
fn parse_signal_attr(
    attributes: &[venial::Attribute],
    attr: &venial::Attribute,
) -> ParseResult<AttrParseResult> {
    // Safe unwrap, since #[signal] must be present if we got to this point.
    let mut parser = KvParser::parse(attributes, "signal")?.unwrap();

    // Private #[signal(__no_builder)]
    let no_builder = parser.handle_alone("__no_builder")?;
    let internal = parser.handle_alone("internal")?;

    parser.finish()?;

    let signal_attr = SignalAttr {
        no_builder,
        internal,
    };

    Ok(AttrParseResult::Signal(signal_attr, attr.value.clone()))
}

/// `#[constant]` attribute.
fn parse_constant_attr(
    attributes: &[venial::Attribute],
    attr: &venial::Attribute,
) -> ParseResult<AttrParseResult> {
    // Ensure no keys are present.
    let parser = KvParser::parse(attributes, "constant")?.unwrap();
    parser.finish()?;

    Ok(AttrParseResult::Constant(attr.value.clone()))
}

/// Parses `#[opt(default = ...)]` parameter attributes and validates that optional parameters only appear at the end.
///
/// Returns a vector of optional default values, one per parameter (skipping receiver).
fn parse_default_expressions(
    params: &mut venial::Punctuated<venial::FnParam>,
) -> ParseResult<Vec<Option<TokenStream>>> {
    let mut res = vec![];

    for param in params.iter_mut() {
        let typed_param = match &mut param.0 {
            venial::FnParam::Receiver(_) => continue,
            venial::FnParam::Typed(fn_typed_param) => fn_typed_param,
        };

        let optional_value = match KvParser::parse_remove(&mut typed_param.attributes, "opt")? {
            None => None,
            Some(mut parser) => Some(parser.handle_expr_required("default")?),
        };

        res.push(optional_value);
    }

    Ok(res)
}

/// Validates that default parameters only appear at the end of the parameter list.
/// Consumes the input and returns only the non-None default expressions.
fn validate_default_exprs(
    all_param_maybe_defaults: Vec<Option<TokenStream>>,
    param_idents: &[Ident],
) -> ParseResult<Vec<TokenStream>> {
    let mut must_be_default = false;
    let mut result = Vec::new();

    for (i, param) in all_param_maybe_defaults.into_iter().enumerate() {
        match (param, must_be_default) {
            // First optional parameter encountered.
            (Some(default_expr), false) => {
                must_be_default = true;
                result.push(default_expr);
            }

            // Subsequent optional parameters.
            (Some(default_expr), true) => {
                result.push(default_expr);
            }

            // Required parameter before any optional ones.
            (None, false) => {}

            // Required parameter after optional ones.
            (None, true) => {
                let name = &param_idents[i];
                return bail!(
                    name,
                    "parameter `{name}` must have a default value, because previous parameters are already optional",
                );
            }
        }
    }

    Ok(result)
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

trait ImplItem
where
    Self: ToTokens,
    for<'a> &'a Self: Spanned,
{
    fn attributes_mut(&mut self) -> &mut Vec<venial::Attribute>;
}

impl ImplItem for venial::Function {
    fn attributes_mut(&mut self) -> &mut Vec<venial::Attribute> {
        &mut self.attributes
    }
}

impl ImplItem for venial::Constant {
    fn attributes_mut(&mut self) -> &mut Vec<venial::Attribute> {
        &mut self.attributes
    }
}