Skip to content

Commit

Permalink
Things that were accidentally left out of the last commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremydavis519 committed Oct 31, 2018
1 parent 8a6b3a5 commit 8420e17
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "runtime-macros"
version = "0.2.0"
version = "0.3.0"
authors = ["Jeremy Davis <jeremydavis519@gmail.com>"]
license = "MIT OR Apache-2.0"
description = "Simulate expansion of procedural macros at run time"
Expand Down
15 changes: 13 additions & 2 deletions examples/custom_assert/src/lib.rs
Expand Up @@ -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};

Expand All @@ -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());
}
}
78 changes: 68 additions & 10 deletions src/lib.rs
Expand Up @@ -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
Expand All @@ -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
///
Expand All @@ -73,26 +81,76 @@ use std::io::Read;
/// emulate_macro_expansion(file, "remove", |ts| remove(ts.into()).into());
/// }
/// ```
pub fn emulate_macro_expansion<F>(mut file: fs::File, macro_path: &str, proc_macro_fn: F)
pub fn emulate_macro_expansion_fallible<F>(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<F: Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream> {
macro_path: syn::Path,
proc_macro_fn: AssertUnwindSafe<F>
}
impl<'a, 'ast, F> syn::visit::Visit<'ast> for MacroVisitor<'a, F>
impl<'ast, F> syn::visit::Visit<'ast> for MacroVisitor<F>
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::<syn::Path>(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::<F> { 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::<F> {
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<F>(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)]
Expand Down

0 comments on commit 8420e17

Please sign in to comment.