1 //! [![github]](https://github.com/dtolnay/paste) [![crates-io]](https://crates.io/crates/paste) [![docs-rs]](https://docs.rs/paste)
2 //!
3 //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4 //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5 //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6 //!
7 //! <br>
8 //!
9 //! The nightly-only [`concat_idents!`] macro in the Rust standard library is
10 //! notoriously underpowered in that its concatenated identifiers can only refer to
11 //! existing items, they can never be used to define something new.
12 //!
13 //! [`concat_idents!`]: https://doc.rust-lang.org/std/macro.concat_idents.html
14 //!
15 //! This crate provides a flexible way to paste together identifiers in a macro,
16 //! including using pasted identifiers to define new items.
17 //!
18 //! This approach works with any Rust compiler 1.31+.
19 //!
20 //! <br>
21 //!
22 //! # Pasting identifiers
23 //!
24 //! Within the `paste!` macro, identifiers inside `[<`...`>]` are pasted
25 //! together to form a single identifier.
26 //!
27 //! ```
28 //! use paste::paste;
29 //!
30 //! paste! {
31 //!     // Defines a const called `QRST`.
32 //!     const [<Q R S T>]: &str = "success!";
33 //! }
34 //!
35 //! fn main() {
36 //!     assert_eq!(
37 //!         paste! { [<Q R S T>].len() },
38 //!         8,
39 //!     );
40 //! }
41 //! ```
42 //!
43 //! <br><br>
44 //!
45 //! # More elaborate example
46 //!
47 //! The next example shows a macro that generates accessor methods for some
48 //! struct fields. It demonstrates how you might find it useful to bundle a
49 //! paste invocation inside of a macro\_rules macro.
50 //!
51 //! ```
52 //! use paste::paste;
53 //!
54 //! macro_rules! make_a_struct_and_getters {
55 //!     ($name:ident { $($field:ident),* }) => {
56 //!         // Define a struct. This expands to:
57 //!         //
58 //!         //     pub struct S {
59 //!         //         a: String,
60 //!         //         b: String,
61 //!         //         c: String,
62 //!         //     }
63 //!         pub struct $name {
64 //!             $(
65 //!                 $field: String,
66 //!             )*
67 //!         }
68 //!
69 //!         // Build an impl block with getters. This expands to:
70 //!         //
71 //!         //     impl S {
72 //!         //         pub fn get_a(&self) -> &str { &self.a }
73 //!         //         pub fn get_b(&self) -> &str { &self.b }
74 //!         //         pub fn get_c(&self) -> &str { &self.c }
75 //!         //     }
76 //!         paste! {
77 //!             impl $name {
78 //!                 $(
79 //!                     pub fn [<get_ $field>](&self) -> &str {
80 //!                         &self.$field
81 //!                     }
82 //!                 )*
83 //!             }
84 //!         }
85 //!     }
86 //! }
87 //!
88 //! make_a_struct_and_getters!(S { a, b, c });
89 //!
90 //! fn call_some_getters(s: &S) -> bool {
91 //!     s.get_a() == s.get_b() && s.get_c().is_empty()
92 //! }
93 //! #
94 //! # fn main() {}
95 //! ```
96 //!
97 //! <br><br>
98 //!
99 //! # Case conversion
100 //!
101 //! Use `$var:lower` or `$var:upper` in the segment list to convert an
102 //! interpolated segment to lower- or uppercase as part of the paste. For
103 //! example, `[<ld_ $reg:lower _expr>]` would paste to `ld_bc_expr` if invoked
104 //! with $reg=`Bc`.
105 //!
106 //! Use `$var:snake` to convert CamelCase input to snake\_case.
107 //! Use `$var:camel` to convert snake\_case to CamelCase.
108 //! These compose, so for example `$var:snake:upper` would give you SCREAMING\_CASE.
109 //!
110 //! The precise Unicode conversions are as defined by [`str::to_lowercase`] and
111 //! [`str::to_uppercase`].
112 //!
113 //! [`str::to_lowercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase
114 //! [`str::to_uppercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_uppercase
115 //!
116 //! <br>
117 //!
118 //! # Pasting documentation strings
119 //!
120 //! Within the `paste!` macro, arguments to a #\[doc ...\] attribute are
121 //! implicitly concatenated together to form a coherent documentation string.
122 //!
123 //! ```
124 //! use paste::paste;
125 //!
126 //! macro_rules! method_new {
127 //!     ($ret:ident) => {
128 //!         paste! {
129 //!             #[doc = "Create a new `" $ret "` object."]
130 //!             pub fn new() -> $ret { todo!() }
131 //!         }
132 //!     };
133 //! }
134 //!
135 //! pub struct Paste {}
136 //!
137 //! method_new!(Paste);  // expands to #[doc = "Create a new `Paste` object"]
138 //! ```
139 
140 #![doc(html_root_url = "https://docs.rs/paste/1.0.14")]
141 #![allow(
142     clippy::derive_partial_eq_without_eq,
143     clippy::doc_markdown,
144     clippy::match_same_arms,
145     clippy::module_name_repetitions,
146     clippy::needless_doctest_main,
147     clippy::too_many_lines
148 )]
149 
150 extern crate proc_macro;
151 
152 mod attr;
153 mod error;
154 mod segment;
155 
156 use crate::attr::expand_attr;
157 use crate::error::{Error, Result};
158 use crate::segment::Segment;
159 use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
160 use std::char;
161 use std::iter;
162 use std::panic;
163 
164 #[proc_macro]
paste(input: TokenStream) -> TokenStream165 pub fn paste(input: TokenStream) -> TokenStream {
166     let mut contains_paste = false;
167     let flatten_single_interpolation = true;
168     match expand(
169         input.clone(),
170         &mut contains_paste,
171         flatten_single_interpolation,
172     ) {
173         Ok(expanded) => {
174             if contains_paste {
175                 expanded
176             } else {
177                 input
178             }
179         }
180         Err(err) => err.to_compile_error(),
181     }
182 }
183 
184 #[doc(hidden)]
185 #[proc_macro]
item(input: TokenStream) -> TokenStream186 pub fn item(input: TokenStream) -> TokenStream {
187     paste(input)
188 }
189 
190 #[doc(hidden)]
191 #[proc_macro]
expr(input: TokenStream) -> TokenStream192 pub fn expr(input: TokenStream) -> TokenStream {
193     paste(input)
194 }
195 
expand( input: TokenStream, contains_paste: &mut bool, flatten_single_interpolation: bool, ) -> Result<TokenStream>196 fn expand(
197     input: TokenStream,
198     contains_paste: &mut bool,
199     flatten_single_interpolation: bool,
200 ) -> Result<TokenStream> {
201     let mut expanded = TokenStream::new();
202     let mut lookbehind = Lookbehind::Other;
203     let mut prev_none_group = None::<Group>;
204     let mut tokens = input.into_iter().peekable();
205     loop {
206         let token = tokens.next();
207         if let Some(group) = prev_none_group.take() {
208             if match (&token, tokens.peek()) {
209                 (Some(TokenTree::Punct(fst)), Some(TokenTree::Punct(snd))) => {
210                     fst.as_char() == ':' && snd.as_char() == ':' && fst.spacing() == Spacing::Joint
211                 }
212                 _ => false,
213             } {
214                 expanded.extend(group.stream());
215                 *contains_paste = true;
216             } else {
217                 expanded.extend(iter::once(TokenTree::Group(group)));
218             }
219         }
220         match token {
221             Some(TokenTree::Group(group)) => {
222                 let delimiter = group.delimiter();
223                 let content = group.stream();
224                 let span = group.span();
225                 if delimiter == Delimiter::Bracket && is_paste_operation(&content) {
226                     let segments = parse_bracket_as_segments(content, span)?;
227                     let pasted = segment::paste(&segments)?;
228                     let tokens = pasted_to_tokens(pasted, span)?;
229                     expanded.extend(tokens);
230                     *contains_paste = true;
231                 } else if flatten_single_interpolation
232                     && delimiter == Delimiter::None
233                     && is_single_interpolation_group(&content)
234                 {
235                     expanded.extend(content);
236                     *contains_paste = true;
237                 } else {
238                     let mut group_contains_paste = false;
239                     let is_attribute = delimiter == Delimiter::Bracket
240                         && (lookbehind == Lookbehind::Pound || lookbehind == Lookbehind::PoundBang);
241                     let mut nested = expand(
242                         content,
243                         &mut group_contains_paste,
244                         flatten_single_interpolation && !is_attribute,
245                     )?;
246                     if is_attribute {
247                         nested = expand_attr(nested, span, &mut group_contains_paste)?;
248                     }
249                     let group = if group_contains_paste {
250                         let mut group = Group::new(delimiter, nested);
251                         group.set_span(span);
252                         *contains_paste = true;
253                         group
254                     } else {
255                         group.clone()
256                     };
257                     if delimiter != Delimiter::None {
258                         expanded.extend(iter::once(TokenTree::Group(group)));
259                     } else if lookbehind == Lookbehind::DoubleColon {
260                         expanded.extend(group.stream());
261                         *contains_paste = true;
262                     } else {
263                         prev_none_group = Some(group);
264                     }
265                 }
266                 lookbehind = Lookbehind::Other;
267             }
268             Some(TokenTree::Punct(punct)) => {
269                 lookbehind = match punct.as_char() {
270                     ':' if lookbehind == Lookbehind::JointColon => Lookbehind::DoubleColon,
271                     ':' if punct.spacing() == Spacing::Joint => Lookbehind::JointColon,
272                     '#' => Lookbehind::Pound,
273                     '!' if lookbehind == Lookbehind::Pound => Lookbehind::PoundBang,
274                     _ => Lookbehind::Other,
275                 };
276                 expanded.extend(iter::once(TokenTree::Punct(punct)));
277             }
278             Some(other) => {
279                 lookbehind = Lookbehind::Other;
280                 expanded.extend(iter::once(other));
281             }
282             None => return Ok(expanded),
283         }
284     }
285 }
286 
287 #[derive(PartialEq)]
288 enum Lookbehind {
289     JointColon,
290     DoubleColon,
291     Pound,
292     PoundBang,
293     Other,
294 }
295 
296 // https://github.com/dtolnay/paste/issues/26
is_single_interpolation_group(input: &TokenStream) -> bool297 fn is_single_interpolation_group(input: &TokenStream) -> bool {
298     #[derive(PartialEq)]
299     enum State {
300         Init,
301         Ident,
302         Literal,
303         Apostrophe,
304         Lifetime,
305         Colon1,
306         Colon2,
307     }
308 
309     let mut state = State::Init;
310     for tt in input.clone() {
311         state = match (state, &tt) {
312             (State::Init, TokenTree::Ident(_)) => State::Ident,
313             (State::Init, TokenTree::Literal(_)) => State::Literal,
314             (State::Init, TokenTree::Punct(punct)) if punct.as_char() == '\'' => State::Apostrophe,
315             (State::Apostrophe, TokenTree::Ident(_)) => State::Lifetime,
316             (State::Ident, TokenTree::Punct(punct))
317                 if punct.as_char() == ':' && punct.spacing() == Spacing::Joint =>
318             {
319                 State::Colon1
320             }
321             (State::Colon1, TokenTree::Punct(punct))
322                 if punct.as_char() == ':' && punct.spacing() == Spacing::Alone =>
323             {
324                 State::Colon2
325             }
326             (State::Colon2, TokenTree::Ident(_)) => State::Ident,
327             _ => return false,
328         };
329     }
330 
331     state == State::Ident || state == State::Literal || state == State::Lifetime
332 }
333 
is_paste_operation(input: &TokenStream) -> bool334 fn is_paste_operation(input: &TokenStream) -> bool {
335     let mut tokens = input.clone().into_iter();
336 
337     match &tokens.next() {
338         Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
339         _ => return false,
340     }
341 
342     let mut has_token = false;
343     loop {
344         match &tokens.next() {
345             Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {
346                 return has_token && tokens.next().is_none();
347             }
348             Some(_) => has_token = true,
349             None => return false,
350         }
351     }
352 }
353 
parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segment>>354 fn parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segment>> {
355     let mut tokens = input.into_iter().peekable();
356 
357     match &tokens.next() {
358         Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
359         Some(wrong) => return Err(Error::new(wrong.span(), "expected `<`")),
360         None => return Err(Error::new(scope, "expected `[< ... >]`")),
361     }
362 
363     let mut segments = segment::parse(&mut tokens)?;
364 
365     match &tokens.next() {
366         Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {}
367         Some(wrong) => return Err(Error::new(wrong.span(), "expected `>`")),
368         None => return Err(Error::new(scope, "expected `[< ... >]`")),
369     }
370 
371     if let Some(unexpected) = tokens.next() {
372         return Err(Error::new(
373             unexpected.span(),
374             "unexpected input, expected `[< ... >]`",
375         ));
376     }
377 
378     for segment in &mut segments {
379         if let Segment::String(string) = segment {
380             if string.value.starts_with("'\\u{") {
381                 let hex = &string.value[4..string.value.len() - 2];
382                 if let Ok(unsigned) = u32::from_str_radix(hex, 16) {
383                     if let Some(ch) = char::from_u32(unsigned) {
384                         string.value.clear();
385                         string.value.push(ch);
386                         continue;
387                     }
388                 }
389             }
390             if string.value.contains(&['#', '\\', '.', '+'][..])
391                 || string.value.starts_with("b'")
392                 || string.value.starts_with("b\"")
393                 || string.value.starts_with("br\"")
394             {
395                 return Err(Error::new(string.span, "unsupported literal"));
396             }
397             let mut range = 0..string.value.len();
398             if string.value.starts_with("r\"") {
399                 range.start += 2;
400                 range.end -= 1;
401             } else if string.value.starts_with(&['"', '\''][..]) {
402                 range.start += 1;
403                 range.end -= 1;
404             }
405             string.value = string.value[range].replace('-', "_");
406         }
407     }
408 
409     Ok(segments)
410 }
411 
pasted_to_tokens(mut pasted: String, span: Span) -> Result<TokenStream>412 fn pasted_to_tokens(mut pasted: String, span: Span) -> Result<TokenStream> {
413     let mut tokens = TokenStream::new();
414 
415     #[cfg(not(no_literal_fromstr))]
416     {
417         use proc_macro::{LexError, Literal};
418         use std::str::FromStr;
419 
420         if pasted.starts_with(|ch: char| ch.is_ascii_digit()) {
421             let literal = match panic::catch_unwind(|| Literal::from_str(&pasted)) {
422                 Ok(Ok(literal)) => TokenTree::Literal(literal),
423                 Ok(Err(LexError { .. })) | Err(_) => {
424                     return Err(Error::new(
425                         span,
426                         &format!("`{:?}` is not a valid literal", pasted),
427                     ));
428                 }
429             };
430             tokens.extend(iter::once(literal));
431             return Ok(tokens);
432         }
433     }
434 
435     if pasted.starts_with('\'') {
436         let mut apostrophe = TokenTree::Punct(Punct::new('\'', Spacing::Joint));
437         apostrophe.set_span(span);
438         tokens.extend(iter::once(apostrophe));
439         pasted.remove(0);
440     }
441 
442     let ident = match panic::catch_unwind(|| Ident::new(&pasted, span)) {
443         Ok(ident) => TokenTree::Ident(ident),
444         Err(_) => {
445             return Err(Error::new(
446                 span,
447                 &format!("`{:?}` is not a valid identifier", pasted),
448             ));
449         }
450     };
451 
452     tokens.extend(iter::once(ident));
453     Ok(tokens)
454 }
455