From 8420e17a0635afbbe862e73dffa4d77d53cfb567 Mon Sep 17 00:00:00 2001 From: jeremydavis519 Date: Wed, 31 Oct 2018 05:47:44 -0500 Subject: [PATCH] Things that were accidentally left out of the last commit --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/custom_assert/src/lib.rs | 15 +++++- src/lib.rs | 78 +++++++++++++++++++++++++++---- 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a2b130..9e4d372 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -924,7 +924,7 @@ dependencies = [ [[package]] name = "runtime-macros" -version = "0.2.0" +version = "0.3.0" dependencies = [ "cargo-tarpaulin 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 693ed3e..92f3239 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-macros" -version = "0.2.0" +version = "0.3.0" authors = ["Jeremy Davis "] license = "MIT OR Apache-2.0" description = "Simulate expansion of procedural macros at run time" diff --git a/examples/custom_assert/src/lib.rs b/examples/custom_assert/src/lib.rs index 85d9b61..78c82c1 100755 --- a/examples/custom_assert/src/lib.rs +++ b/examples/custom_assert/src/lib.rs @@ -54,7 +54,7 @@ impl ToTokens for CustomAssert { #[cfg(test)] mod tests { extern crate runtime_macros; - use self::runtime_macros::emulate_macro_expansion; + use self::runtime_macros::emulate_macro_expansion_fallible; use super::custom_assert_internal; use std::{env, fs}; @@ -66,6 +66,17 @@ mod tests { path.push("tests"); path.push("tests.rs"); let file = fs::File::open(path).unwrap(); - emulate_macro_expansion(file, "custom_assert", custom_assert_internal); + 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()); } } diff --git a/src/lib.rs b/src/lib.rs index cfa8704..5c8c568 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ extern crate syn; use std::fs; use std::io::Read; +use std::panic::{self, AssertUnwindSafe}; /// Parses the given Rust source code file, searching for macro expansions that use `macro_path`. /// Each time it finds one, it calls `proc_macro_fn`, passing it the inner `TokenStream` just as @@ -50,6 +51,13 @@ use std::io::Read; /// Also, this function uses `proc_macro2::TokenStream`, not the standard but partly unstable /// `proc_macro::TokenStream`. You can convert between them using their `into` methods, as shown /// below. +/// +/// # Returns +/// +/// `Ok` on success, or an instance of [`Error`] indicating any error that occurred when trying to +/// read or parse the file. +/// +/// [`Error`]: enum.Error.html /// /// # Example /// @@ -73,26 +81,76 @@ use std::io::Read; /// emulate_macro_expansion(file, "remove", |ts| remove(ts.into()).into()); /// } /// ``` -pub fn emulate_macro_expansion(mut file: fs::File, macro_path: &str, proc_macro_fn: F) +pub fn emulate_macro_expansion_fallible(mut file: fs::File, macro_path: &str, proc_macro_fn: F) + -> Result<(), Error> where F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream { - struct MacroVisitor<'a, F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream> { - macro_path: &'a str, - proc_macro_fn: F + struct MacroVisitor proc_macro2::TokenStream> { + macro_path: syn::Path, + proc_macro_fn: AssertUnwindSafe } - impl<'a, 'ast, F> syn::visit::Visit<'ast> for MacroVisitor<'a, F> + impl<'ast, F> syn::visit::Visit<'ast> for MacroVisitor where F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream { fn visit_macro(&mut self, macro_item: &'ast syn::Macro) { - if macro_item.path == syn::parse_str::(self.macro_path).unwrap() { - (self.proc_macro_fn)(macro_item.tts.clone()); + if macro_item.path == self.macro_path { + (*self.proc_macro_fn)(macro_item.tts.clone()); } } } + let proc_macro_fn = AssertUnwindSafe(proc_macro_fn); + let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); + file.read_to_string(&mut content).map_err(|e| Error::IoError(e))?; - let ast = syn::parse_file(content.as_str()).unwrap(); - syn::visit::visit_file(&mut MacroVisitor:: { macro_path, proc_macro_fn }, &ast); + let ast = AssertUnwindSafe(syn::parse_file(content.as_str()).map_err(|e| Error::ParseError(e))?); + let macro_path: syn::Path = syn::parse_str(macro_path).map_err(|e| Error::ParseError(e))?; + + panic::catch_unwind(|| { + syn::visit::visit_file(&mut MacroVisitor:: { + macro_path, + proc_macro_fn + }, &*ast); + }).map_err(|_| Error::ParseError(syn::parse::Error::new(proc_macro2::Span::call_site(), "macro expansion panicked")))?; + + Ok(()) +} + +/// This type is like [`emulate_macro_expansion_fallible`] but automatically unwraps any errors it +/// encounters. As such, it's deprecated due to being less flexible. +/// +/// [`emulate_macro_expansion_fallible`]: fn.emulate_macro_expansion_fallible.html +#[deprecated] +pub fn emulate_macro_expansion(file: fs::File, macro_path: &str, proc_macro_fn: F) + where F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream { + emulate_macro_expansion_fallible(file, macro_path, proc_macro_fn).unwrap() +} + +/// The error type for [`emulate_macro_expansion_fallible`]. If anything goes wrong during the file +/// loading or macro expansion, this type describes it. +/// +/// [`emulate_macro_expansion_fallible`]: fn.emulate_macro_expansion_fallible.html +#[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<&(std::error::Error+'static)> { + match self { + Error::IoError(e) => e.source(), + Error::ParseError(e) => e.source() + } + } } #[cfg(test)]