Make syn optional

This commit is contained in:
CreepySkeleton 2020-07-05 18:37:41 +03:00
parent d99e966694
commit f7bbbe3e5e
8 changed files with 248 additions and 102 deletions

View File

@ -22,9 +22,13 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
quote = "1"
proc-macro2 = "1"
syn = { version = "1", default-features = false, features = ["derive", "parsing", "proc-macro", "printing"] }
proc-macro-error-attr = { path = "./proc-macro-error-attr", version = "=1.0.3"}
[dependencies.syn]
version = "1"
optional = true
default-features = false
[dev-dependencies]
test-crate = { path = "./test-crate" }
proc-macro-hack-test = { path = "./test-crate/proc-macro-hack-test" }
@ -34,3 +38,7 @@ serde_derive = "=1.0.107" # DO NOT BUMP
[build-dependencies]
version_check = "0.9"
[features]
default = ["syn-error"]
syn-error = ["syn"]

View File

@ -18,9 +18,6 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
quote = "1"
proc-macro2 = "1"
syn-mid = "0.5"
# "derive" is for `Attribute`, "parsing" is for `Parse`
syn = { version = "1", default-features = false, features = ["derive", "parsing", "proc-macro", "printing"] }
[build-dependencies]
version_check = "0.9"

View File

@ -3,37 +3,59 @@
extern crate proc_macro;
use crate::parse::parse_input;
use crate::parse::Attribute;
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use std::iter::FromIterator;
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
Attribute, Token,
};
use syn_mid::{Block, ItemFn};
use proc_macro2::{Literal, Span, TokenStream as TokenStream2, TokenTree};
use quote::{quote, quote_spanned};
use self::Setting::*;
use crate::settings::{Setting::*, *};
mod parse;
mod settings;
type Result<T> = std::result::Result<T, Error>;
struct Error {
span: Span,
message: String,
}
impl Error {
fn new(span: Span, message: String) -> Self {
Error { span, message }
}
fn into_compile_error(self) -> TokenStream2 {
let mut message = Literal::string(&self.message);
message.set_span(self.span);
quote_spanned!(self.span=> compile_error!{#message})
}
}
#[proc_macro_attribute]
pub fn proc_macro_error(attr: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);
let mut settings = match syn::parse::<Settings>(attr) {
Ok(settings) => settings,
Err(err) => {
let err = err.to_compile_error();
return quote!(#input #err).into();
}
};
match impl_proc_macro_error(attr.into(), input.clone().into()) {
Ok(ts) => ts,
Err(e) => {
let error = e.into_compile_error();
let input = TokenStream2::from(input);
let is_proc_macro = is_proc_macro(&input.attrs);
quote!(#input #error).into()
}
}
}
fn impl_proc_macro_error(attr: TokenStream2, input: TokenStream2) -> Result<TokenStream> {
let (attrs, signature, body) = parse_input(input)?;
let mut settings = parse_settings(attr)?;
let is_proc_macro = is_proc_macro(&attrs);
if is_proc_macro {
settings.set(AssertUnwindSafe);
}
if detect_proc_macro_hack(&input.attrs) {
if detect_proc_macro_hack(&attrs) {
settings.set(ProcMacroHack);
}
@ -42,80 +64,27 @@ pub fn proc_macro_error(attr: TokenStream, input: TokenStream) -> TokenStream {
}
if !(settings.is_set(AllowNotMacro) || is_proc_macro) {
return quote!(
#input
compile_error!(
"#[proc_macro_error] attribute can be used only with a proc-macro\n\n \
= hint: if you are really sure that #[proc_macro_error] should be applied \
to this exact function use #[proc_macro_error(allow_not_macro)]\n");
)
.into();
return Err(Error::new(
Span::call_site(),
"#[proc_macro_error] attribute can be used only with procedural macros\n\n \
= hint: if you are really sure that #[proc_macro_error] should be applied \
to this exact function use, #[proc_macro_error(allow_not_macro)]\n"
.into(),
));
}
let ItemFn {
attrs,
vis,
sig,
block,
} = input;
let body = gen_body(body, settings);
let body = gen_body(*block, settings);
quote!(
let res = quote! {
#(#attrs)*
#vis
#sig
#(#signature)*
{ #body }
)
.into()
}
#[derive(PartialEq)]
enum Setting {
AssertUnwindSafe,
AllowNotMacro,
ProcMacroHack,
}
impl Parse for Setting {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
match &*ident.to_string() {
"assert_unwind_safe" => Ok(AssertUnwindSafe),
"allow_not_macro" => Ok(AllowNotMacro),
"proc_macro_hack" => Ok(ProcMacroHack),
_ => Err(syn::Error::new(
ident.span(),
format!(
"unknown setting `{}`, expected one of \
`assert_unwind_safe`, `allow_not_macro`, `proc_macro_hack`",
ident
),
)),
}
}
}
struct Settings(Vec<Setting>);
impl Parse for Settings {
fn parse(input: ParseStream) -> syn::Result<Self> {
let punct = Punctuated::<Setting, Token![,]>::parse_terminated(input)?;
Ok(Settings(Vec::from_iter(punct)))
}
}
impl Settings {
fn is_set(&self, setting: Setting) -> bool {
self.0.iter().any(|s| *s == setting)
}
fn set(&mut self, setting: Setting) {
self.0.push(setting)
}
};
Ok(res.into())
}
#[cfg(not(always_assert_unwind))]
fn gen_body(block: Block, settings: Settings) -> proc_macro2::TokenStream {
fn gen_body(block: TokenTree, settings: Settings) -> proc_macro2::TokenStream {
let is_proc_macro_hack = settings.is_set(ProcMacroHack);
let closure = if settings.is_set(AssertUnwindSafe) {
quote!(::std::panic::AssertUnwindSafe(|| #block ))
@ -131,7 +100,7 @@ fn gen_body(block: Block, settings: Settings) -> proc_macro2::TokenStream {
// Considering this is the closure's return type the unwind safety check would fail
// for virtually every closure possible, the check is meaningless.
#[cfg(always_assert_unwind)]
fn gen_body(block: Block, settings: Settings) -> proc_macro2::TokenStream {
fn gen_body(block: TokenTree, settings: Settings) -> proc_macro2::TokenStream {
let is_proc_macro_hack = settings.is_set(ProcMacroHack);
let closure = quote!(::std::panic::AssertUnwindSafe(|| #block ));
quote!( ::proc_macro_error::entry_point(#closure, #is_proc_macro_hack) )
@ -140,13 +109,13 @@ fn gen_body(block: Block, settings: Settings) -> proc_macro2::TokenStream {
fn detect_proc_macro_hack(attrs: &[Attribute]) -> bool {
attrs
.iter()
.any(|attr| attr.path.is_ident("proc_macro_hack"))
.any(|attr| attr.path_is_ident("proc_macro_hack"))
}
fn is_proc_macro(attrs: &[Attribute]) -> bool {
attrs.iter().any(|attr| {
attr.path.is_ident("proc_macro")
|| attr.path.is_ident("proc_macro_derive")
|| attr.path.is_ident("proc_macro_attribute")
attr.path_is_ident("proc_macro")
|| attr.path_is_ident("proc_macro_derive")
|| attr.path_is_ident("proc_macro_attribute")
})
}

View File

@ -0,0 +1,89 @@
use crate::{Error, Result};
use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
use quote::ToTokens;
use std::iter::Peekable;
pub(crate) fn parse_input(
input: TokenStream,
) -> Result<(Vec<Attribute>, Vec<TokenTree>, TokenTree)> {
let mut input = input.into_iter().peekable();
let mut attrs = Vec::new();
while let Some(attr) = parse_next_attr(&mut input)? {
attrs.push(attr);
}
let sig = parse_signature(&mut input);
let body = input.next().ok_or_else(|| {
Error::new(
Span::call_site(),
"`#[proc_macro_error]` can be applied only to functions".to_string(),
)
})?;
Ok((attrs, sig, body))
}
fn parse_next_attr(
input: &mut Peekable<impl Iterator<Item = TokenTree>>,
) -> Result<Option<Attribute>> {
let shebang = match input.peek() {
Some(TokenTree::Punct(punct)) if punct.as_char() == '#' => input.next().unwrap(),
_ => return Ok(None),
};
let group = match input.peek() {
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Bracket => {
let res = group.clone();
input.next();
res
}
other => {
let span = other.map_or(Span::call_site(), |tt| tt.span());
return Err(Error::new(span, "expected `[`".to_string()));
}
};
let path = match group.stream().into_iter().next() {
Some(TokenTree::Ident(ident)) => Some(ident),
_ => None,
};
Ok(Some(Attribute {
shebang,
group: TokenTree::Group(group),
path,
}))
}
fn parse_signature(input: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Vec<TokenTree> {
let mut sig = Vec::new();
loop {
match input.peek() {
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => {
return sig;
}
None => return sig,
_ => sig.push(input.next().unwrap()),
}
}
}
pub(crate) struct Attribute {
pub(crate) shebang: TokenTree,
pub(crate) group: TokenTree,
pub(crate) path: Option<Ident>,
}
impl Attribute {
pub(crate) fn path_is_ident(&self, ident: &str) -> bool {
self.path.as_ref().map_or(false, |p| *p == ident)
}
}
impl ToTokens for Attribute {
fn to_tokens(&self, ts: &mut TokenStream) {
self.shebang.to_tokens(ts);
self.group.to_tokens(ts);
}
}

View File

@ -0,0 +1,72 @@
use crate::{Error, Result};
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
macro_rules! decl_settings {
($($val:expr => $variant:ident),+ $(,)*) => {
#[derive(PartialEq)]
pub(crate) enum Setting {
$($variant),*
}
fn ident_to_setting(ident: Ident) -> Result<Setting> {
match &*ident.to_string() {
$($val => Ok(Setting::$variant),)*
_ => {
let possible_vals = [$($val),*]
.iter()
.map(|v| format!("`{}`", v))
.collect::<Vec<_>>()
.join(", ");
Err(Error::new(
ident.span(),
format!("unknown setting `{}`, expected one of {}", ident, possible_vals)))
}
}
}
};
}
decl_settings! {
"assert_unwind_safe" => AssertUnwindSafe,
"allow_not_macro" => AllowNotMacro,
"proc_macro_hack" => ProcMacroHack,
}
pub(crate) fn parse_settings(input: TokenStream) -> Result<Settings> {
let mut input = input.into_iter();
let mut res = Settings(Vec::new());
loop {
match input.next() {
Some(TokenTree::Ident(ident)) => {
res.0.push(ident_to_setting(ident)?);
}
None => return Ok(res),
other => {
let span = other.map_or(Span::call_site(), |tt| tt.span());
return Err(Error::new(span, "expected identifier".to_string()));
}
}
match input.next() {
Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => {}
None => return Ok(res),
other => {
let span = other.map_or(Span::call_site(), |tt| tt.span());
return Err(Error::new(span, "expected `,`".to_string()));
}
}
}
}
pub(crate) struct Settings(Vec<Setting>);
impl Settings {
pub(crate) fn is_set(&self, setting: Setting) -> bool {
self.0.iter().any(|s| *s == setting)
}
pub(crate) fn set(&mut self, setting: Setting) {
self.0.push(setting)
}
}

View File

@ -181,7 +181,8 @@ impl ToTokens for Diagnostic {
Cow::Owned(message)
};
let msg = syn::LitStr::new(&*message, end);
let mut msg = proc_macro2::Literal::string(&message);
msg.set_span(end);
let group = quote_spanned!(end=> { #msg } );
quote_spanned!(start=> compile_error!#group)
}
@ -216,6 +217,7 @@ impl SuggestionKind {
}
}
#[cfg(feature = "syn-error")]
impl From<syn::Error> for Diagnostic {
fn from(err: syn::Error) -> Self {
use proc_macro2::{Delimiter, TokenTree};

View File

@ -13,4 +13,11 @@ proc-macro = true
proc-macro-error = { path = "../" }
quote = "1"
proc-macro2 = "1"
syn = { version = "1", default-features = false, features = ["derive", "parsing", "proc-macro", "printing"] }
[dependencies.syn]
version = "1"
optional = true
default-features = false
[features]
syn-error = ["syn"]

View File

@ -13,6 +13,7 @@ use syn::{parse_macro_input, spanned::Spanned};
#[proc_macro]
#[proc_macro_error]
#[cfg(feature = "syn-error")]
pub fn abort_from(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let span = input.into_iter().next().unwrap().span();
abort!(
@ -145,7 +146,7 @@ pub fn emit_notes(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
#[proc_macro]
#[proc_macro_error]
pub fn option_ext(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let none: Option<syn::Error> = None;
let none: Option<Diagnostic> = None;
none.expect_or_abort("Option::expect_or_abort() test");
quote!().into()
}
@ -154,7 +155,7 @@ pub fn option_ext(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
#[proc_macro_error]
pub fn result_unwrap_or_abort(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let span = input.into_iter().next().unwrap().span();
let err = syn::Error::new(span.into(), "Result::unwrap_or_abort() test");
let err = Diagnostic::spanned(span.into(), Level::Error, "Result::unwrap_or_abort() test");
let res: Result<(), _> = Err(err);
res.unwrap_or_abort();
quote!().into()
@ -164,7 +165,7 @@ pub fn result_unwrap_or_abort(input: proc_macro::TokenStream) -> proc_macro::Tok
#[proc_macro_error]
pub fn result_expect_or_abort(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let span = input.into_iter().next().unwrap().span();
let err = syn::Error::new(span.into(), "Result::expect_or_abort() test");
let err = Diagnostic::spanned(span.into(), Level::Error, "Result::expect_or_abort() test");
let res: Result<(), _> = Err(err);
res.expect_or_abort("BOOM");
quote!().into()
@ -228,11 +229,12 @@ pub fn multiple_tokens(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let input = proc_macro2::TokenStream::from(input);
Err(syn::Error::new_spanned(input, "...")).unwrap_or_abort()
abort!(input, "...");
}
#[proc_macro]
#[proc_macro_error]
#[cfg(feature = "syn-error")]
pub fn to_tokens_span(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ty = parse_macro_input!(input as syn::Type);
emit_error!(ty, "whole type");