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"> --&gt;</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">  --&gt;</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}