Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I provide helpful compiler errors in a procedural macro?

I am designing a custom HTML syntax parser using proc_macro and syn. A sample:

#[derive(Debug)]
struct BlockElement {
    stag: Ident,
    child: Vec<Element>,
    ctag: Ident
}

impl Synom for BlockElement {
     named!(parse -> Self, do_parse!(
         punct!(<) >>
         stag: syn!(Ident) >>
         punct!(>) >>
         child: syn!(ElementList) >>
         punct!(<) >>
         punct!(/) >>
         ctag: syn!(Ident) >>
         punct!(>) >>
         (BlockElement { stag, child: child.inner, ctag })
     ));
 }

Though I know how give out errors using Span after it has been parsed, I am not able to figure how to do it during a parse. It just errors out with failed to parse anything. How to pin-point where parsing failed and give appropriate error?

like image 816
pepsighan Avatar asked Feb 27 '18 09:02

pepsighan


1 Answers

By upgrading your macro to use Syn 0.15 or greater, you get helpful parse error messages with no additional effort.

extern crate proc_macro;
use self::proc_macro::TokenStream;

use syn::parse::{Parse, ParseStream, Result};
use syn::{parse_macro_input, Ident, Token};

// FIXME
type Element = Ident;

struct ElementList {
    inner: Vec<Element>,
}

impl Parse for ElementList {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut list = Vec::new();
        while let Some(element) = input.parse()? {
            list.push(element);
        }
        Ok(ElementList { inner: list })
    }
}

struct BlockElement {
    stag: Ident,
    child: Vec<Element>,
    ctag: Ident
}

impl Parse for BlockElement {
    fn parse(input: ParseStream) -> Result<Self> {
        input.parse::<Token![<]>()?;
        let stag: Ident = input.parse()?;
        input.parse::<Token![>]>()?;
        let child: ElementList = input.parse()?;
        input.parse::<Token![<]>()?;
        input.parse::<Token![/]>()?;
        let ctag: Ident = input.parse()?;
        input.parse::<Token![>]>()?;
        Ok(BlockElement { stag, child: child.inner, ctag })
    }
}

#[proc_macro]
pub fn html_syntax(input: TokenStream) -> TokenStream {
    let _input = parse_macro_input!(input as BlockElement);

    // TODO
    TokenStream::new()
}

Here is an example of an input parse error. Such errors will indicate where the input failed to parse and what token(s) the macro would have expected in that position.

fn main() {
    html_syntax!(<em>syn quote proc_macro2<em>);
}
error: expected `/`
 --> src/main.rs
  |
  |     html_syntax!(<em>syn quote proc_macro2<em>);
  |                                            ^^
like image 99
dtolnay Avatar answered Oct 21 '22 19:10

dtolnay