@@ -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( StackError , 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,16 +102,20 @@ 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 }
108- _ => Err ( err ( & input, "#[derive(Error)] only supports enums or structs" ) . into ( ) ) ,
109+ _ => Err ( err (
110+ & input,
111+ "#[derive(StackError)] only supports enums or structs" ,
112+ )
113+ . into ( ) ) ,
109114 }
110115}
111116
112117struct SourceField < ' a > {
113118 kind : SourceKind ,
114- transparent : bool ,
115119 field : FieldInfo < ' a > ,
116120}
117121
@@ -137,7 +141,7 @@ enum SourceKind {
137141}
138142
139143#[ derive( Default , Clone , Copy , FromAttributes ) ]
140- #[ darling( default , attributes( error) ) ]
144+ #[ darling( default , attributes( error, stackerr ) ) ]
141145struct TopAttrs {
142146 from_sources : bool ,
143147 std_sources : bool ,
@@ -154,18 +158,12 @@ struct FieldAttrs {
154158 meta : bool ,
155159}
156160
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
161+ // For each variant, capture doc comment text or #[error] attr
164162struct VariantInfo < ' a > {
165163 ident : Ident ,
166164 fields : Vec < FieldInfo < ' a > > ,
167165 kind : Kind ,
168- display : Option < proc_macro2 :: TokenStream > ,
166+ display : Display ,
169167 source : Option < SourceField < ' a > > ,
170168 /// The field that is used for From<..> impls
171169 from : Option < FieldInfo < ' a > > ,
@@ -231,8 +229,10 @@ impl<'a> FieldIdent<'a> {
231229
232230impl < ' a > VariantInfo < ' a > {
233231 fn transparent ( & self ) -> Option < & FieldInfo < ' _ > > {
234- let source = self . source . as_ref ( ) ?;
235- source. transparent . then_some ( & source. field )
232+ match self . display {
233+ Display :: Transparent => self . source . as_ref ( ) . map ( |s| & s. field ) ,
234+ _ => None ,
235+ }
236236 }
237237
238238 fn field_binding_idents ( & self ) -> impl Iterator < Item = Ident > + ' _ {
@@ -263,15 +263,7 @@ impl<'a> VariantInfo<'a> {
263263 attrs : & [ Attribute ] ,
264264 top : & TopAttrs ,
265265 ) -> 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- // }
266+ let display = get_display ( & attrs) ?;
275267 let ( kind, fields) : ( Kind , Vec < FieldInfo > ) = match fields {
276268 Fields :: Named ( ref fields) => (
277269 Kind :: Named ,
@@ -308,15 +300,15 @@ impl<'a> VariantInfo<'a> {
308300 _ => false ,
309301 } ) ,
310302 Kind :: Tuple => {
311- if variant_attrs . transparent {
303+ if display . is_transparent ( ) {
312304 fields. first ( )
313305 } else {
314306 None
315307 }
316308 }
317309 } ) ;
318310
319- if variant_attrs . transparent && source_field. is_none ( ) {
311+ if display . is_transparent ( ) && source_field. is_none ( ) {
320312 return Err ( err (
321313 ident,
322314 "Variants with #[error(transparent)] require a source field" ,
@@ -333,11 +325,7 @@ impl<'a> VariantInfo<'a> {
333325 SourceKind :: Stack
334326 } ;
335327 let field = ( * field) . clone ( ) ;
336- Some ( SourceField {
337- kind,
338- transparent : variant_attrs. transparent ,
339- field,
340- } )
328+ Some ( SourceField { kind, field } )
341329 }
342330 } ;
343331
@@ -478,12 +466,12 @@ fn generate_enum_impls(
478466 } ) ;
479467
480468 let match_fmt_message_arms = variants. iter ( ) . map ( |vi| match & vi. display {
481- Some ( expr) => {
469+ Display :: Format ( expr) => {
482470 let binds: Vec < Ident > = vi. field_binding_idents ( ) . collect ( ) ;
483471 let pat = vi. spread_all ( & binds) ;
484472 quote ! { #pat => { #expr } }
485473 }
486- None => {
474+ Display :: Default | Display :: Transparent => {
487475 let text = format ! ( "{}::{}" , enum_ident, vi. ident) ;
488476 let pat = vi. spread_empty ( ) ;
489477 quote ! { #pat => write!( f, #text) }
@@ -705,7 +693,7 @@ fn generate_struct_impl(
705693 quote ! { write!( f, "{}" , #expr) }
706694 } else {
707695 match & info. display {
708- Some ( expr) => {
696+ Display :: Format ( expr) => {
709697 let binds: Vec < Ident > = info. field_binding_idents ( ) . collect ( ) ;
710698 match info. kind {
711699 Kind :: Named => {
@@ -716,7 +704,7 @@ fn generate_struct_impl(
716704 }
717705 }
718706 }
719- None => {
707+ Display :: Default | Display :: Transparent => {
720708 // Fallback to struct name
721709 let text = info. ident . to_string ( ) ;
722710 quote ! { write!( f, #text) }
@@ -868,47 +856,61 @@ fn generate_struct_impl(
868856 }
869857}
870858
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- }
859+ enum Display {
860+ Default ,
861+ Transparent ,
862+ Format ( proc_macro2:: TokenStream ) ,
863+ }
864+
865+ impl Display {
866+ fn is_transparent ( & self ) -> bool {
867+ matches ! ( self , Self :: Transparent )
909868 }
910869}
911870
871+ fn get_display ( attrs : & [ Attribute ] ) -> Result < Display , syn:: Error > {
872+ // Only consider #[error(...)]
873+ let Some ( attr) = attrs. iter ( ) . find ( |a| a. path ( ) . is_ident ( "error" ) ) else {
874+ return Ok ( Display :: Default ) ;
875+ } ;
876+
877+ // syn 2: parse args inside the attribute's parentheses
878+ let args: Punctuated < Expr , syn:: Token ![ , ] > =
879+ attr. parse_args_with ( Punctuated :: < Expr , syn:: Token ![ , ] > :: parse_terminated) ?;
880+
881+ if args. is_empty ( ) {
882+ return Err ( err (
883+ attr,
884+ "#[error(..)] requires arguments: a format string or `transparent`" ,
885+ ) ) ;
886+ }
887+
888+ // #[error(transparent)]
889+ if args. len ( ) == 1 {
890+ if let Expr :: Path ( p) = & args[ 0 ] {
891+ if p. path . is_ident ( "transparent" ) {
892+ return Ok ( Display :: Transparent ) ;
893+ }
894+ }
895+ }
896+
897+ // #[error("...", args...)]
898+ let mut it = args. into_iter ( ) ;
899+ let first = it. next ( ) . unwrap ( ) ;
900+
901+ let fmt_lit = match first {
902+ Expr :: Lit ( syn:: ExprLit { lit : syn:: Lit :: Str ( s) , .. } ) => s,
903+ other => {
904+ return Err ( err (
905+ other,
906+ "first argument to #[error(\" ...\" )] must be a string literal, or use #[error(transparent)]" ,
907+ ) )
908+ }
909+ } ;
910+
911+ let rest: Vec < Expr > = it. collect ( ) ;
912+ Ok ( Display :: Format ( quote ! { write!( f, #fmt_lit #( , #rest) * ) } ) )
913+ }
912914fn err ( ident : impl ToTokens , err : impl ToString ) -> syn:: Error {
913915 syn:: Error :: new_spanned ( ident, err. to_string ( ) )
914916}
0 commit comments