aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/group_impl.rs
blob: 1b765f3838c7bb62d3390e71fd26a7d991943341 (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
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);
/// ```
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()
}

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 module with the Groupped implementation
    let expanded = quote! {
        #[allow(non_camel_case_types)]
        mod #module_name {
            use #program_path as __MinglingProgram;
            use #type_path;

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

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

    expanded.into()
}