macros/
lib.rs

1/*
2 * Copyright (C) 2023-2024. James Draycott me@racci.dev
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 * See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see https://www.gnu.org/licenses/.
15 */
16
17#![feature(proc_macro_diagnostic)]
18#![feature(downcast_unchecked)]
19#![feature(cfg_eval)]
20#![feature(cfg_match)]
21#![feature(assert_matches)]
22
23mod builder;
24mod enums;
25
26use proc_macro::TokenStream;
27use proc_macro2::TokenStream as TokenStream2;
28use quote::quote;
29use std::any::{type_name, type_name_of_val};
30use syn::spanned::Spanned;
31use syn::{parse_macro_input, Data, DeriveInput, Ident, Path, TypePath};
32
33pub(crate) fn error(span: proc_macro2::Span, message: &str) -> TokenStream2 {
34    // span.unwrap().error(message).emit();
35    syn::Error::new(span, message).into_compile_error()
36}
37
38fn error_input<E, O>(span: proc_macro2::Span, other: O) -> TokenStream2 {
39    error(
40        span,
41        &format!(
42            "Derive macro can only be applied to {}, got {}",
43            type_name::<E>(),
44            type_name_of_val(&other),
45        ),
46    )
47}
48
49#[proc_macro_derive(EnumVariants)]
50pub fn enum_variants(input: TokenStream) -> TokenStream {
51    let input = parse_macro_input!(input as DeriveInput);
52
53    enums::variants::variants(input).into()
54}
55
56#[proc_macro_derive(EnumNames)]
57pub fn enum_names(input: TokenStream) -> TokenStream {
58    let input = parse_macro_input!(input as DeriveInput);
59
60    enums::names::names(input).into()
61}
62
63#[proc_macro_derive(EnumRegex)]
64pub fn enum_regex(input: TokenStream) -> TokenStream {
65    let input = parse_macro_input!(input as DeriveInput);
66
67    enums::regex::regex(input).into()
68}
69
70#[proc_macro_derive(Delegation, attributes(delegate))]
71pub fn delegate_trait(input: TokenStream) -> TokenStream {
72    // Parse the input tokens into a syntax tree
73    let input = parse_macro_input!(input as DeriveInput);
74
75    // Check if the input is an enum
76    let item_enum = match &input.data {
77        Data::Enum(item_enum) => item_enum,
78        _ => return error(input.span(), "Delegate can only be derived for enums").into(),
79    };
80
81    // Retrieve the name of the enum
82    let enum_name = &input.ident;
83
84    let delegate_attr = input
85        .attrs
86        .iter()
87        .find(|attr| attr.path().is_ident("delegate"))
88        .unwrap_or_else(|| panic!("Missing `delegate` attribute on enum root: {enum_name}"));
89
90    let mut delegate_type = None;
91    delegate_attr
92        .parse_nested_meta(|meta| {
93            if meta.path.is_ident("trait") {
94                let value = meta.value()?;
95                let path: TypePath = value.parse()?;
96                delegate_type = Some(path);
97                Ok(())
98            } else {
99                Err(meta.error("Expected `trait = crate::path::to::class`"))
100            }
101        })
102        .unwrap();
103
104    // Generate the output code for each enum variant
105    let mut consts = Vec::new();
106    let mut delegation_arms = Vec::new();
107    for variant in &item_enum.variants {
108        let ident = &variant.ident;
109        let attr = match variant.attrs.iter().find(|a| a.path().is_ident("delegate")) {
110            Some(attr) => attr,
111            None => return error(variant.span(), "Missing `delegate` attribute on variant").into(),
112        };
113
114        let nested = attr.parse_nested_meta(|meta| {
115            if !meta.path.is_ident("path") {
116                return Err(meta.error("Expected `delegate(path = crate::path::to::class)`"));
117            }
118
119            let path = meta.value()?.parse::<Path>()?;
120            let const_ident = Ident::new(&format!("{}_INSTANCE", ident), ident.span());
121            let r#const = quote! {
122                #[allow(non_upper_case_globals)]
123                static #const_ident: _LazyLock<#enum_name::Delegate> = _LazyLock::new(|| Box::new(#path::new()));
124            };
125            let arm = quote! { #enum_name::#ident => &*#const_ident };
126            consts.push(r#const);
127            delegation_arms.push(arm);
128            Ok(())
129        });
130
131        if let Err(err) = nested {
132            return err.into_compile_error().into();
133        }
134    }
135
136    // Generate the output code
137    let expanded = quote! {
138        #[automatically_derived]
139        impl #enum_name {
140            #[automatically_derived]
141            pub type Delegate = Box<(dyn #delegate_type)>;
142        }
143
144        const _: () = {
145            use std::sync::LazyLock as _LazyLock;
146            use std::ops::Deref as _Deref;
147
148            #(#consts)*
149
150            #[automatically_derived]
151            impl std::ops::Deref for #enum_name {
152                type Target = #enum_name::Delegate;
153
154                #[automatically_derived]
155                fn deref(&self) -> &Self::Target {
156                    match self {
157                        #(#delegation_arms),*
158                    }
159                }
160            }
161        };
162
163    };
164
165    // Return the generated code as a TokenStream
166    TokenStream::from(expanded)
167}
168
169#[proc_macro_derive(CommonFields)]
170pub fn conditional_fields_macro(input: TokenStream) -> TokenStream {
171    let input = parse_macro_input!(input as DeriveInput);
172    enums::common_fields::common_fields(input).into()
173}
174
175#[proc_macro_derive(Builder, attributes(builder))]
176pub fn builder_macro(input: TokenStream) -> TokenStream {
177    let input = parse_macro_input!(input as DeriveInput);
178    builder::builder(input)
179}
180
181// #[proc_macro]
182// pub fn hack_impls(input: TokenStream) -> TokenStream {
183//     println!("{:#?}", input);
184//
185//     let actual_type = quote::quote! { type_name::<#input[0]>() };
186//     let quote = quote::quote! { impls::impls!(#actual_type, ) };
187//     // let actual_type_of_generic = quote::quote! { impls::impls!(#input[0]: #input[2]) };
188//
189//     TokenStream::new()
190// }
191// #[proc_macro_derive(Generics)]
192// pub fn generics_macro(input: TokenStream) -> TokenStream {
193//     // Parse the input tokens into a syntax tree
194//     let input = parse_macro_input!(input as DeriveInput);
195//
196//     // Get the name of the struct
197//     let struct_name = &input.ident;
198//
199//     // Extract generics information from the first field (you can modify this based on your needs)
200//     let generics_from_field = if let Data::Struct(data) = &input.data {
201//         if let Some(field) = data.fields.iter().next() {
202//             let ty = &field.ty;
203//             let is_bool = quote! { impls::impls!(#ty) }
204//
205//             quote! { #ty }
206//         } else {
207//             quote! { compile_error!("Struct must have at least one field") }
208//         }
209//     } else {
210//         quote! { compile_error!("Only structs are supported") }
211//     };
212//
213//     // Convert the generated code into a TokenStream and return it
214//     output.into()
215// }