use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use syn::{
parse::{Parse, ParseStream},
parse2, parse_quote,
token::Comma,
Block, Ident, Stmt,
};
pub fn impl_with_py_raises(input: TokenStream2) -> TokenStream2 {
let withraisesstmt: WithRaisesStmt = match parse2(input) {
Ok(withraisesstmt) => withraisesstmt,
Err(e) => return e.into_compile_error(),
};
expand(withraisesstmt)
}
#[derive(Debug, PartialEq)]
struct WithRaisesStmt {
err: Ident,
block: Block,
}
impl Parse for WithRaisesStmt {
fn parse(input: ParseStream) -> syn::Result<Self> {
let error_example =
"\nCorrect format for with_py_raises is: `Error Type` `Comma: [,]` `{block in braces}`\n\
E.g.: `with_py_raises!(PyTypeError, { addone.call1((\"4\",)) })`";
let err: Ident = input.parse()?;
let _comma: Comma = match input.parse() {
Ok(comma) => comma,
Err(_) => {
return Err(syn::Error::new(
err.span(),
"Expected a comma (`,`) after this:".to_string() + error_example,
))
}
};
let block: Block = match input.parse() {
Ok(block) => block,
Err(error) => {
return Err(syn::Error::new(
error.span(),
"Expected a code block with braces (`{ ... }`) here:".to_string()
+ error_example,
))
}
};
Ok(WithRaisesStmt { err, block })
}
}
fn expand(withraisesstmt: WithRaisesStmt) -> TokenStream2 {
let err = withraisesstmt.err;
let block = withraisesstmt.block;
let expanded: Stmt = parse_quote! {
match #block {
Ok(_) => panic!("No Error"),
Err(error) if error.is_instance_of::<#err>(py) => return (),
Err(_) => panic!("Wrong Error"),
}
};
expanded.into_token_stream()
}
#[cfg(test)]
mod test {
use super::*;
use quote::quote;
#[test]
fn test_expansion() {
let codeblock = parse_quote! {
{addone.call1("4",)}
};
let errortype = parse_quote! {
PyTypeError
};
let invocation = WithRaisesStmt {
err: errortype,
block: codeblock,
};
let expected: TokenStream2 = quote! {
match {
addone.call1("4",)
} {
Ok(_) => panic!("No Error"),
Err(error) if error.is_instance_of::<PyTypeError>(py) => return (),
Err(_) => panic!("Wrong Error"),
}
};
assert_eq!(expand(invocation).to_string(), expected.to_string())
}
#[test]
fn test_parse_input() {
let input: WithRaisesStmt = parse_quote! {
PyTypeError, {
addone.call1("4",)
}
};
let codeblock = parse_quote! {
{addone.call1("4",)}
};
let errortype = parse_quote! {
PyTypeError
};
let expected = WithRaisesStmt {
err: errortype,
block: codeblock,
};
assert_eq!(input, expected);
}
}