extern crate proc_macro;
extern crate quote;
extern crate syn;
use {
std::{
fs,
io::Read,
panic::{self, AssertUnwindSafe}
},
quote::ToTokens,
syn::{Meta, NestedMeta}
};
pub fn emulate_functionlike_macro_expansion<'a, F>(
mut file: fs::File,
macro_paths_and_proc_macro_fns: &[(&'a str, F)]
) -> Result<(), Error>
where F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream {
struct MacroVisitor<'a, F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream> {
macro_paths_and_proc_macro_fns: AssertUnwindSafe<Vec<(syn::Path, &'a F)>>
}
impl<'a, 'ast, F> syn::visit::Visit<'ast> for MacroVisitor<'a, F>
where F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream {
fn visit_macro(&mut self, macro_item: &'ast syn::Macro) {
for (path, proc_macro_fn) in self.macro_paths_and_proc_macro_fns.iter() {
if macro_item.path == *path {
proc_macro_fn(macro_item.tokens.clone().into());
}
}
}
}
let mut content = String::new();
file.read_to_string(&mut content).map_err(|e| Error::IoError(e))?;
let ast = AssertUnwindSafe(syn::parse_file(content.as_str()).map_err(|e| Error::ParseError(e))?);
let macro_paths_and_proc_macro_fns = AssertUnwindSafe(
macro_paths_and_proc_macro_fns.iter()
.map(|(s, f)| Ok((syn::parse_str(s)?, f)))
.collect::<Result<Vec<(syn::Path, &F)>, _>>()
.map_err(|e| Error::ParseError(e))?
);
panic::catch_unwind(|| {
syn::visit::visit_file(&mut MacroVisitor::<F> {
macro_paths_and_proc_macro_fns
}, &*ast);
}).map_err(|_| Error::ParseError(syn::parse::Error::new(
proc_macro2::Span::call_site().into(), "macro expansion panicked"
)))?;
Ok(())
}
pub fn emulate_derive_macro_expansion<'a, F>(
mut file: fs::File,
macro_paths_and_proc_macro_fns: &[(&'a str, F)]
) -> Result<(), Error>
where F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream {
struct MacroVisitor<'a, F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream> {
macro_paths_and_proc_macro_fns: AssertUnwindSafe<Vec<(syn::Path, &'a F)>>
}
impl<'a, 'ast, F> syn::visit::Visit<'ast> for MacroVisitor<'a, F>
where F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream {
fn visit_item(&mut self, item: &'ast syn::Item) {
macro_rules! visit {
( $($ident:ident),* ) => {
match *item {
$(syn::Item::$ident(ref item) => {
for attr in item.attrs.iter() {
let meta = match attr.parse_meta() {
Ok(Meta::List(list)) => list,
_ => continue
};
let path_ident = match meta.path.get_ident() {
Some(x) => x,
None => continue
};
if path_ident.to_string() != "derive" {
continue;
}
for nested_meta in meta.nested.iter() {
let meta_path = match *nested_meta {
NestedMeta::Meta(Meta::Path(ref path)) => path,
_ => continue
};
for (path, proc_macro_fn) in self.macro_paths_and_proc_macro_fns.iter() {
if meta_path == path {
proc_macro_fn( item.to_token_stream());
}
}
}
}
},)*
_ => {}
}
}
}
visit!(
Const,
Enum,
ExternCrate,
Fn,
ForeignMod,
Impl,
Macro,
Macro2,
Mod,
Static,
Struct,
Trait,
TraitAlias,
Type,
Union,
Use
);
}
}
let mut content = String::new();
file.read_to_string(&mut content).map_err(|e| Error::IoError(e))?;
let ast = AssertUnwindSafe(syn::parse_file(content.as_str()).map_err(|e| Error::ParseError(e))?);
let macro_paths_and_proc_macro_fns = AssertUnwindSafe(
macro_paths_and_proc_macro_fns.iter()
.map(|(s, f)| Ok((syn::parse_str(s)?, f)))
.collect::<Result<Vec<(syn::Path, &F)>, _>>()
.map_err(|e| Error::ParseError(e))?
);
panic::catch_unwind(|| {
syn::visit::visit_file(&mut MacroVisitor::<F> {
macro_paths_and_proc_macro_fns
}, &*ast);
}).map_err(|_| Error::ParseError(syn::parse::Error::new(
proc_macro2::Span::call_site().into(), "macro expansion panicked"
)))?;
Ok(())
}
pub fn emulate_attributelike_macro_expansion<'a, F>(
mut file: fs::File,
macro_paths_and_proc_macro_fns: &[(&'a str, F)]
) -> Result<(), Error>
where F: Fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream {
struct MacroVisitor<'a, F: Fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream> {
macro_paths_and_proc_macro_fns: AssertUnwindSafe<Vec<(syn::Path, &'a F)>>
}
impl<'a, 'ast, F> syn::visit::Visit<'ast> for MacroVisitor<'a, F>
where F: Fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream {
fn visit_item(&mut self, item: &'ast syn::Item) {
macro_rules! visit {
( $($ident:ident),* ) => {
match *item {
$(syn::Item::$ident(ref item) => {
for attr in item.attrs.iter() {
for (path, proc_macro_fn) in self.macro_paths_and_proc_macro_fns.iter() {
if attr.path == *path {
proc_macro_fn(attr.tokens.clone().into(), item.to_token_stream());
}
}
}
},)*
_ => {}
}
}
}
visit!(
Const,
Enum,
ExternCrate,
Fn,
ForeignMod,
Impl,
Macro,
Macro2,
Mod,
Static,
Struct,
Trait,
TraitAlias,
Type,
Union,
Use
);
}
}
let mut content = String::new();
file.read_to_string(&mut content).map_err(|e| Error::IoError(e))?;
let ast = AssertUnwindSafe(syn::parse_file(content.as_str()).map_err(|e| Error::ParseError(e))?);
let macro_paths_and_proc_macro_fns = AssertUnwindSafe(
macro_paths_and_proc_macro_fns.iter()
.map(|(s, f)| Ok((syn::parse_str(s)?, f)))
.collect::<Result<Vec<(syn::Path, &F)>, _>>()
.map_err(|e| Error::ParseError(e))?
);
panic::catch_unwind(|| {
syn::visit::visit_file(&mut MacroVisitor::<F> {
macro_paths_and_proc_macro_fns
}, &*ast);
}).map_err(|_| Error::ParseError(syn::parse::Error::new(
proc_macro2::Span::call_site().into(), "macro expansion panicked"
)))?;
Ok(())
}
#[derive(Debug)]
pub enum Error {
IoError(std::io::Error),
ParseError(syn::parse::Error)
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::IoError(e) => e.fmt(f),
Error::ParseError(e) => e.fmt(f)
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error+'static)> {
match self {
Error::IoError(e) => e.source(),
Error::ParseError(e) => e.source()
}
}
}
#[cfg(test)]
mod tests {
extern crate cargo_tarpaulin;
use self::cargo_tarpaulin::launch_tarpaulin;
use self::cargo_tarpaulin::config::Config;
use std::{env, time};
#[test]
fn proc_macro_coverage() {
{
let mut config = Config::default();
let test_dir = env::current_dir().unwrap().join("examples").join("custom_assert");
config.manifest = test_dir.join("Cargo.toml");
config.test_timeout = time::Duration::from_secs(60);
let (_trace_map, return_code) = launch_tarpaulin(&config, &None).unwrap();
assert_eq!(return_code, 0);
}
{
let mut config = Config::default();
let test_dir = env::current_dir().unwrap().join("examples").join("reference_counting");
config.manifest = test_dir.join("Cargo.toml");
config.test_timeout = time::Duration::from_secs(60);
let (_trace_map, return_code) = launch_tarpaulin(&config, &None).unwrap();
assert_eq!(return_code, 0);
}
}
}