@@ -75,13 +75,13 @@ fn add_meta_field(fields: &mut Fields) -> Result<(), syn::Error> {
7575/// - `#[error(from_sources)]`: Creates `From` impls for the `source` types of all variants. Will fail to compile if multiple sources have the same type.
7676/// - `#[error(std_sources)]`: Defaults all sources to be std errors instead of stack errors.
7777/// - on enum variants and structs:
78- /// - `#[display(".." )]`: Sets the display formatting. You can refer to named fields by their names, and to tuple fields by `_0`, `_1` etc.
78+ /// - `#[error("format {field}: {}", a + b )]`: Sets the display formatting. You can refer to named fields by their names, and to tuple fields by `_0`, `_1` etc.
7979/// - `#[error(transparent)]`: Directly forwards the display implementation to the error source, and omits the outer error in the source chain when reporting errors.
8080/// - on fields:
8181/// - `#[error(source)]`: Sets a field as the source of this error. If a field is named `source` this is applied implicitly and not needed.
8282/// - `#[error(from)]`: Creates a `From` impl for the field's type to the error type.
8383/// - `#[error(std_err)]`: Marks the error as a `std` error. Without this attribute, errors are expected to implement `StackError`. Only applicable to source fields.
84- #[ proc_macro_derive( Error , attributes( display , error) ) ]
84+ #[ proc_macro_derive( Error , attributes( error) ) ]
8585pub fn derive_error ( input : TokenStream ) -> TokenStream {
8686 let input = parse_macro_input ! ( input as syn:: DeriveInput ) ;
8787 match derive_error_inner ( input) {
@@ -91,9 +91,9 @@ pub fn derive_error(input: TokenStream) -> TokenStream {
9191}
9292
9393fn derive_error_inner ( input : DeriveInput ) -> Result < proc_macro2:: TokenStream , darling:: Error > {
94- let top = TopAttrs :: from_attributes ( & input. attrs ) ?;
9594 match & input. data {
9695 syn:: Data :: Enum ( item_enum) => {
96+ let top = TopAttrs :: from_attributes ( & input. attrs ) ?;
9797 let infos = item_enum
9898 . variants
9999 . iter ( )
@@ -102,6 +102,7 @@ fn derive_error_inner(input: DeriveInput) -> Result<proc_macro2::TokenStream, da
102102 Ok ( generate_enum_impls ( & input. ident , & input. generics , infos) )
103103 }
104104 syn:: Data :: Struct ( item) => {
105+ let top = TopAttrs :: default ( ) ;
105106 let info = VariantInfo :: parse ( & input. ident , & item. fields , & input. attrs , & top) ?;
106107 Ok ( generate_struct_impl ( & input. ident , & input. generics , info) )
107108 }
@@ -111,7 +112,6 @@ fn derive_error_inner(input: DeriveInput) -> Result<proc_macro2::TokenStream, da
111112
112113struct SourceField < ' a > {
113114 kind : SourceKind ,
114- transparent : bool ,
115115 field : FieldInfo < ' a > ,
116116}
117117
@@ -154,18 +154,12 @@ struct FieldAttrs {
154154 meta : bool ,
155155}
156156
157- #[ derive( Default , Clone , Copy , FromAttributes ) ]
158- #[ darling( default , attributes( error) ) ]
159- struct VariantAttrs {
160- transparent : bool ,
161- }
162-
163- // For each variant, capture doc comment text or #[display] attr
157+ // For each variant, capture doc comment text or #[error] attr
164158struct VariantInfo < ' a > {
165159 ident : Ident ,
166160 fields : Vec < FieldInfo < ' a > > ,
167161 kind : Kind ,
168- display : Option < proc_macro2 :: TokenStream > ,
162+ display : Display ,
169163 source : Option < SourceField < ' a > > ,
170164 /// The field that is used for From<..> impls
171165 from : Option < FieldInfo < ' a > > ,
@@ -231,8 +225,10 @@ impl<'a> FieldIdent<'a> {
231225
232226impl < ' a > VariantInfo < ' a > {
233227 fn transparent ( & self ) -> Option < & FieldInfo < ' _ > > {
234- let source = self . source . as_ref ( ) ?;
235- source. transparent . then_some ( & source. field )
228+ match self . display {
229+ Display :: Transparent => self . source . as_ref ( ) . map ( |s| & s. field ) ,
230+ _ => None ,
231+ }
236232 }
237233
238234 fn field_binding_idents ( & self ) -> impl Iterator < Item = Ident > + ' _ {
@@ -263,15 +259,7 @@ impl<'a> VariantInfo<'a> {
263259 attrs : & [ Attribute ] ,
264260 top : & TopAttrs ,
265261 ) -> Result < VariantInfo < ' a > , syn:: Error > {
266- let variant_attrs = VariantAttrs :: from_attributes ( attrs) ?;
267- let display = get_doc_or_display ( & attrs) ?;
268- // TODO: enable this but only for #[display] not for doc comments
269- // if display.is_some() && variant_attrs.transparent {
270- // return Err(err(
271- // ident,
272- // "#[display] and #[error(transparent)] are mutually exclusive",
273- // ));
274- // }
262+ let display = get_display ( & attrs) ?;
275263 let ( kind, fields) : ( Kind , Vec < FieldInfo > ) = match fields {
276264 Fields :: Named ( ref fields) => (
277265 Kind :: Named ,
@@ -308,15 +296,15 @@ impl<'a> VariantInfo<'a> {
308296 _ => false ,
309297 } ) ,
310298 Kind :: Tuple => {
311- if variant_attrs . transparent {
299+ if display . is_transparent ( ) {
312300 fields. first ( )
313301 } else {
314302 None
315303 }
316304 }
317305 } ) ;
318306
319- if variant_attrs . transparent && source_field. is_none ( ) {
307+ if display . is_transparent ( ) && source_field. is_none ( ) {
320308 return Err ( err (
321309 ident,
322310 "Variants with #[error(transparent)] require a source field" ,
@@ -333,11 +321,7 @@ impl<'a> VariantInfo<'a> {
333321 SourceKind :: Stack
334322 } ;
335323 let field = ( * field) . clone ( ) ;
336- Some ( SourceField {
337- kind,
338- transparent : variant_attrs. transparent ,
339- field,
340- } )
324+ Some ( SourceField { kind, field } )
341325 }
342326 } ;
343327
@@ -478,12 +462,12 @@ fn generate_enum_impls(
478462 } ) ;
479463
480464 let match_fmt_message_arms = variants. iter ( ) . map ( |vi| match & vi. display {
481- Some ( expr) => {
465+ Display :: Format ( expr) => {
482466 let binds: Vec < Ident > = vi. field_binding_idents ( ) . collect ( ) ;
483467 let pat = vi. spread_all ( & binds) ;
484468 quote ! { #pat => { #expr } }
485469 }
486- None => {
470+ Display :: Default | Display :: Transparent => {
487471 let text = format ! ( "{}::{}" , enum_ident, vi. ident) ;
488472 let pat = vi. spread_empty ( ) ;
489473 quote ! { #pat => write!( f, #text) }
@@ -705,7 +689,7 @@ fn generate_struct_impl(
705689 quote ! { write!( f, "{}" , #expr) }
706690 } else {
707691 match & info. display {
708- Some ( expr) => {
692+ Display :: Format ( expr) => {
709693 let binds: Vec < Ident > = info. field_binding_idents ( ) . collect ( ) ;
710694 match info. kind {
711695 Kind :: Named => {
@@ -716,7 +700,7 @@ fn generate_struct_impl(
716700 }
717701 }
718702 }
719- None => {
703+ Display :: Default | Display :: Transparent => {
720704 // Fallback to struct name
721705 let text = info. ident . to_string ( ) ;
722706 quote ! { write!( f, #text) }
@@ -868,47 +852,61 @@ fn generate_struct_impl(
868852 }
869853}
870854
871- fn get_doc_or_display ( attrs : & [ Attribute ] ) -> Result < Option < proc_macro2:: TokenStream > , syn:: Error > {
872- // Prefer #[display("...")]
873- if let Some ( attr) = attrs. iter ( ) . find ( |a| a. path ( ) . is_ident ( "display" ) ) {
874- // Accept format!-style args: #[display("text {}", arg1, arg2, ...)]
875- let args = attr. parse_args_with ( Punctuated :: < Expr , syn:: Token ![ , ] > :: parse_terminated) ?;
876- if args. is_empty ( ) {
877- Err ( err (
878- attr,
879- "#[display(..)] requires at least a format string" ,
880- ) )
881- } else {
882- let mut it = args. into_iter ( ) ;
883- let fmt = it. next ( ) . unwrap ( ) ;
884- let rest: Vec < _ > = it. collect ( ) ;
885- Ok ( Some ( quote ! { write!( f, #fmt #( , #rest) * ) } ) )
886- }
887- } else {
888- // Otherwise collect doc lines: #[doc = "..."]
889- let docs: Vec < String > = attrs
890- . iter ( )
891- . filter ( |a| a. path ( ) . is_ident ( "doc" ) )
892- . filter_map ( |attr| {
893- let s = attr. meta . require_name_value ( ) . ok ( ) ?;
894- match & s. value {
895- syn:: Expr :: Lit ( syn:: ExprLit {
896- lit : syn:: Lit :: Str ( s) ,
897- ..
898- } ) => Some ( s. value ( ) . trim ( ) . to_string ( ) ) ,
899- _ => None ,
900- }
901- } )
902- . collect ( ) ;
903- if docs. is_empty ( ) {
904- Ok ( None )
905- } else {
906- let doc = docs. join ( "\n " ) ;
907- Ok ( Some ( quote ! { write!( f, #doc) } ) )
908- }
855+ enum Display {
856+ Default ,
857+ Transparent ,
858+ Format ( proc_macro2:: TokenStream ) ,
859+ }
860+
861+ impl Display {
862+ fn is_transparent ( & self ) -> bool {
863+ matches ! ( self , Self :: Transparent )
909864 }
910865}
911866
867+ fn get_display ( attrs : & [ Attribute ] ) -> Result < Display , syn:: Error > {
868+ // Only consider #[error(...)]
869+ let Some ( attr) = attrs. iter ( ) . find ( |a| a. path ( ) . is_ident ( "error" ) ) else {
870+ return Ok ( Display :: Default ) ;
871+ } ;
872+
873+ // syn 2: parse args inside the attribute's parentheses
874+ let args: Punctuated < Expr , syn:: Token ![ , ] > =
875+ attr. parse_args_with ( Punctuated :: < Expr , syn:: Token ![ , ] > :: parse_terminated) ?;
876+
877+ if args. is_empty ( ) {
878+ return Err ( err (
879+ attr,
880+ "#[error(..)] requires arguments: a format string or `transparent`" ,
881+ ) ) ;
882+ }
883+
884+ // #[error(transparent)]
885+ if args. len ( ) == 1 {
886+ if let Expr :: Path ( p) = & args[ 0 ] {
887+ if p. path . is_ident ( "transparent" ) {
888+ return Ok ( Display :: Transparent ) ;
889+ }
890+ }
891+ }
892+
893+ // #[error("...", args...)]
894+ let mut it = args. into_iter ( ) ;
895+ let first = it. next ( ) . unwrap ( ) ;
896+
897+ let fmt_lit = match first {
898+ Expr :: Lit ( syn:: ExprLit { lit : syn:: Lit :: Str ( s) , .. } ) => s,
899+ other => {
900+ return Err ( err (
901+ other,
902+ "first argument to #[error(\" ...\" )] must be a string literal, or use #[error(transparent)]" ,
903+ ) )
904+ }
905+ } ;
906+
907+ let rest: Vec < Expr > = it. collect ( ) ;
908+ Ok ( Display :: Format ( quote ! { write!( f, #fmt_lit #( , #rest) * ) } ) )
909+ }
912910fn err ( ident : impl ToTokens , err : impl ToString ) -> syn:: Error {
913911 syn:: Error :: new_spanned ( ident, err. to_string ( ) )
914912}
0 commit comments