paste/
lib.rs

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.15")]
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
150extern crate proc_macro;
151
152mod attr;
153mod error;
154mod segment;
155
156use crate::attr::expand_attr;
157use crate::error::{Error, Result};
158use crate::segment::Segment;
159use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
160use std::char;
161use std::iter;
162use std::panic;
163
164#[proc_macro]
165pub 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]
186pub fn item(input: TokenStream) -> TokenStream {
187    paste(input)
188}
189
190#[doc(hidden)]
191#[proc_macro]
192pub fn expr(input: TokenStream) -> TokenStream {
193    paste(input)
194}
195
196fn 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)]
288enum Lookbehind {
289    JointColon,
290    DoubleColon,
291    Pound,
292    PoundBang,
293    Other,
294}
295
296// https://github.com/dtolnay/paste/issues/26
297fn 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
334fn 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
354fn 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
412fn 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}