// Copyright (c) 2020 Google LLC All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. use { proc_macro2::{Span, TokenStream}, quote::ToTokens, std::cell::RefCell, }; /// A type for collecting procedural macro errors. #[derive(Default)] pub struct Errors { errors: RefCell>, } /// Produce functions to expect particular literals in `syn::Expr` macro_rules! expect_lit_fn { ($(($fn_name:ident, $syn_type:ident, $variant:ident, $lit_name:literal),)*) => { $( pub fn $fn_name<'a>(&self, e: &'a syn::Expr) -> Option<&'a syn::$syn_type> { if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::$variant(inner), .. }) = e { Some(inner) } else { self.unexpected_lit($lit_name, e); None } } )* } } /// Produce functions to expect particular variants of `syn::Meta` macro_rules! expect_meta_fn { ($(($fn_name:ident, $syn_type:ident, $variant:ident, $meta_name:literal),)*) => { $( pub fn $fn_name<'a>(&self, meta: &'a syn::Meta) -> Option<&'a syn::$syn_type> { if let syn::Meta::$variant(inner) = meta { Some(inner) } else { self.unexpected_meta($meta_name, meta); None } } )* } } impl Errors { /// Issue an error like: /// /// Duplicate foo attribute /// First foo attribute here pub fn duplicate_attrs( &self, attr_kind: &str, first: &impl syn::spanned::Spanned, second: &impl syn::spanned::Spanned, ) { self.duplicate_attrs_inner(attr_kind, first.span(), second.span()) } fn duplicate_attrs_inner(&self, attr_kind: &str, first: Span, second: Span) { self.err_span(second, &["Duplicate ", attr_kind, " attribute"].concat()); self.err_span(first, &["First ", attr_kind, " attribute here"].concat()); } expect_lit_fn![ (expect_lit_str, LitStr, Str, "string"), (expect_lit_char, LitChar, Char, "character"), (expect_lit_int, LitInt, Int, "integer"), ]; expect_meta_fn![ (expect_meta_word, Path, Path, "path"), (expect_meta_list, MetaList, List, "list"), (expect_meta_name_value, MetaNameValue, NameValue, "name-value pair"), ]; fn unexpected_lit(&self, expected: &str, found: &syn::Expr) { fn lit_kind(lit: &syn::Lit) -> &'static str { use syn::Lit::{Bool, Byte, ByteStr, Char, Float, Int, Str, Verbatim}; match lit { Str(_) => "string", ByteStr(_) => "bytestring", Byte(_) => "byte", Char(_) => "character", Int(_) => "integer", Float(_) => "float", Bool(_) => "boolean", Verbatim(_) => "unknown (possibly extra-large integer)", _ => "unknown literal kind", } } if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = found { self.err( found, &["Expected ", expected, " literal, found ", lit_kind(lit), " literal"].concat(), ) } else { self.err( found, &["Expected ", expected, " literal, found non-literal expression."].concat(), ) } } fn unexpected_meta(&self, expected: &str, found: &syn::Meta) { fn meta_kind(meta: &syn::Meta) -> &'static str { use syn::Meta::{List, NameValue, Path}; match meta { Path(_) => "path", List(_) => "list", NameValue(_) => "name-value pair", } } self.err( found, &["Expected ", expected, " attribute, found ", meta_kind(found), " attribute"].concat(), ) } /// Issue an error relating to a particular `Spanned` structure. pub fn err(&self, spanned: &impl syn::spanned::Spanned, msg: &str) { self.err_span(spanned.span(), msg); } /// Issue an error relating to a particular `Span`. pub fn err_span(&self, span: Span, msg: &str) { self.push(syn::Error::new(span, msg)); } /// Issue an error spanning over the given syntax tree node. pub fn err_span_tokens(&self, tokens: T, msg: &str) { self.push(syn::Error::new_spanned(tokens, msg)); } /// Push a `syn::Error` onto the list of errors to issue. pub fn push(&self, err: syn::Error) { self.errors.borrow_mut().push(err); } /// Convert a `syn::Result` to an `Option`, logging the error if present. pub fn ok(&self, r: syn::Result) -> Option { match r { Ok(v) => Some(v), Err(e) => { self.push(e); None } } } } impl ToTokens for Errors { /// Convert the errors into tokens that, when emit, will cause /// the user of the macro to receive compiler errors. fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(self.errors.borrow().iter().map(|e| e.to_compile_error())); } }