scrub/lib.rs
1#![feature(proc_macro_span)]
2#![feature(proc_macro_quote)]
3
4/*!
5A macro for letting macros absolve themselves of guilt, blaming any and all errors on their callers.
6
7For example, consider a macro that has as its contract that the input must be an expression of a specific type.
8If a caller breaks this contract, a part of the macro body is highlighted as the error cause, even though it *obviously* was the caller's fault.
9
10<style type="text/css">
11.diag-r { color: var(--code-highlight-prelude-val-color); }
12.diag-b { color: var(--code-highlight-prelude-color); }
13</style>
14
15<div class="example-wrap"><pre class="language-text"><code><b><span class="diag-r">error[E0308]</span>: mismatched types</b>
16<b class="diag-b"> --></b> examples/blame.rs:3:7
17<b class="diag-b"> |</b>
18<b class="diag-b">3 |</b> let () = $e;
19<b class="diag-b"> |</b> <b class="diag-r">^^</b> <b class="diag-r">expected `A`, found `()`</b>
20<b class="diag-b">...</b>
21<b class="diag-b">9 |</b> b!(A);
22<b class="diag-b"> |</b> <b class="diag-b">---<b class="diag-b">-</b>-</b>
23<b class="diag-b"> |</b> <b class="diag-b">|</b> <b class="diag-b">|</b>
24<b class="diag-b"> |</b> <b class="diag-b">|</b> <b class="diag-b">this expression has type `A`</b>
25<b class="diag-b"> |</b> <b class="diag-b">in this macro invocation</b>
26</code></pre></div>
27
28Now you can finally tell those callers whose fault it *really* is.
29
30<div class="example-wrap"><pre class="language-text"><code><b><span class="diag-r">error[E0308]</span>: mismatched types</b>
31<b class="diag-b"> --></b> examples/blameless.rs:11:2
32<b class="diag-b"> |</b>
33<b class="diag-b">11 |</b> b!(A);
34<b class="diag-b"> |</b> <b class="diag-r">^^^<b class="diag-b">-</b>^</b>
35<b class="diag-b"> |</b> <b class="diag-r">|</b> <b class="diag-b">|</b>
36<b class="diag-b"> |</b> <b class="diag-r">|</b> <b class="diag-b">this expression has type `A`</b>
37<b class="diag-b"> |</b> <b class="diag-r">expected `A`, found `()`</b>
38</code></pre></div>
39
40Due to using the `proc_macro_span` feature, this crate requires nightly.
41*/
42
43use proc_macro::{Delimiter, Group, Spacing, Span, TokenStream, TokenTree};
44
45/// Hides the contained token tree from diagnostics.
46#[proc_macro]
47pub fn scrub(body: TokenStream) -> TokenStream {
48 let parent = Span::call_site()
49 .parent()
50 .unwrap()
51 .parent()
52 .expect("called outside macro");
53 map_stream(body, |mut t| {
54 if let Some(p) = t.span().parent() {
55 t.set_span(p.located_at(parent).resolved_at(t.span()));
56 }
57 if let TokenTree::Group(g) = t {
58 t = map_group(g, scrub)
59 }
60 t
61 })
62}
63
64fn map_stream(stream: TokenStream, f: impl FnMut(TokenTree) -> TokenTree) -> TokenStream {
65 stream.into_iter().map(f).collect()
66}
67
68fn map_group(g: Group, f: impl FnOnce(TokenStream) -> TokenStream) -> TokenTree {
69 let mut g2 = Group::new(g.delimiter(), f(g.stream()));
70 g2.set_span(g.span());
71 TokenTree::Group(g2)
72}
73
74/// Scrubs all arms of a macro.
75#[proc_macro_attribute]
76pub fn scrubbed(attr: TokenStream, body: TokenStream) -> TokenStream {
77 assert!(attr.is_empty());
78 let mut func: fn(TokenStream) -> TokenStream = scrub_macro_body;
79 map_stream(body, |mut t| {
80 match t {
81 TokenTree::Group(ref g) if g.delimiter() == Delimiter::Parenthesis => {
82 func = add_scrub;
83 }
84 TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => {
85 t = map_group(g, func);
86 }
87 _ => {}
88 }
89 t
90 })
91}
92
93fn scrub_macro_body(body: TokenStream) -> TokenStream {
94 let mut state = 0;
95 map_stream(body, |mut t| {
96 match t {
97 TokenTree::Punct(ref p) if p.spacing() == Spacing::Joint => state = 1,
98 TokenTree::Punct(ref p) if p.as_char() == '>' && state == 1 => state = 2,
99 TokenTree::Group(g) if g.delimiter() == Delimiter::Brace && state == 2 => {
100 t = map_group(g, add_scrub);
101 state = 0;
102 }
103 _ => state = 0,
104 };
105 t
106 })
107}
108
109fn add_scrub(body: TokenStream) -> TokenStream {
110 proc_macro::quote! { ::scrub::scrub! { $body } }
111}