aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/group_impl.rs
blob: a7bc84f8f6df16f413c4c2e8ab5536f474650e66 (plain) (blame)
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
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, Result as SynResult, TypePath};

/// Input for the `group!` macro
///
/// # Syntax
///
/// ```rust,ignore
/// /// Only a type path — uses default `crate::ThisProgram` as program
/// group!(std::io::Error);
/// group!(ParseIntError);
/// ```
struct GroupInput {
    type_path: TypePath,
}

impl Parse for GroupInput {
    fn parse(input: ParseStream) -> SynResult<Self> {
        let type_path: TypePath = input.parse()?;
        Ok(GroupInput { type_path })
    }
}

/// Convert a type path into a valid module name segment
///
/// e.g. `std::io::Error` -> `internal_group_std_io_error`
fn module_name_from_type(type_path: &TypePath) -> Ident {
    let segments: Vec<String> = type_path
        .path
        .segments
        .iter()
        .map(|seg| seg.ident.to_string().to_lowercase())
        .collect();
    Ident::new(
        &format!("internal_group_{}", segments.join("_")),
        proc_macro2::Span::call_site(),
    )
}

/// Get the last segment name of a type path (the simple type name)
///
/// e.g. `std::io::Error` -> `Error`
fn type_simple_name(type_path: &TypePath) -> Ident {
    type_path
        .path
        .segments
        .last()
        .expect("TypePath must have at least one segment")
        .ident
        .clone()
}

/// Generate the `use` token for the type path inside the generated module.
///
/// - Multi-segment path (e.g. `std::num::ParseIntError`): `use std::num::ParseIntError;`
/// - Single-segment path (e.g. `ParseIntError`): `use super::ParseIntError;`
fn gen_type_use(type_path: &TypePath) -> proc_macro2::TokenStream {
    if type_path.path.segments.len() > 1 {
        // Full path: use it directly
        quote! { use #type_path; }
    } else {
        // Single ident: import from parent scope
        let ident = type_simple_name(type_path);
        quote! { use super::#ident; }
    }
}

pub fn group_macro(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as GroupInput);
    let type_path = input.type_path;

    let program_path = crate::default_program_path();

    // Use the type's simple name as the enum variant identifier
    let type_name = type_simple_name(&type_path);
    // Create a unique module name from the full type path
    let module_name = module_name_from_type(&type_path);
    // Generate the appropriate `use` statement for the type
    let type_use = gen_type_use(&type_path);

    // Generate the module with the Groupped implementation
    let expanded = quote! {
        #[allow(non_camel_case_types)]
        mod #module_name {
            use #program_path as __MinglingProgram;
            #type_use

            impl ::mingling::Groupped<__MinglingProgram> for #type_name {
                fn member_id() -> __MinglingProgram {
                    __MinglingProgram::#type_name
                }
            }

            ::mingling::macros::register_type!(#type_name);
        }
    };

    expanded.into()
}