-
Notifications
You must be signed in to change notification settings - Fork 3
/
lib.rs
executable file
·82 lines (73 loc) · 2.72 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;
use quote::{ToTokens, TokenStreamExt};
use syn::Expr;
use syn::parse::{self, Parse, ParseStream};
use syn::punctuated::Punctuated;
/// Used exactly like the built-in `assert!` macro. This function has to be a stub whether
/// proc_macro2 is used or not because Rust complains if we try to use a `#[proc_macro]` function
/// as a regular function outside of a procedural macro context (e.g. in a test). The real logic
/// begins in `custom_assert_internal`.
#[proc_macro]
pub fn custom_assert(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
custom_assert_internal(ts.into()).into()
}
fn custom_assert_internal(ts: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let ast: CustomAssert = syn::parse2(ts).unwrap();
let mut ts = proc_macro2::TokenStream::new();
ast.to_tokens(&mut ts);
ts
}
struct CustomAssert {
expr: Expr,
message: Punctuated<Expr, Token![,]>
}
impl Parse for CustomAssert {
fn parse(input: ParseStream) -> parse::Result<Self> {
let expr = input.call(Expr::parse)?; // Required expression
if input.parse::<Token![,]>().is_ok() { // Optional message
let message = input.call(Punctuated::parse_separated_nonempty)?;
Ok(CustomAssert { expr, message })
} else {
Ok(CustomAssert { expr, message: Punctuated::new() })
}
}
}
impl ToTokens for CustomAssert {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let expr = &self.expr;
let message = &self.message;
tokens.append_all(quote!(if !(#expr) { panic!(#message); }));
}
}
#[cfg(test)]
mod tests {
extern crate runtime_macros;
use self::runtime_macros::emulate_macro_expansion_fallible;
use super::custom_assert_internal;
use std::{env, fs};
#[test]
fn code_coverage() {
// This code doesn't check much. Instead, it does macro expansion at run time to let
// tarpaulin measure code coverage for the macro.
let mut path = env::current_dir().unwrap();
path.push("tests");
path.push("tests.rs");
let file = fs::File::open(path).unwrap();
emulate_macro_expansion_fallible(file, "custom_assert", custom_assert_internal).unwrap();
}
#[test]
fn syntax_error() {
// This code makes sure that the given file doesn't compile.
let mut path = env::current_dir().unwrap();
path.push("tests");
path.push("compile-fail");
path.push("syntax_error.rs");
let file = fs::File::open(path).unwrap();
assert!(emulate_macro_expansion_fallible(file, "custom_assert", custom_assert_internal).is_err());
}
}