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

/// Input for the `group!` macro
///
/// # Syntax
///
/// ```rust,ignore
/// // Explicit mode: specify both program path and type path
/// group!(crate::ThisProgram, std::io::Error);
///
/// // Implicit mode: only type path, uses default `crate::ThisProgram` as program
/// group!(std::io::Error);
/// group!(ParseIntError);
/// ```
enum GroupInput {
    Explicit {
        program_path: Path,
        type_path: TypePath,
    },
    Implicit {
        type_path: TypePath,
    },
}

impl Parse for GroupInput {
    fn parse(input: ParseStream) -> SynResult<Self> {
        // Parse the first path (could be program path or type path)
        let first_path: Path = input.parse()?;

        // If followed by a comma, it's explicit mode: Path, TypePath
        if input.peek(Token![,]) {
            input.parse::<Token![,]>()?;
            let type_path: TypePath = input.parse()?;
            Ok(GroupInput::Explicit {
                program_path: first_path,
                type_path,
            })
        } else {
            // Otherwise it's implicit mode: just a type path
            Ok(GroupInput::Implicit {
                type_path: TypePath {
                    qself: None,
                    path: first_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 (program_path, type_path) = match input {
        GroupInput::Explicit {
            program_path,
            type_path,
        } => (quote! { #program_path }, type_path),
        GroupInput::Implicit { type_path } => (crate::default_program_path(), type_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()
}