extern crate proc_macro;
extern crate quote;
extern crate syn;
use {
quote::ToTokens,
std::{
fs,
io::Read,
panic::{self, AssertUnwindSafe},
},
};
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.meta {
syn::Meta::List(list) => list,
_ => continue
};
match meta.path.get_ident() {
Some(x) => {
if x != "derive" {
continue;
}
},
None => continue
}
match meta.parse_nested_meta(|meta| {
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());
}
}
Ok(())
}) {
Ok(_) => {},
Err(err) => panic!("Error parsing nested meta: {}", err),
};
}
},)*
_ => {}
}
}
}
visit!(
Const,
Enum,
ExternCrate,
Fn,
ForeignMod,
Impl,
Macro,
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() {
let (path, args) = match &attr.meta {
syn::Meta::Path(path) => (path, proc_macro2::TokenStream::new()),
syn::Meta::List(list) => (&list.path, list.tokens.clone().into()),
_ => continue
};
for (proc_macro_path, proc_macro_fn) in self.macro_paths_and_proc_macro_fns.iter() {
if path == proc_macro_path {
proc_macro_fn(args.clone(), item.to_token_stream());
}
}
}
},)*
_ => {}
}
}
}
visit!(
Const,
Enum,
ExternCrate,
Fn,
ForeignMod,
Impl,
Macro,
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::config::Config;
use self::cargo_tarpaulin::launch_tarpaulin;
use std::panic;
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.set_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.set_manifest(test_dir.join("Cargo.toml"));
config.test_timeout = time::Duration::from_secs(60);
let (_trace_map, return_code) = match launch_tarpaulin(&config, &None) {
Ok(ret) => ret,
Err(err) => panic!("{}", err),
};
assert_eq!(return_code, 0);
}
}
}