paste3/
lib.rs

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