aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/func/group_structural.rs
blob: 2bd2f83a0237f0c1fcaf1f8d38e1329ab5008687 (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
use proc_macro::TokenStream;
use quote::quote;
use syn::{Ident, TypePath};

use crate::get_global_set;

/// `group_structural!` — like `group!` but also marks the type as supporting
/// structured output via `StructuralData`.
pub(crate) fn group_structural(input: TokenStream) -> TokenStream {
    // Parse the same input as group!
    let input_parsed = syn::parse_macro_input!(input as GroupStructuralInput);

    let is_aliased = matches!(&input_parsed, GroupStructuralInput::Aliased { .. });

    let (type_path, type_name, alias_stmt) = match &input_parsed {
        GroupStructuralInput::Plain(type_path) => {
            let name = type_path
                .path
                .segments
                .last()
                .expect("TypePath must have at least one segment")
                .ident
                .clone();
            (type_path.clone(), name, quote! {})
        }
        GroupStructuralInput::Aliased { alias, type_path } => {
            let alias_stmt = quote! {
                pub(crate) type #alias = #type_path;
            };
            (type_path.clone(), alias.clone(), alias_stmt)
        }
    };

    let type_name_str = type_name.to_string();

    // Register in STRUCTURED_TYPES
    get_global_set(&crate::STRUCTURED_TYPES)
        .lock()
        .unwrap()
        .insert(type_name_str);

    let program_path = crate::default_program_path();

    // Generate unique module name
    let segments: Vec<String> = type_path
        .path
        .segments
        .iter()
        .map(|seg| seg.ident.to_string().to_lowercase())
        .collect();
    let module_name = Ident::new(
        &format!("internal_group_{}", segments.join("_")),
        proc_macro2::Span::call_site(),
    );

    // Generate the appropriate `use` statement for the original type
    let type_use = if type_path.path.segments.len() > 1 {
        quote! { #[allow(unused_imports)] use #type_path; }
    } else {
        let ident = type_path
            .path
            .segments
            .last()
            .expect("TypePath must have at least one segment")
            .ident
            .clone();
        quote! { #[allow(unused_imports)] use super::#ident; }
    };

    let alias_use = if is_aliased {
        quote! { use super::#type_name; }
    } else {
        quote! {}
    };

    let expanded = quote! {
        #alias_stmt
        #[allow(non_camel_case_types)]
        mod #module_name {
            use #program_path as __MinglingProgram;
            #type_use
            #alias_use

            /// SAFETY: This is an internal implementation of the `pack_structural!` macro,
            /// guaranteeing that the enum value registered by the `register_type!` macro
            /// is exactly the same as the actual return value,
            /// which can be confirmed via the `Ident` in the `quote!` block.
            unsafe impl ::mingling::Grouped<__MinglingProgram> for #type_name {
                fn member_id() -> __MinglingProgram {
                    __MinglingProgram::#type_name
                }
            }

            impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
            impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}

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

    expanded.into()
}

/// Input for `group_structural!` — same format as `group!`.
enum GroupStructuralInput {
    Plain(TypePath),
    Aliased { alias: Ident, type_path: TypePath },
}

impl syn::parse::Parse for GroupStructuralInput {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let fork = input.fork();
        let _first: Ident = fork.parse()?;
        if fork.peek(syn::Token![=]) {
            let alias: Ident = input.parse()?;
            let _eq: syn::Token![=] = input.parse()?;
            let type_path: TypePath = input.parse()?;
            Ok(GroupStructuralInput::Aliased { alias, type_path })
        } else {
            let type_path: TypePath = input.parse()?;
            Ok(GroupStructuralInput::Plain(type_path))
        }
    }
}