Implement Display for structs

This commit is contained in:
David Tolnay 2019-10-09 08:25:18 -07:00
parent 8e866cde57
commit 63ba03bacb
No known key found for this signature in database
GPG key ID: F9BA143B95FF6D82
3 changed files with 99 additions and 8 deletions

View file

@ -1,5 +1,54 @@
use syn::parse::Nothing;
use syn::{Field, Result};
use proc_macro2::{TokenStream, TokenTree};
use quote::{format_ident, quote, ToTokens};
use std::iter::once;
use syn::parse::{Nothing, Parse, ParseStream};
use syn::{Attribute, Error, Field, Ident, Index, LitInt, LitStr, Result, Token};
pub struct Display {
pub fmt: LitStr,
pub args: TokenStream,
}
impl Parse for Display {
fn parse(input: ParseStream) -> Result<Self> {
let fmt: LitStr = input.parse()?;
let mut args = TokenStream::new();
let mut last_is_comma = false;
while !input.is_empty() {
if last_is_comma && input.peek(Token![.]) {
if input.peek2(Ident) {
input.parse::<Token![.]>()?;
last_is_comma = false;
continue;
}
if input.peek2(LitInt) {
input.parse::<Token![.]>()?;
let int: Index = input.parse()?;
let ident = format_ident!("_{}", int.index, span = int.span);
args.extend(once(TokenTree::Ident(ident)));
last_is_comma = false;
continue;
}
}
last_is_comma = input.peek(Token![,]);
let token: TokenTree = input.parse()?;
args.extend(once(token));
}
Ok(Display { fmt, args })
}
}
impl ToTokens for Display {
fn to_tokens(&self, tokens: &mut TokenStream) {
let fmt = &self.fmt;
let args = &self.args;
tokens.extend(quote! {
write!(formatter, #fmt #args)
});
}
}
pub fn is_source(field: &Field) -> Result<bool> {
for attr in &field.attrs {
@ -10,3 +59,21 @@ pub fn is_source(field: &Field) -> Result<bool> {
}
Ok(false)
}
pub fn display(attrs: &[Attribute]) -> Result<Option<Display>> {
let mut display = None;
for attr in attrs {
if attr.path.is_ident("error") {
if display.is_some() {
return Err(Error::new_spanned(
attr,
"only one #[error(...)] attribute is allowed",
));
}
display = Some(attr.parse_args()?);
}
}
Ok(display)
}

View file

@ -1,6 +1,6 @@
use crate::attr;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use quote::{format_ident, quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{
Data, DataEnum, DataStruct, DeriveInput, Error, Field, Fields, Ident, Index, Member, Result,
@ -9,8 +9,8 @@ use syn::{
pub fn derive(input: &DeriveInput) -> Result<TokenStream> {
match &input.data {
Data::Struct(data) => struct_error(input, data),
Data::Enum(data) => enum_error(input, data),
Data::Struct(data) => impl_struct(input, data),
Data::Enum(data) => impl_enum(input, data),
Data::Union(_) => Err(Error::new_spanned(
input,
"union as errors are not supported",
@ -18,7 +18,7 @@ pub fn derive(input: &DeriveInput) -> Result<TokenStream> {
}
}
fn struct_error(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> {
fn impl_struct(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> {
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
@ -52,15 +52,39 @@ fn struct_error(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> {
}
});
let display = attr::display(&input.attrs)?.map(|display| {
let pat = match &data.fields {
Fields::Named(fields) => {
let var = fields.named.iter().map(|field| &field.ident);
quote!(Self { #(#var),* })
}
Fields::Unnamed(fields) => {
let var = (0..fields.unnamed.len()).map(|i| format_ident!("_{}", i));
quote!(Self(#(#var),*))
}
Fields::Unit => quote!(_),
};
quote! {
impl #impl_generics std::fmt::Display for #ident #ty_generics #where_clause {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
#[allow(unused_variables)]
let #pat = self;
#display
}
}
}
});
Ok(quote! {
impl #impl_generics std::error::Error for #ident #ty_generics #where_clause {
#source_method
#backtrace_method
}
#display
})
}
fn enum_error(input: &DeriveInput, data: &DataEnum) -> Result<TokenStream> {
fn impl_enum(input: &DeriveInput, data: &DataEnum) -> Result<TokenStream> {
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

View file

@ -6,7 +6,7 @@ mod expand;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Error, attributes(source))]
#[proc_macro_derive(Error, attributes(error, source))]
pub fn derive_error(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand::derive(&input)