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#[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 #[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 #[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}