• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Alorel / delegate-display-rs / 11355618640

15 Oct 2024 10:51PM UTC coverage: 93.118%. Remained the same
11355618640

push

github

Alorel
feat: Support deriving multiple traits at once

BREAKING CHANGE: `dboth` attribute renamed to `dany`

81 of 85 new or added lines in 6 files covered. (95.29%)

9 existing lines in 1 file now uncovered.

433 of 465 relevant lines covered (93.12%)

8.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.83
/src/implementation.rs
1
mod compound;
2
mod dual_attr;
3
mod main_field;
4
mod opts;
5
mod variant;
6

7
pub use compound::Alias;
8

9
use main_field::MainField;
10
use variant::{Style, Variant};
11

12
use macroific::elements::module_prefix::RESULT;
13
use macroific::elements::{GenericImpl, ModulePrefix};
14
use macroific::prelude::*;
15
use opts::ContainerOptions;
16
use proc_macro::TokenStream as TokenStream1;
17
use proc_macro2::{Ident, TokenStream};
18
use quote::{quote, ToTokens};
19
use syn::{parse_quote, Data, DeriveInput, Error, Generics};
20

21
const FMT: ModulePrefix<2> = ModulePrefix::new(["core", "fmt"]);
22

23
pub(crate) struct Implementation<'a> {
24
    trait_name: &'a str,
25
    ident: Ident,
26
    generics: Generics,
27
    opts: ContainerOptions,
28
}
29

30
impl<'a> Implementation<'a> {
31
    pub fn exec(input: TokenStream1, attr_name: &str, trait_name: &'a str) -> TokenStream1 {
21✔
32
        Self::exec_2(input, attr_name, trait_name)
21✔
33
            .unwrap_or_else(Error::into_compile_error)
21✔
34
            .into()
21✔
35
    }
21✔
36

37
    fn exec_2(
21✔
38
        input: TokenStream1,
21✔
39
        attr_name: &str,
21✔
40
        trait_name: &'a str,
21✔
41
    ) -> syn::Result<TokenStream> {
21✔
42
        let DeriveInput {
43
            attrs,
21✔
44
            ident,
21✔
45
            generics,
21✔
46
            data,
21✔
47
            ..
48
        } = syn::parse(input)?;
21✔
49

50
        let common = Self {
21✔
51
            opts: ContainerOptions::resolve(attrs, attr_name)?,
21✔
52
            trait_name,
21✔
53
            ident,
21✔
54
            generics,
21✔
55
        };
21✔
56

21✔
57
        common.exec_data(data, attr_name)
21✔
58
    }
21✔
59

60
    fn exec_data(self, data: Data, attr_name: &str) -> syn::Result<TokenStream> {
24✔
61
        match data {
24✔
62
            Data::Struct(data) => {
19✔
63
                let main_field = MainField::resolve_from_fields(data.fields, attr_name)?;
19✔
64
                Ok(self.impl_struct(main_field))
16✔
65
            }
66
            Data::Enum(data) => {
4✔
67
                let variants: Vec<Variant> = data
4✔
68
                    .variants
4✔
69
                    .into_iter()
4✔
70
                    .map(move |v| Variant::from_syn(v, attr_name))
11✔
71
                    .collect::<syn::Result<_>>()?;
4✔
72

73
                Ok(self.impl_enum(variants))
3✔
74
            }
75
            Data::Union(u) => Err(Error::new_spanned(u.union_token, "Unions not supported")),
1✔
76
        }
77
    }
24✔
78

79
    fn trait_path(&self) -> ModulePrefix<3> {
42✔
80
        ModulePrefix::new(["core", "fmt", self.trait_name])
42✔
81
    }
42✔
82

83
    fn impl_enum(mut self, variants: Vec<Variant>) -> TokenStream {
3✔
84
        self.preprocess_generics_enum(&variants);
3✔
85
        let mut tokens = self.header();
3✔
86
        let mut has_skipped_arms = false;
3✔
87
        let trait_path = self.trait_path();
3✔
88

3✔
89
        let arms = variants.into_iter()
3✔
90
            .filter_map(|variant| {
7✔
91
                let Variant { ident, style, main_field } = variant;
7✔
92

7✔
93
                match style {
7✔
94
                    Style::Tuple => {
95
                        let Some(main_field) = main_field else {
3✔
UNCOV
96
                            has_skipped_arms = true;
×
UNCOV
97
                            return None;
×
98
                        };
99

100
                        let args = main_field.args_for_tuple_enum();
3✔
101
                        let ty = if let Some(delegate_to) = &self.opts.delegate_to {
3✔
UNCOV
102
                            delegate_to
×
103
                        } else {
104
                            &main_field.ty
3✔
105
                        };
106

107
                        Some(quote! {
3✔
108
                            Self::#ident(#(#args),*) => <#ty as #trait_path>::fmt(v, f),
3✔
109
                        })
3✔
110
                    },
111
                    Style::Named => {
112
                        let Some(main_field) = main_field else {
3✔
UNCOV
113
                            has_skipped_arms = true;
×
UNCOV
114
                            return None;
×
115
                        };
116

117
                        let dots = if main_field.num_fields > 1 {
3✔
118
                            quote!(,..)
1✔
119
                        } else {
120
                            TokenStream::new()
2✔
121
                        };
122

123
                        let field_name = &main_field.ident;
3✔
124
                        let ty = if let Some(delegate_to) = &self.opts.delegate_to {
3✔
UNCOV
125
                            delegate_to
×
126
                        } else {
127
                            &main_field.ty
3✔
128
                        };
129

130
                        Some(quote! {
3✔
131
                            Self::#ident { #field_name: v #dots } => <#ty as #trait_path>::fmt(v, f),
3✔
132
                        })
3✔
133
                    },
134
                    Style::Unit => {
135
                        has_skipped_arms = true;
1✔
136
                        None
1✔
137
                    },
138
                }
139
            })
7✔
140
            .collect::<TokenStream>();
3✔
141

142
        let other_arm = if has_skipped_arms {
3✔
143
            quote!(_ => #RESULT::Ok(()),)
1✔
144
        } else {
145
            TokenStream::new()
2✔
146
        };
147

148
        let formatter_name = Ident::create(if arms.is_empty() { "_" } else { "f" });
3✔
149

150
        tokens.extend(quote! {{
3✔
151
            fn fmt(&self, #formatter_name: &mut #FMT::Formatter<'_>) -> #FMT::Result {
3✔
152
                match self {
3✔
153
                    #arms
3✔
154
                    #other_arm
3✔
155
                }
3✔
156
            }
3✔
157
        }});
3✔
158

3✔
159
        tokens
3✔
160
    }
3✔
161

162
    fn impl_struct(mut self, main_field: Option<MainField>) -> TokenStream {
16✔
163
        self.preprocess_generics_struct(&main_field);
16✔
164
        let mut tokens = self.header();
16✔
165
        let trait_path = self.trait_path();
16✔
166

167
        let (body, param) = if let Some(main_field) = main_field {
16✔
168
            let ident = main_field.ident_for_struct();
10✔
169
            let ty = if let Some(delegate_to) = &self.opts.delegate_to {
10✔
170
                delegate_to
5✔
171
            } else {
172
                &main_field.ty
5✔
173
            };
174

175
            (
10✔
176
                quote!(<#ty as #trait_path>::fmt(&self.#ident, f)),
10✔
177
                Ident::create("f"),
10✔
178
            )
10✔
179
        } else {
180
            (quote!(#RESULT::Ok(())), Ident::create("_"))
6✔
181
        };
182

183
        tokens.extend(quote! {{
16✔
184
            #[inline]
16✔
185
            fn fmt(&self, #param: &mut #FMT::Formatter<'_>) -> #FMT::Result {
16✔
186
                #body
16✔
187
            }
16✔
188
        }});
16✔
189

16✔
190
        tokens
16✔
191
    }
16✔
192

193
    fn header(&self) -> TokenStream {
19✔
194
        let header = GenericImpl::new(&self.generics)
19✔
195
            .with_trait(self.trait_path())
19✔
196
            .with_target(&self.ident);
19✔
197

19✔
198
        quote! {
19✔
199
            #[automatically_derived]
19✔
200
            #[allow(clippy::all)]
19✔
201
            #header
19✔
202
        }
19✔
203
    }
19✔
204

205
    fn preprocess_generics_struct(&mut self, main_field: &Option<MainField>) {
16✔
206
        if self.generics.params.is_empty() {
16✔
207
            return;
13✔
208
        }
3✔
209

3✔
210
        if self.preprocess_generics_common() {
3✔
211
            return;
1✔
212
        }
2✔
213

214
        if let Some(main_field) = main_field {
2✔
215
            self.add_debug_clause(&main_field.ty);
2✔
216
        }
2✔
217
    }
16✔
218

219
    fn preprocess_generics_enum(&mut self, variants: &[Variant]) {
3✔
220
        if self.generics.params.is_empty() {
3✔
221
            return;
2✔
222
        }
1✔
223

1✔
224
        if self.preprocess_generics_common() {
1✔
UNCOV
225
            return;
×
226
        }
1✔
227

228
        for variant in variants {
3✔
229
            if let Some(main_field) = &variant.main_field {
2✔
230
                self.add_debug_clause(&main_field.ty);
2✔
231
            }
2✔
232
        }
233
    }
3✔
234

235
    fn preprocess_generics_common(&mut self) -> bool {
4✔
236
        if !self.opts.bounds.is_empty() {
4✔
237
            let iter = self.opts.bounds.iter().cloned();
1✔
238
            self.generics.make_where_clause().predicates.extend(iter);
1✔
239
            true
1✔
240
        } else if let Some(delegate_to) = &self.opts.delegate_to {
3✔
UNCOV
241
            self.add_debug_clause(delegate_to.clone());
×
UNCOV
242
            true
×
243
        } else {
244
            false
3✔
245
        }
246
    }
4✔
247

248
    fn add_debug_clause<T>(&mut self, ty: T)
4✔
249
    where
4✔
250
        T: ToTokens,
4✔
251
    {
4✔
252
        let predicate = {
4✔
253
            let path = self.trait_path();
4✔
254
            parse_quote!(#ty: #path)
4✔
255
        };
4✔
256

4✔
257
        self.generics.make_where_clause().predicates.push(predicate);
4✔
258
    }
4✔
259
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc