fast_concat_macro/
lib.rs

1#![deny(clippy::unwrap_used)]
2#![deny(clippy::expect_used)]
3#![warn(clippy::pedantic)]
4#![warn(clippy::nursery)]
5#![deny(clippy::unwrap_used, clippy::expect_used)]
6#![allow(clippy::must_use_candidate)]
7#![warn(missing_debug_implementations)]
8#![warn(missing_copy_implementations)]
9#![allow(clippy::module_name_repetitions)]
10#![warn(
11    clippy::arithmetic_side_effects,
12    clippy::unreachable,
13    clippy::unchecked_duration_subtraction,
14    clippy::todo,
15    clippy::string_slice,
16    clippy::panic_in_result_fn,
17    clippy::indexing_slicing,
18    clippy::panic,
19    clippy::exit,
20    clippy::as_conversions,
21    clippy::large_futures,
22    clippy::large_stack_arrays,
23    clippy::large_stack_frames,
24    clippy::modulo_one,
25    clippy::mem_replace_with_uninit,
26    clippy::iterator_step_by_zero,
27    clippy::invalid_regex,
28    clippy::print_stdout,
29    clippy::print_stderr
30)]
31
32extern crate proc_macro;
33
34mod fast_concat_parser;
35
36use crate::fast_concat_parser::FastConcatParser;
37use proc_macro::TokenStream;
38use proc_macro2::{Ident, Span};
39use quote::{quote, ToTokens};
40use syn::{parse_macro_input, parse_quote, Expr, Stmt};
41
42struct OutExpression {
43    pub expr: Expr,
44    pub is_const: bool,
45}
46
47// CLIPPY: Doesn't actually panic
48#[allow(clippy::missing_panics_doc)]
49#[proc_macro]
50pub fn fast_concat(input: TokenStream) -> TokenStream {
51    let body = parse_macro_input!(input as FastConcatParser);
52
53    if body.items.is_empty() {
54        return quote!("").into_token_stream().into();
55    }
56
57    let mut consts = Vec::<Expr>::new();
58    let mut out_expressions = Vec::<OutExpression>::with_capacity(body.items.len());
59
60    for item in body.items {
61        if item.is_const || matches!(item.expr, Expr::Lit(_)) {
62            consts.push(item.expr);
63        } else {
64            if !consts.is_empty() {
65                if consts.len() == 1 {
66                    // CLIPPY: Above is len check
67                    #[allow(clippy::unwrap_used)]
68                    let const_e = consts.pop().unwrap();
69                    out_expressions.push(OutExpression {
70                        is_const: true,
71                        expr: parse_quote!(#const_e),
72                    });
73                } else {
74                    out_expressions.push(OutExpression {
75                        is_const: true,
76                        expr: parse_quote!(::constcat::concat!(#(#consts,)*)),
77                    });
78                }
79
80                consts.clear();
81            }
82
83            out_expressions.push(OutExpression {
84                is_const: false,
85                expr: item.expr,
86            });
87        }
88    }
89
90    if !consts.is_empty() {
91        out_expressions.push(OutExpression {
92            is_const: true,
93            expr: parse_quote!(::constcat::concat!(#(#consts,)*)),
94        });
95    }
96
97    if out_expressions.len() == 1 {
98        // CLIPPY: Above is len check
99        #[allow(clippy::unwrap_used)]
100        let OutExpression { is_const, expr } = out_expressions.pop().unwrap();
101
102        return if is_const {
103            quote!({
104                const OUTPUT: &'static str = #expr;
105                OUTPUT
106            })
107            .into_token_stream()
108            .into()
109        } else {
110            quote!(#expr).into_token_stream().into()
111        };
112    }
113
114    let mut variable_idents = Vec::<Ident>::new();
115    let mut variable_declarations = Vec::<Stmt>::new();
116
117    for (i, out_expression) in out_expressions.into_iter().enumerate() {
118        let OutExpression { is_const, expr } = out_expression;
119        let variable_ident = Ident::new(&format!("_{i}"), Span::call_site());
120
121        variable_idents.push(variable_ident.clone());
122
123        if is_const {
124            variable_declarations.push(parse_quote!(const #variable_ident: &'static str = #expr;));
125        } else {
126            variable_declarations.push(parse_quote!(let #variable_ident: &str = #expr;));
127        }
128    }
129
130    quote!({
131        extern crate alloc;
132        #(#variable_declarations)*
133        let mut buf = alloc::string::String::with_capacity(0 #(+ #variable_idents.len())*);
134        #(buf.push_str(#variable_idents);)*
135        buf
136    })
137    .into_token_stream()
138    .into()
139}