format_like/lib.rs
1//! A macro for creating format-like macros
2//!
3//! Have you ever wanted to emulate the functionality of the `format!`
4//! family of macros, but with an output that is not a [`String`] or
5//! something built from a [`String`]?
6//!
7//! No?
8//!
9//! Well, still, this might still be interesting for you.
10//!
11//! `format-like` aims to let _you_ decide how to interpret what is
12//! inside `{}` pairs, instead of calling something like
13//! `std::fmt::Display::fmt(&value)`.
14//!
15//! Additionaly, it lets you create 3 other types of bracket pairs:
16//! `()`, `[]` and `<>`, so you can interpret things in even more
17//! ways! This does of course come with the regular escaping that the
18//! [`format!`] macro does, so `{{` is escaped to just `{`, the same
19//! being the case for the other delimiters as well.
20//!
21//! Here's how it works:
22//!
23//! ```rust
24//! # #![feature(decl_macro)]
25//! use format_like::format_like;
26//!
27//! struct CommentedString(String, Vec<(usize, String)>);
28//!
29//! let comment = "there is an error in this word";
30//! let text = "text";
31//! let range = 0..usize::MAX;
32//!
33//! let commented_string = format_like!(
34//! parse_str,
35//! [('{', parse_interpolation, false), ('<', parse_comment, true)],
36//! CommentedString(String::new(), Vec::new()),
37//! "This is <comment>regluar {}, interpolated and commented {range.end}",
38//! text
39//! );
40//! # macro parse_str($value:expr, $str:literal) {{ $value }}
41//! # macro parse_interpolation($value:expr, $modif:literal, $added:expr) {{ $value }}
42//! # macro parse_comment($value:expr, $modif:literal, $added:expr) {{ $value }}
43//! ```
44//!
45//! In this example, the `{}` should work as intended, but you also
46//! have access to `<>` interpolation. Inside `<>`, a comment will be
47//! added, with the associated `usize` being its position in the
48//! [`String`].
49//!
50//! This will all be done through the `parse_str`,
51//! `parse_interpolation` and `parse_comment` macros:
52//!
53//! ```rust
54//! #![feature(decl_macro)]
55//! macro parse_str($value:expr, $str:literal) {{
56//! let mut commented_string = $value;
57//! commented_string.0.push_str($str);
58//! commented_string
59//! }}
60//!
61//! macro parse_interpolation($value:expr, $modif:literal, $added:expr) {{
62//! let CommentedString(string, comments) = $value;
63//! let string = format!(concat!("{}{", $modif, "}"), string, $added);
64//! CommentedString(string, comments)
65//! }}
66//!
67//! macro parse_comment($value:expr, $_modif:literal, $added:expr) {{
68//! let mut commented_string = $value;
69//! commented_string
70//! .1
71//! .push((commented_string.0.len(), $added.to_string()));
72//! commented_string
73//! }}
74//! ```
75//!
76//! The `parse_str` macro will be responsible for handling the non
77//! `{}` or `<>` parts of the literal `&str`. The `parse_comment` and
78//! `parse_interpolation` methods will handle what's inside the `<>`
79//! and `{}` pairs, respectively.
80//!
81//! `parse_comment` and `parse_interpolation` must have three
82//! parameters, one for the `value` being modified (in this case, a
83//! `CommentedString`), one for the modifier (`"?", "#?", ".3", etc),
84//! which might come after a `":"` in the pair. and one for the object
85//! being added (it's [`Display`] objects in this case, but
86//! it could be anything else).
87//!
88//! Now, as I mentioned earlier, this crate is meant for you to create
89//! _your own_ format like macros, so you should package all of this
90//! up into a single macro, like this:
91//!
92//! ```rust
93//! #![feature(decl_macro)]
94//! use format_like::format_like;
95//!
96//! #[derive(Debug, PartialEq)]
97//! struct CommentedString(String, Vec<(usize, String)>);
98//!
99//! let comment = "there is an error in this word";
100//! let text = "text";
101//! let range = 0..usize::MAX;
102//!
103//! let commented_string = commented_string!(
104//! "This is <comment>regluar {}, interpolated and commented {range.end}",
105//! text
106//! );
107//!
108//! assert_eq!(
109//! commented_string,
110//! CommentedString(
111//! "This is regluar text, interpolated and commented 18446744073709551615".to_string(),
112//! vec![(8, "there is an error in this word".to_string())]
113//! )
114//! );
115//!
116//! macro commented_string($($parts:tt)*) {
117//! format_like!(
118//! parse_str,
119//! [('{', parse_interpolation, false), ('<', parse_comment, true)],
120//! CommentedString(String::new(), Vec::new()),
121//! $($parts)*
122//! )
123//! }
124//!
125//! macro parse_str($value:expr, $str:literal) {{
126//! let mut commented_string = $value;
127//! commented_string.0.push_str($str);
128//! commented_string
129//! }}
130//!
131//! macro parse_interpolation($value:expr, $modif:literal, $added:expr) {{
132//! let CommentedString(string, comments) = $value;
133//! let string = format!(concat!("{}{", $modif, "}"), string, $added);
134//! CommentedString(string, comments)
135//! }}
136//!
137//! macro parse_comment($value:expr, $_modif:literal, $added:expr) {{
138//! let mut commented_string = $value;
139//! commented_string.1.push((commented_string.0.len(), $added.to_string()));
140//! commented_string
141//! }}
142//! ```
143//!
144//! ## Forced inlining
145//!
146//! You might be wondering: What are the `false` and `true` in the
147//! second argument of [`format_like!`] used for?
148//!
149//! Well, they determine wether an argument _must_ be inlined (i.e. be
150//! placed within the string like `{arg}`). This is useful when you
151//! want to limit the types of arguments that a macro should handle.
152//!
153//! As you might have seen earlier, [`format_like!`] accepts member
154//! access, like `{range.end}`. If you force a parameter to always be
155//! placed inline, that limits the types of tokens your macro must be
156//! able to handle, so you could rewrite the `parse_comment` macro to
157//! be:
158//!
159//! ```rust
160//! #![feature(decl_macro)]
161//! macro parse_comment($value:expr, $modif:literal, $($identifier:ident).*) {{
162//! // innards
163//! }}
164//! ```
165//!
166//! While this may not seem useful, it comes with two interesting
167//! abilities:
168//!
169//! 1 - If arguments must be inlined, you are allowed to leave the
170//! pair empty, like `<>`, and you can handle this situation
171//! differently if you want.
172//! 2 - By accessing the `$identifiers` directly, you can manipulate
173//! them in whichever way you want, heck, they may not even point to
174//! any actual variable in the code, and could be some sort of
175//! differently handled string literal.
176//!
177//! ## Motivation
178//!
179//! Even after reading all that, I wouldn't be surprised if you
180//! haven't found any particular use for this crate, and that's fine.
181//!
182//! But here is what was _my_ motivation for creating it:
183//!
184//! In my _in development_ text editor [Duat], there _used to be_ a
185//! `text` macro, which created a `Text` struct, which was essentially
186//! a [`String`] with formatting `Tag`s added on to it.
187//!
188//! It used to work like this:
189//!
190//! ```rust,ignore
191//! let text = text!("start " [RedColor.subvalue] variable " " other_variable " ");
192//! ```
193//!
194//! This macro was a simple declarative macro, so while it was easy to
195//! implement, there were several drawbacks to its design:
196//!
197//! - It was ignored by rustfmt;
198//! - It didn't look like Rust;
199//! - tree-sitter failed at syntax highlighting it;
200//! - It didn't look like Rust;
201//! - Way too much space was occupied by simple things like `" "`;
202//! - It didn't look like Rust;
203//!
204//! And now I have replaced the old `text` macro with a new version,
205//! based on `format_like!`, which makes for a much cleaner design:
206//!
207//! ```rust,ignore
208//! let text = text!("start [RedColor.subvalue]{variable} {other_variable} ");
209//! ```
210//!
211//! [`Display`]: std::fmt::Display
212//! [Duat]: https://github.com/AhoyISki/duat
213use std::ops::Range;
214
215use litrs::StringLit;
216use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
217
218/// A macro for creating format-like macros
219///
220/// ```rust
221/// #![feature(decl_macro)]
222/// use format_like::format_like;
223///
224/// #[derive(Debug, PartialEq)]
225/// struct CommentedString(String, Vec<(usize, String)>);
226///
227/// let comment = "there is an error in this word";
228/// let text = "text";
229/// let range = 0..usize::MAX;
230///
231/// let commented_string = commented_string!(
232/// "This is <comment>worng {}, and this is the end of the range {range.end}",
233/// text
234/// );
235///
236/// assert_eq!(
237/// commented_string,
238/// CommentedString(
239/// "This is worng text, and this is the end of the range 18446744073709551615".to_string(),
240/// vec![(8, "there is an error in this word".to_string())]
241/// )
242/// );
243///
244/// macro commented_string($($parts:tt)*) {
245/// format_like!(
246/// parse_str,
247/// [('{', parse_interpolation, false), ('<', parse_comment, true)],
248/// CommentedString(String::new(), Vec::new()),
249/// $($parts)*
250/// )
251/// }
252///
253/// macro parse_str($value:expr, $str:literal) {{
254/// let mut commented_string = $value;
255/// commented_string.0.push_str($str);
256/// commented_string
257/// }}
258///
259/// macro parse_interpolation($value:expr, $modif:literal, $added:expr) {{
260/// let CommentedString(string, comments) = $value;
261/// let string = format!(concat!("{}{", $modif, "}"), string, $added);
262/// CommentedString(string, comments)
263/// }}
264///
265/// macro parse_comment($value:expr, $_modif:literal, $added:expr) {{
266/// let mut commented_string = $value;
267/// commented_string.1.push((commented_string.0.len(), $added.to_string()));
268/// commented_string
269/// }}
270/// ```
271#[proc_macro]
272pub fn format_like(input: TokenStream) -> TokenStream {
273 let fmt_like = match FormatLike::parse(input.into_iter()) {
274 Ok(fmt_like) => fmt_like,
275 Err(err) => return err,
276 };
277
278 let str = fmt_like.str.value();
279 let arg_parsers = &fmt_like.arg_parsers;
280
281 let mut args = Vec::new();
282
283 let mut arg: Option<CurrentArg> = None;
284 let mut unescaped_rhs: Option<(usize, char)> = None;
285 let mut push_new_ident = true;
286 let mut positional_needed = 0;
287
288 let str_span = |_r: Range<usize>| fmt_like.str_span;
289
290 for (i, char) in str.char_indices() {
291 if let Some((j, p, mut idents, mut modif)) = arg.take() {
292 let [lhs, rhs] = arg_parsers[p].delims;
293 if char == rhs {
294 let modif = match modif {
295 Some(range) => {
296 let str =
297 unsafe { str::from_utf8_unchecked(&str.as_bytes()[range.clone()]) };
298 let mut str = Literal::string(str);
299 str.set_span(fmt_like.str_span);
300
301 TokenStream::from(TokenTree::Literal(str))
302 }
303 None => TokenStream::from(TokenTree::Literal(Literal::string(""))),
304 };
305
306 if idents.is_empty() {
307 if arg_parsers[p].inline_only {
308 args.push(Arg::Inlined(p, TokenStream::new(), modif));
309 } else {
310 positional_needed += 1;
311 args.push(Arg::Positional(p, j..i + 1, modif));
312 }
313 } else if push_new_ident {
314 return compile_err(
315 str_span(i - 1..i),
316 "invalid format string: field access expected an identifier",
317 );
318 } else {
319 let mut stream = Vec::new();
320 let len = idents.len();
321
322 for (i, (range, is_tuple_member)) in idents.into_iter().enumerate() {
323 let str =
324 unsafe { str::from_utf8_unchecked(&str.as_bytes()[range.clone()]) };
325
326 stream.push(if is_tuple_member {
327 TokenTree::Literal({
328 let mut literal = Literal::usize_unsuffixed(str.parse().unwrap());
329 literal.set_span(str_span(range.clone()));
330 literal
331 })
332 } else {
333 TokenTree::Ident(Ident::new(str, str_span(range.clone())))
334 });
335
336 if i != len - 1 {
337 stream.push(TokenTree::Punct({
338 let mut punct = Punct::new('.', Spacing::Alone);
339 punct.set_span(str_span(range.end..range.end + 1));
340 punct
341 }));
342 }
343 }
344
345 args.push(Arg::Inlined(p, TokenStream::from_iter(stream), modif));
346 }
347
348 continue;
349 } else if char == lhs && idents.is_empty() {
350 // If arg was empty, that means the delimiter was repeated, so escape
351 // it.
352 extend_str_arg(&mut args, char, i - 1);
353 continue;
354 }
355
356 // We might have mismatched delimiters
357 if arg_parsers
358 .iter()
359 .any(|ap| char == ap.delims[0] || char == ap.delims[1])
360 {
361 return TokenStream::from_iter([
362 compile_err(
363 str_span(i..i + 1),
364 "invalid format string: wrong match for delimiter",
365 ),
366 compile_err(
367 str_span(j..j + 1),
368 format!("from this delimiter, expected {rhs}"),
369 ),
370 ]);
371 } else if char.is_alphanumeric() || char == '_' || modif.is_some() {
372 if let Some(modif) = &mut modif {
373 modif.end = i + 1;
374 } else if let Some((range, is_tuple_member)) = idents.last_mut()
375 && !push_new_ident
376 {
377 *is_tuple_member &= char.is_ascii_digit();
378 range.end = i + 1;
379 } else {
380 idents.push((i..i + 1, char.is_ascii_digit()));
381 push_new_ident = false;
382 }
383 } else if char == '.' {
384 if let Some(modif) = &mut modif {
385 modif.end = i + 1;
386 } else if push_new_ident {
387 // Can't start an identifier list with '.' or put multiple '.'s in a
388 // row.
389 return compile_err(
390 str_span(i..i + 1),
391 "invalid format string: unexpected '.' here",
392 );
393 } else {
394 push_new_ident = true;
395 }
396 } else if char == ':' {
397 if let Some(modif) = &mut modif {
398 modif.end = i + 1;
399 } else {
400 modif = Some(i + 1..i + 1);
401 }
402 } else {
403 return compile_err(
404 str_span(i..i + 1),
405 format!("invalid format string: unexpected {char} here"),
406 );
407 }
408
409 arg = Some((j, p, idents, modif));
410 } else if let Some(p) = arg_parsers
411 .iter()
412 .position(|ap| char == ap.delims[0] || char == ap.delims[1])
413 {
414 // If the char is a left delimiter, begin an argument.
415 // If it is a right delimiter, handle dangling right parameter
416 // scenarios.
417 if char == arg_parsers[p].delims[0] {
418 push_new_ident = true;
419 arg = Some((i, p, Vec::new(), None));
420 } else if let Some((j, unescaped)) = unescaped_rhs {
421 // Double delimiters are escaped.
422 if char == unescaped {
423 unescaped_rhs = None;
424 extend_str_arg(&mut args, char, i);
425 } else {
426 return compile_err(
427 str_span(j..j + 1),
428 format!("invalid format string: unmatched {unescaped} found"),
429 );
430 }
431 } else {
432 unescaped_rhs = Some((i, char));
433 }
434 } else if let Some((j, unescaped)) = unescaped_rhs {
435 return compile_err(
436 str_span(j..j + 1),
437 format!("invalid format string: unmatched {unescaped} found"),
438 );
439 } else {
440 extend_str_arg(&mut args, char, i);
441 }
442 }
443
444 if let Some((i, unescaped)) = unescaped_rhs {
445 return compile_err(
446 str_span(i..i + 1),
447 format!("invalid format string: unmatched {unescaped} found"),
448 );
449 }
450
451 let mut token_stream = fmt_like.initial;
452
453 let positional_provided = fmt_like.exprs.len();
454 let mut exprs = fmt_like.exprs.into_iter();
455
456 let comma = TokenTree::Punct(Punct::new(',', Spacing::Alone));
457
458 for arg in args {
459 token_stream = match arg {
460 Arg::Str(string, range) => {
461 let mut str = Literal::string(&string);
462 str.set_span(str_span(range));
463
464 recurse_parser(
465 &fmt_like.str_parser,
466 token_stream
467 .into_iter()
468 .chain([comma.clone(), TokenTree::Literal(str)]),
469 )
470 }
471 Arg::Positional(p, range, modif) => {
472 if let Some(expr) = exprs.next() {
473 recurse_parser(
474 &fmt_like.arg_parsers[p].parser,
475 token_stream
476 .into_iter()
477 .chain([comma.clone()])
478 .chain(modif)
479 .chain([comma.clone()])
480 .chain(expr),
481 )
482 } else {
483 let npl = if positional_needed == 1 { "" } else { "s" };
484 let pverb = if positional_provided == 1 {
485 "is"
486 } else {
487 "are"
488 };
489 let ppl = if positional_provided == 1 { "" } else { "s" };
490
491 return compile_err(
492 str_span(range),
493 format!(
494 "{positional_needed} positional argument{npl} in format string, but there {pverb} {positional_provided} argument{ppl}"
495 ),
496 );
497 }
498 }
499 Arg::Inlined(p, idents, modif) => recurse_parser(
500 &fmt_like.arg_parsers[p].parser,
501 token_stream
502 .into_iter()
503 .chain([comma.clone()])
504 .chain(modif)
505 .chain([comma.clone()])
506 .chain(idents),
507 ),
508 }
509 }
510
511 // There should be no positional arguments left.
512 if let Some(expr) = exprs.next() {
513 return compile_err(
514 expr.into_iter().next().unwrap().span(),
515 "argument never used",
516 );
517 }
518
519 token_stream
520}
521
522struct ArgParser {
523 delims: [char; 2],
524 delim_span: Span,
525 parser: Ident,
526 inline_only: bool,
527}
528
529struct FormatLike {
530 str_parser: Ident,
531 arg_parsers: Vec<ArgParser>,
532 initial: TokenStream,
533 str: StringLit<String>,
534 str_span: Span,
535 exprs: Vec<TokenStream>,
536}
537
538impl FormatLike {
539 fn parse(mut stream: impl Iterator<Item = TokenTree>) -> Result<Self, TokenStream> {
540 use TokenTree as TT;
541
542 let str_parser = get_ident(stream.next())?;
543
544 consume_comma(stream.next())?;
545
546 let arg_parsers = {
547 let group = match stream.next() {
548 Some(TT::Group(group)) if group.delimiter() == Delimiter::Bracket => group,
549 Some(other) => return Err(compile_err(other.span(), "expected a list")),
550 _ => return Err(compile_err(Span::mixed_site(), "expected a list")),
551 };
552
553 let mut arg_parsers = Vec::new();
554
555 let mut stream = group.stream().into_iter();
556
557 loop {
558 static INVALID_ERR: &str = "expected one of '{', '(', '[', or '<'";
559
560 let group = match stream.next() {
561 Some(TT::Group(group)) if group.delimiter() == Delimiter::Parenthesis => group,
562 None => break,
563 Some(other) => return Err(compile_err(other.span(), "expected a tuple")),
564 };
565
566 let mut substream = group.stream().into_iter();
567
568 let (delims, delim_span) = match substream.next() {
569 Some(TT::Literal(literal)) => match literal.to_string().as_str() {
570 "'{'" => (['{', '}'], literal.span()),
571 "'('" => (['(', ')'], literal.span()),
572 "'['" => (['[', ']'], literal.span()),
573 "'<'" => (['<', '>'], literal.span()),
574 _ => return Err(compile_err(literal.span(), INVALID_ERR)),
575 },
576 Some(other) => return Err(compile_err(other.span(), INVALID_ERR)),
577 _ => return Err(compile_err(Span::mixed_site(), INVALID_ERR)),
578 };
579
580 consume_comma(substream.next())?;
581
582 let parser = get_ident(substream.next())?;
583
584 consume_comma(substream.next())?;
585
586 let inline_only = match substream.next() {
587 Some(TT::Ident(ident)) => match ident.to_string().as_str() {
588 "true" => true,
589 "false" => false,
590 _ => {
591 return Err(compile_err(
592 ident.span(),
593 format!("expected a bool, got {ident:?}"),
594 ));
595 }
596 },
597 Some(other) => {
598 return Err(compile_err(
599 other.span(),
600 format!("expected a bool, got {other}"),
601 ));
602 }
603 _ => return Err(compile_err(Span::mixed_site(), "expected a bool")),
604 };
605
606 arg_parsers.push(ArgParser { delims, delim_span, parser, inline_only });
607
608 _ = consume_comma(stream.next());
609 }
610
611 arg_parsers
612 };
613
614 if let Some((lhs, rhs)) = arg_parsers.iter().enumerate().find_map(|(i, lhs)| {
615 arg_parsers.iter().enumerate().find_map(|(j, rhs)| {
616 (i != j)
617 .then(|| (rhs.delims == lhs.delims).then_some((lhs, rhs)))
618 .flatten()
619 })
620 }) {
621 return Err(TokenStream::from_iter([
622 compile_err(lhs.delim_span, "this delimiter"),
623 compile_err(rhs.delim_span, "is the same as this"),
624 ]));
625 }
626
627 consume_comma(stream.next())?;
628
629 let initial = {
630 let mut initial = Vec::new();
631
632 for token in stream.by_ref() {
633 if let TokenTree::Punct(punct) = &token
634 && punct.as_char() == ','
635 {
636 break;
637 }
638
639 initial.push(token);
640 }
641
642 TokenStream::from_iter(initial)
643 };
644
645 let (str, str_span) = match stream.next() {
646 Some(TokenTree::Literal(literal)) => {
647 match litrs::StringLit::parse(literal.to_string()) {
648 Ok(str) => (str, literal.span()),
649 Err(_) => return Err(compile_err(literal.span(), "expected a string literal")),
650 }
651 }
652 Some(other) => return Err(compile_err(other.span(), "expected a string literal")),
653 None => return Err(compile_err(Span::mixed_site(), "expected a string literal")),
654 };
655
656 let exprs = match stream.next() {
657 Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => {
658 let mut exprs = Vec::new();
659
660 let mut tokens = Vec::new();
661
662 for token in stream {
663 if let TokenTree::Punct(punct) = &token
664 && punct.as_char() == ','
665 {
666 if !tokens.is_empty() {
667 exprs.push(TokenStream::from_iter(tokens.drain(..)));
668 }
669 } else {
670 tokens.push(token);
671 }
672 }
673
674 if !tokens.is_empty() {
675 exprs.push(TokenStream::from_iter(tokens));
676 }
677
678 exprs
679 }
680 Some(other) => return Err(compile_err(other.span(), "expected a comma")),
681 None => Vec::new(),
682 };
683
684 Ok(Self {
685 str_parser,
686 arg_parsers,
687 initial,
688 str,
689 str_span,
690 exprs,
691 })
692 }
693}
694
695enum Arg {
696 Str(String, Range<usize>),
697 Positional(usize, Range<usize>, TokenStream),
698 Inlined(usize, TokenStream, TokenStream),
699}
700
701fn recurse_parser(parser: &Ident, stream: impl Iterator<Item = TokenTree>) -> TokenStream {
702 let start = parser.span().start();
703
704 TokenStream::from_iter([
705 TokenTree::Ident(parser.clone()),
706 TokenTree::Punct({
707 let mut punct = Punct::new('!', Spacing::Alone);
708 punct.set_span(start);
709 punct
710 }),
711 TokenTree::Group(Group::new(Delimiter::Parenthesis, stream.collect())),
712 ])
713}
714
715fn consume_comma(value: Option<TokenTree>) -> Result<(), TokenStream> {
716 match value {
717 Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => Ok(()),
718 Some(other) => Err(compile_err(other.span(), "Expected a comma")),
719 _ => Err(compile_err(Span::mixed_site(), "Expected a comma")),
720 }
721}
722
723fn get_ident(value: Option<TokenTree>) -> Result<Ident, TokenStream> {
724 match value {
725 Some(TokenTree::Ident(ident)) => Ok(ident),
726 Some(other) => Err(compile_err(other.span(), "Expected an identifier")),
727 _ => Err(compile_err(Span::mixed_site(), "Expected an identifier")),
728 }
729}
730
731fn extend_str_arg(args: &mut Vec<Arg>, char: char, i: usize) {
732 if let Some(Arg::Str(string, range)) = args.last_mut() {
733 string.push(char);
734 range.end = i + 1;
735 } else {
736 args.push(Arg::Str(String::from(char), i..i + 1))
737 }
738}
739
740fn compile_err(span: Span, msg: impl std::fmt::Display) -> TokenStream {
741 let (start, end) = (span.start(), span.end());
742
743 TokenStream::from_iter([
744 TokenTree::Punct({
745 let mut punct = Punct::new(':', Spacing::Joint);
746 punct.set_span(start);
747 punct
748 }),
749 TokenTree::Punct({
750 let mut punct = Punct::new(':', Spacing::Alone);
751 punct.set_span(start);
752 punct
753 }),
754 TokenTree::Ident(Ident::new("core", start)),
755 TokenTree::Punct({
756 let mut punct = Punct::new(':', Spacing::Joint);
757 punct.set_span(start);
758 punct
759 }),
760 TokenTree::Punct({
761 let mut punct = Punct::new(':', Spacing::Alone);
762 punct.set_span(start);
763 punct
764 }),
765 TokenTree::Ident({
766 let mut ident = Ident::new("compile_error", start);
767 ident.set_span(start);
768 ident
769 }),
770 TokenTree::Punct({
771 let mut punct = Punct::new('!', Spacing::Alone);
772 punct.set_span(start);
773 punct
774 }),
775 TokenTree::Group({
776 let mut group = Group::new(Delimiter::Brace, {
777 TokenStream::from_iter([TokenTree::Literal({
778 let mut string = Literal::string(&msg.to_string());
779 string.set_span(end);
780 string
781 })])
782 });
783 group.set_span(end);
784 group
785 }),
786 ])
787}
788
789type CurrentArg = (
790 usize,
791 usize,
792 Vec<(Range<usize>, bool)>,
793 Option<Range<usize>>,
794);