1#![cfg_attr(nightly, feature(proc_macro_span))]
2
3extern crate proc_macro;
7
8use proc_macro2::TokenStream;
9use quote::{ToTokens, quote};
10use syn::punctuated::Punctuated;
11
12type FormatArgs = Punctuated<syn::Expr, syn::token::Comma>;
13
14#[doc(hidden)]
15#[proc_macro]
16pub fn check_impl(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
17 hygiene_bug::fix(check_or_assert_impl(syn::parse_macro_input!(tokens)).into())
18}
19
20mod hygiene_bug;
21mod let_assert;
22
23#[doc(hidden)]
24#[proc_macro]
25pub fn let_assert_impl(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
26 hygiene_bug::fix(let_assert::let_assert_impl(syn::parse_macro_input!(tokens)).into())
27}
28
29fn check_or_assert_impl(args: Args) -> TokenStream {
31 match args.expr {
32 syn::Expr::Binary(expr) => check_binary_op(args.crate_name, args.macro_name, expr, args.format_args),
33 syn::Expr::Let(expr) => check_let_expr(args.crate_name, args.macro_name, expr, args.format_args),
34 expr => check_bool_expr(args.crate_name, args.macro_name, expr, args.format_args),
35 }
36}
37
38fn check_binary_op(crate_name: syn::Path, macro_name: syn::Expr, expr: syn::ExprBinary, format_args: Option<FormatArgs>) -> TokenStream {
39 match expr.op {
40 syn::BinOp::Eq(_) => (),
41 syn::BinOp::Lt(_) => (),
42 syn::BinOp::Le(_) => (),
43 syn::BinOp::Ne(_) => (),
44 syn::BinOp::Ge(_) => (),
45 syn::BinOp::Gt(_) => (),
46 _ => return check_bool_expr(crate_name, macro_name, syn::Expr::Binary(expr), format_args),
47 };
48
49 let syn::ExprBinary { left, right, op, .. } = &expr;
50 let mut fragments = Fragments::new();
51 let left_expr = expression_to_string(&crate_name, left.to_token_stream(), &mut fragments);
52 let right_expr = expression_to_string(&crate_name, right.to_token_stream(), &mut fragments);
53 let op_str = tokens_to_string(op.to_token_stream(), &mut fragments);
54
55 let custom_msg = match format_args {
56 Some(x) => quote!(Some(format_args!(#x))),
57 None => quote!(None),
58 };
59
60 quote! {
61 match (&(#left), &(#right)) {
62 (left, right) if !(left #op right) => {
63 use #crate_name::__assert2_impl::maybe_debug::{IsDebug, IsMaybeNotDebug};
64 let left = (&&#crate_name::__assert2_impl::maybe_debug::Wrap(left)).__assert2_maybe_debug().wrap(left);
65 let right = (&&#crate_name::__assert2_impl::maybe_debug::Wrap(right)).__assert2_maybe_debug().wrap(right);
66 #crate_name::__assert2_impl::print::FailedCheck {
67 macro_name: #macro_name,
68 file: file!(),
69 line: line!(),
70 column: column!(),
71 custom_msg: #custom_msg,
72 expression: #crate_name::__assert2_impl::print::BinaryOp {
73 left: &left,
74 right: &right,
75 operator: #op_str,
76 left_expr: #left_expr,
77 right_expr: #right_expr,
78 },
79 fragments: #fragments,
80 }.print();
81 Err(())
82 }
83 _ => Ok(()),
84 }
85 }
86}
87
88fn check_bool_expr(crate_name: syn::Path, macro_name: syn::Expr, expr: syn::Expr, format_args: Option<FormatArgs>) -> TokenStream {
89 let mut fragments = Fragments::new();
90 let expr_str = expression_to_string(&crate_name, expr.to_token_stream(), &mut fragments);
91
92 let custom_msg = match format_args {
93 Some(x) => quote!(Some(format_args!(#x))),
94 None => quote!(None),
95 };
96
97 quote! {
98 match #expr {
99 false => {
100 #crate_name::__assert2_impl::print::FailedCheck {
101 macro_name: #macro_name,
102 file: file!(),
103 line: line!(),
104 column: column!(),
105 custom_msg: #custom_msg,
106 expression: #crate_name::__assert2_impl::print::BooleanExpr {
107 expression: #expr_str,
108 },
109 fragments: #fragments,
110 }.print();
111 Err(())
112 }
113 true => Ok(()),
114 }
115 }
116}
117
118fn check_let_expr(crate_name: syn::Path, macro_name: syn::Expr, expr: syn::ExprLet, format_args: Option<FormatArgs>) -> TokenStream {
119 let syn::ExprLet {
120 pat,
121 expr,
122 ..
123 } = expr;
124
125 let mut fragments = Fragments::new();
126 let pat_str = tokens_to_string(pat.to_token_stream(), &mut fragments);
127 let expr_str = expression_to_string(&crate_name, expr.to_token_stream(), &mut fragments);
128
129 let custom_msg = match format_args {
130 Some(x) => quote!(Some(format_args!(#x))),
131 None => quote!(None),
132 };
133
134 quote! {
135 match &(#expr) {
136 #pat => Ok(()),
137 value => {
138 use #crate_name::__assert2_impl::maybe_debug::{IsDebug, IsMaybeNotDebug};
139 let value = (&&#crate_name::__assert2_impl::maybe_debug::Wrap(value)).__assert2_maybe_debug().wrap(value);
140 #crate_name::__assert2_impl::print::FailedCheck {
141 macro_name: #macro_name,
142 file: file!(),
143 line: line!(),
144 column: column!(),
145 custom_msg: #custom_msg,
146 expression: #crate_name::__assert2_impl::print::MatchExpr {
147 print_let: true,
148 value: &value,
149 pattern: #pat_str,
150 expression: #expr_str,
151 },
152 fragments: #fragments,
153 }.print();
154 Err(())
155 }
156 }
157 }
158}
159
160fn tokens_to_string(ts: TokenStream, fragments: &mut Fragments) -> TokenStream {
161 #[cfg(nightly)]
162 {
163 use syn::spanned::Spanned;
164 find_macro_fragments(ts.clone(), fragments);
165 if let Some(s) = ts.span().unwrap().source_text() {
166 return quote!(#s);
167 }
168 }
169
170 let _ = fragments;
171
172 let tokens = ts.to_string();
173 quote!(#tokens)
174}
175
176fn expression_to_string(crate_name: &syn::Path, ts: TokenStream, fragments: &mut Fragments) -> TokenStream {
177 #[cfg(nightly)]
178 {
179 use syn::spanned::Spanned;
180 find_macro_fragments(ts.clone(), fragments);
181 if let Some(s) = ts.span().unwrap().source_text() {
182 return quote!(#s);
183 }
184 }
185
186 let _ = fragments;
187
188 quote!(#crate_name::__assert2_stringify!(#ts))
189}
190
191#[cfg(nightly)]
192fn find_macro_fragments(ts: TokenStream, f: &mut Fragments) {
193 use syn::spanned::Spanned;
194 use proc_macro2::{Delimiter, TokenTree};
195
196 for token in ts {
197 if let TokenTree::Group(g) = token {
198 if g.delimiter() == Delimiter::None {
199 let name = g.span().unwrap().source_text().unwrap_or_else(|| "???".into());
200 let contents = g.stream();
201 let expansion = contents.span().unwrap().source_text().unwrap_or_else(|| contents.to_string());
202 if name != expansion {
203 let entry = (name, expansion);
204 if !f.list.contains(&entry) {
205 f.list.push(entry);
206 }
207 }
208 }
209 find_macro_fragments(g.stream(), f);
210 }
211 }
212}
213
214struct Fragments {
215 list: Vec<(String, String)>,
216}
217
218impl Fragments {
219 fn new() -> Self {
220 Self { list: Vec::new() }
221 }
222}
223
224impl quote::ToTokens for Fragments {
225 fn to_tokens(&self, tokens: &mut TokenStream) {
226 let mut t = TokenStream::new();
227 for (name, expansion) in &self.list {
228 t.extend(quote!((#name, #expansion),));
229 }
230 tokens.extend(quote!(&[#t]));
231 }
232}
233
234struct Args {
235 crate_name: syn::Path,
236 macro_name: syn::Expr,
237 expr: syn::Expr,
238 format_args: Option<FormatArgs>,
239}
240
241impl syn::parse::Parse for Args {
242 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
243 let crate_name = input.parse()?;
244 let _comma: syn::token::Comma = input.parse()?;
245 let macro_name = input.parse()?;
246 let _comma: syn::token::Comma = input.parse()?;
247 let expr = input.parse()?;
248 let format_args = if input.is_empty() {
249 FormatArgs::new()
250 } else {
251 input.parse::<syn::token::Comma>()?;
252 FormatArgs::parse_terminated(input)?
253 };
254
255 let format_args = Some(format_args).filter(|x| !x.is_empty());
256 Ok(Self {
257 crate_name,
258 macro_name,
259 expr,
260 format_args,
261 })
262 }
263}