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&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
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 #![allow(
141 clippy::doc_markdown,
142 clippy::module_name_repetitions,
143 clippy::needless_doctest_main,
144 clippy::too_many_lines
145 )]
146
147 extern crate proc_macro;
148
149 mod attr;
150 mod error;
151 mod segment;
152
153 use crate::attr::expand_attr;
154 use crate::error::{Error, Result};
155 use crate::segment::Segment;
156 use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
157 use std::iter;
158 use std::panic;
159
160 #[proc_macro]
paste(input: TokenStream) -> TokenStream161 pub fn paste(input: TokenStream) -> TokenStream {
162 let mut contains_paste = false;
163 let flatten_single_interpolation = true;
164 match expand(input, &mut contains_paste, flatten_single_interpolation) {
165 Ok(expanded) => expanded,
166 Err(err) => err.to_compile_error(),
167 }
168 }
169
170 #[doc(hidden)]
171 #[proc_macro]
item(input: TokenStream) -> TokenStream172 pub fn item(input: TokenStream) -> TokenStream {
173 paste(input)
174 }
175
176 #[doc(hidden)]
177 #[proc_macro]
expr(input: TokenStream) -> TokenStream178 pub fn expr(input: TokenStream) -> TokenStream {
179 paste(input)
180 }
181
expand( input: TokenStream, contains_paste: &mut bool, flatten_single_interpolation: bool, ) -> Result<TokenStream>182 fn expand(
183 input: TokenStream,
184 contains_paste: &mut bool,
185 flatten_single_interpolation: bool,
186 ) -> Result<TokenStream> {
187 let mut expanded = TokenStream::new();
188 let mut lookbehind = Lookbehind::Other;
189 let mut prev_none_group = None::<Group>;
190 let mut tokens = input.into_iter().peekable();
191 loop {
192 let token = tokens.next();
193 if let Some(group) = prev_none_group.take() {
194 if match (&token, tokens.peek()) {
195 (Some(TokenTree::Punct(fst)), Some(TokenTree::Punct(snd))) => {
196 fst.as_char() == ':' && snd.as_char() == ':' && fst.spacing() == Spacing::Joint
197 }
198 _ => false,
199 } {
200 expanded.extend(group.stream());
201 *contains_paste = true;
202 } else {
203 expanded.extend(iter::once(TokenTree::Group(group)));
204 }
205 }
206 match token {
207 Some(TokenTree::Group(group)) => {
208 let delimiter = group.delimiter();
209 let content = group.stream();
210 let span = group.span();
211 if delimiter == Delimiter::Bracket && is_paste_operation(&content) {
212 let segments = parse_bracket_as_segments(content, span)?;
213 let pasted = segment::paste(&segments)?;
214 let tokens = pasted_to_tokens(pasted, span)?;
215 expanded.extend(tokens);
216 *contains_paste = true;
217 } else if flatten_single_interpolation
218 && delimiter == Delimiter::None
219 && is_single_interpolation_group(&content)
220 {
221 expanded.extend(content);
222 *contains_paste = true;
223 } else {
224 let mut group_contains_paste = false;
225 let is_attribute = delimiter == Delimiter::Bracket
226 && (lookbehind == Lookbehind::Pound || lookbehind == Lookbehind::PoundBang);
227 let mut nested = expand(
228 content,
229 &mut group_contains_paste,
230 flatten_single_interpolation && !is_attribute,
231 )?;
232 if is_attribute {
233 nested = expand_attr(nested, span, &mut group_contains_paste)?
234 }
235 let group = if group_contains_paste {
236 let mut group = Group::new(delimiter, nested);
237 group.set_span(span);
238 *contains_paste = true;
239 group
240 } else {
241 group.clone()
242 };
243 if delimiter != Delimiter::None {
244 expanded.extend(iter::once(TokenTree::Group(group)));
245 } else if lookbehind == Lookbehind::DoubleColon {
246 expanded.extend(group.stream());
247 *contains_paste = true;
248 } else {
249 prev_none_group = Some(group);
250 }
251 }
252 lookbehind = Lookbehind::Other;
253 }
254 Some(TokenTree::Punct(punct)) => {
255 lookbehind = match punct.as_char() {
256 ':' if lookbehind == Lookbehind::JointColon => Lookbehind::DoubleColon,
257 ':' if punct.spacing() == Spacing::Joint => Lookbehind::JointColon,
258 '#' => Lookbehind::Pound,
259 '!' if lookbehind == Lookbehind::Pound => Lookbehind::PoundBang,
260 _ => Lookbehind::Other,
261 };
262 expanded.extend(iter::once(TokenTree::Punct(punct)));
263 }
264 Some(other) => {
265 lookbehind = Lookbehind::Other;
266 expanded.extend(iter::once(other));
267 }
268 None => return Ok(expanded),
269 }
270 }
271 }
272
273 #[derive(PartialEq)]
274 enum Lookbehind {
275 JointColon,
276 DoubleColon,
277 Pound,
278 PoundBang,
279 Other,
280 }
281
282 // https://github.com/dtolnay/paste/issues/26
is_single_interpolation_group(input: &TokenStream) -> bool283 fn is_single_interpolation_group(input: &TokenStream) -> bool {
284 #[derive(PartialEq)]
285 enum State {
286 Init,
287 Ident,
288 Literal,
289 Apostrophe,
290 Lifetime,
291 Colon1,
292 Colon2,
293 }
294
295 let mut state = State::Init;
296 for tt in input.clone() {
297 state = match (state, &tt) {
298 (State::Init, TokenTree::Ident(_)) => State::Ident,
299 (State::Init, TokenTree::Literal(_)) => State::Literal,
300 (State::Init, TokenTree::Punct(punct)) if punct.as_char() == '\'' => State::Apostrophe,
301 (State::Apostrophe, TokenTree::Ident(_)) => State::Lifetime,
302 (State::Ident, TokenTree::Punct(punct))
303 if punct.as_char() == ':' && punct.spacing() == Spacing::Joint =>
304 {
305 State::Colon1
306 }
307 (State::Colon1, TokenTree::Punct(punct))
308 if punct.as_char() == ':' && punct.spacing() == Spacing::Alone =>
309 {
310 State::Colon2
311 }
312 (State::Colon2, TokenTree::Ident(_)) => State::Ident,
313 _ => return false,
314 };
315 }
316
317 state == State::Ident || state == State::Literal || state == State::Lifetime
318 }
319
is_paste_operation(input: &TokenStream) -> bool320 fn is_paste_operation(input: &TokenStream) -> bool {
321 let mut tokens = input.clone().into_iter();
322
323 match &tokens.next() {
324 Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
325 _ => return false,
326 }
327
328 let mut has_token = false;
329 loop {
330 match &tokens.next() {
331 Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {
332 return has_token && tokens.next().is_none();
333 }
334 Some(_) => has_token = true,
335 None => return false,
336 }
337 }
338 }
339
parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segment>>340 fn parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segment>> {
341 let mut tokens = input.into_iter().peekable();
342
343 match &tokens.next() {
344 Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
345 Some(wrong) => return Err(Error::new(wrong.span(), "expected `<`")),
346 None => return Err(Error::new(scope, "expected `[< ... >]`")),
347 }
348
349 let mut segments = segment::parse(&mut tokens)?;
350
351 match &tokens.next() {
352 Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {}
353 Some(wrong) => return Err(Error::new(wrong.span(), "expected `>`")),
354 None => return Err(Error::new(scope, "expected `[< ... >]`")),
355 }
356
357 if let Some(unexpected) = tokens.next() {
358 return Err(Error::new(
359 unexpected.span(),
360 "unexpected input, expected `[< ... >]`",
361 ));
362 }
363
364 for segment in &mut segments {
365 if let Segment::String(string) = segment {
366 if string.value.contains(&['#', '\\', '.', '+'][..]) {
367 return Err(Error::new(string.span, "unsupported literal"));
368 }
369 string.value = string
370 .value
371 .replace('"', "")
372 .replace('\'', "")
373 .replace('-', "_");
374 }
375 }
376
377 Ok(segments)
378 }
379
pasted_to_tokens(mut pasted: String, span: Span) -> Result<TokenStream>380 fn pasted_to_tokens(mut pasted: String, span: Span) -> Result<TokenStream> {
381 let mut tokens = TokenStream::new();
382
383 if pasted.starts_with('\'') {
384 let mut apostrophe = TokenTree::Punct(Punct::new('\'', Spacing::Joint));
385 apostrophe.set_span(span);
386 tokens.extend(iter::once(apostrophe));
387 pasted.remove(0);
388 }
389
390 let ident = match panic::catch_unwind(|| Ident::new(&pasted, span)) {
391 Ok(ident) => TokenTree::Ident(ident),
392 Err(_) => {
393 return Err(Error::new(
394 span,
395 &format!("`{:?}` is not a valid identifier", pasted),
396 ));
397 }
398 };
399
400 tokens.extend(iter::once(ident));
401 Ok(tokens)
402 }
403