1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{punctuated::Punctuated, DeriveInput, Token};
4
5use crate::code::Code;
6use crate::diagnostic_arg::DiagnosticArg;
7use crate::diagnostic_source::DiagnosticSource;
8use crate::forward::{Forward, WhichFn};
9use crate::help::Help;
10use crate::label::Labels;
11use crate::related::Related;
12use crate::severity::Severity;
13use crate::source_code::SourceCode;
14use crate::url::Url;
15
16pub enum Diagnostic {
17 Struct {
18 generics: syn::Generics,
19 ident: syn::Ident,
20 fields: syn::Fields,
21 args: DiagnosticDefArgs,
22 },
23 Enum {
24 ident: syn::Ident,
25 generics: syn::Generics,
26 variants: Vec<DiagnosticDef>,
27 },
28}
29
30pub struct DiagnosticDef {
31 pub ident: syn::Ident,
32 pub fields: syn::Fields,
33 pub args: DiagnosticDefArgs,
34}
35
36pub enum DiagnosticDefArgs {
37 Transparent(Forward),
38 Concrete(Box<DiagnosticConcreteArgs>),
39}
40
41impl DiagnosticDefArgs {
42 pub(crate) fn forward_or_override_enum(
43 &self,
44 variant: &syn::Ident,
45 which_fn: WhichFn,
46 mut f: impl FnMut(&DiagnosticConcreteArgs) -> Option<TokenStream>,
47 ) -> Option<TokenStream> {
48 match self {
49 Self::Transparent(forward) => Some(forward.gen_enum_match_arm(variant, which_fn)),
50 Self::Concrete(concrete) => f(concrete).or_else(|| {
51 concrete
52 .forward
53 .as_ref()
54 .map(|forward| forward.gen_enum_match_arm(variant, which_fn))
55 }),
56 }
57 }
58}
59
60#[derive(Default)]
61pub struct DiagnosticConcreteArgs {
62 pub code: Option<Code>,
63 pub severity: Option<Severity>,
64 pub help: Option<Help>,
65 pub labels: Option<Labels>,
66 pub source_code: Option<SourceCode>,
67 pub url: Option<Url>,
68 pub forward: Option<Forward>,
69 pub related: Option<Related>,
70 pub diagnostic_source: Option<DiagnosticSource>,
71}
72
73impl DiagnosticConcreteArgs {
74 fn for_fields(fields: &syn::Fields) -> Result<Self, syn::Error> {
75 let labels = Labels::from_fields(fields)?;
76 let source_code = SourceCode::from_fields(fields)?;
77 let related = Related::from_fields(fields)?;
78 let help = Help::from_fields(fields)?;
79 let diagnostic_source = DiagnosticSource::from_fields(fields)?;
80 Ok(DiagnosticConcreteArgs {
81 code: None,
82 help,
83 related,
84 severity: None,
85 labels,
86 url: None,
87 forward: None,
88 source_code,
89 diagnostic_source,
90 })
91 }
92
93 fn add_args(
94 &mut self,
95 attr: &syn::Attribute,
96 args: impl Iterator<Item = DiagnosticArg>,
97 errors: &mut Vec<syn::Error>,
98 ) {
99 for arg in args {
100 match arg {
101 DiagnosticArg::Transparent => {
102 errors.push(syn::Error::new_spanned(attr, "transparent not allowed"));
103 }
104 DiagnosticArg::Forward(to_field) => {
105 if self.forward.is_some() {
106 errors.push(syn::Error::new_spanned(
107 attr,
108 "forward has already been specified",
109 ));
110 }
111 self.forward = Some(to_field);
112 }
113 DiagnosticArg::Code(new_code) => {
114 if self.code.is_some() {
115 errors.push(syn::Error::new_spanned(
116 attr,
117 "code has already been specified",
118 ));
119 }
120 self.code = Some(new_code);
121 }
122 DiagnosticArg::Severity(sev) => {
123 if self.severity.is_some() {
124 errors.push(syn::Error::new_spanned(
125 attr,
126 "severity has already been specified",
127 ));
128 }
129 self.severity = Some(sev);
130 }
131 DiagnosticArg::Help(hl) => {
132 if self.help.is_some() {
133 errors.push(syn::Error::new_spanned(
134 attr,
135 "help has already been specified",
136 ));
137 }
138 self.help = Some(hl);
139 }
140 DiagnosticArg::Url(u) => {
141 if self.url.is_some() {
142 errors.push(syn::Error::new_spanned(
143 attr,
144 "url has already been specified",
145 ));
146 }
147 self.url = Some(u);
148 }
149 }
150 }
151 }
152}
153
154impl DiagnosticDefArgs {
155 fn parse(
156 _ident: &syn::Ident,
157 fields: &syn::Fields,
158 attrs: &[&syn::Attribute],
159 allow_transparent: bool,
160 ) -> syn::Result<Self> {
161 let mut errors = Vec::new();
162
163 if allow_transparent && attrs.len() == 1 {
165 if let Ok(args) =
166 attrs[0].parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated)
167 {
168 if matches!(args.first(), Some(DiagnosticArg::Transparent)) {
169 let forward = Forward::for_transparent_field(fields)?;
170 return Ok(Self::Transparent(forward));
171 }
172 }
173 }
174
175 let error_message = if allow_transparent {
177 "diagnostic(transparent) not allowed in combination with other args"
178 } else {
179 "diagnostic(transparent) not allowed here"
180 };
181 fn is_transparent(d: &DiagnosticArg) -> bool {
182 matches!(d, DiagnosticArg::Transparent)
183 }
184
185 let mut concrete = DiagnosticConcreteArgs::for_fields(fields)?;
186 for attr in attrs {
187 let args =
188 attr.parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated);
189 let args = match args {
190 Ok(args) => args,
191 Err(error) => {
192 errors.push(error);
193 continue;
194 }
195 };
196
197 if args.iter().any(is_transparent) {
198 errors.push(syn::Error::new_spanned(attr, error_message));
199 }
200
201 let args = args
202 .into_iter()
203 .filter(|x| !matches!(x, DiagnosticArg::Transparent));
204
205 concrete.add_args(attr, args, &mut errors);
206 }
207
208 let combined_error = errors.into_iter().reduce(|mut lhs, rhs| {
209 lhs.combine(rhs);
210 lhs
211 });
212 if let Some(error) = combined_error {
213 Err(error)
214 } else {
215 Ok(DiagnosticDefArgs::Concrete(Box::new(concrete)))
216 }
217 }
218}
219
220impl Diagnostic {
221 pub fn from_derive_input(input: DeriveInput) -> Result<Self, syn::Error> {
222 let input_attrs = input
223 .attrs
224 .iter()
225 .filter(|x| x.path().is_ident("diagnostic"))
226 .collect::<Vec<&syn::Attribute>>();
227 Ok(match input.data {
228 syn::Data::Struct(data_struct) => {
229 let args = DiagnosticDefArgs::parse(
230 &input.ident,
231 &data_struct.fields,
232 &input_attrs,
233 true,
234 )?;
235
236 Diagnostic::Struct {
237 fields: data_struct.fields,
238 ident: input.ident,
239 generics: input.generics,
240 args,
241 }
242 }
243 syn::Data::Enum(syn::DataEnum { variants, .. }) => {
244 let mut vars = Vec::new();
245 for var in variants {
246 let mut variant_attrs = input_attrs.clone();
247 variant_attrs
248 .extend(var.attrs.iter().filter(|x| x.path().is_ident("diagnostic")));
249 let args =
250 DiagnosticDefArgs::parse(&var.ident, &var.fields, &variant_attrs, true)?;
251 vars.push(DiagnosticDef {
252 ident: var.ident,
253 fields: var.fields,
254 args,
255 });
256 }
257 Diagnostic::Enum {
258 ident: input.ident,
259 generics: input.generics,
260 variants: vars,
261 }
262 }
263 syn::Data::Union(_) => {
264 return Err(syn::Error::new(
265 input.ident.span(),
266 "Can't derive Diagnostic for Unions",
267 ))
268 }
269 })
270 }
271
272 pub fn gen(&self) -> TokenStream {
273 match self {
274 Self::Struct {
275 ident,
276 fields,
277 generics,
278 args,
279 } => {
280 let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
281 match args {
282 DiagnosticDefArgs::Transparent(forward) => {
283 let code_method = forward.gen_struct_method(WhichFn::Code);
284 let help_method = forward.gen_struct_method(WhichFn::Help);
285 let url_method = forward.gen_struct_method(WhichFn::Url);
286 let labels_method = forward.gen_struct_method(WhichFn::Labels);
287 let source_code_method = forward.gen_struct_method(WhichFn::SourceCode);
288 let severity_method = forward.gen_struct_method(WhichFn::Severity);
289 let related_method = forward.gen_struct_method(WhichFn::Related);
290 let diagnostic_source_method =
291 forward.gen_struct_method(WhichFn::DiagnosticSource);
292
293 quote! {
294 impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
295 #code_method
296 #help_method
297 #url_method
298 #labels_method
299 #severity_method
300 #source_code_method
301 #related_method
302 #diagnostic_source_method
303 }
304 }
305 }
306 DiagnosticDefArgs::Concrete(concrete) => {
307 let forward = |which| {
308 concrete
309 .forward
310 .as_ref()
311 .map(|fwd| fwd.gen_struct_method(which))
312 };
313 let code_body = concrete
314 .code
315 .as_ref()
316 .and_then(|x| x.gen_struct())
317 .or_else(|| forward(WhichFn::Code));
318 let help_body = concrete
319 .help
320 .as_ref()
321 .and_then(|x| x.gen_struct(fields))
322 .or_else(|| forward(WhichFn::Help));
323 let sev_body = concrete
324 .severity
325 .as_ref()
326 .and_then(|x| x.gen_struct())
327 .or_else(|| forward(WhichFn::Severity));
328 let rel_body = concrete
329 .related
330 .as_ref()
331 .and_then(|x| x.gen_struct())
332 .or_else(|| forward(WhichFn::Related));
333 let url_body = concrete
334 .url
335 .as_ref()
336 .and_then(|x| x.gen_struct(ident, fields))
337 .or_else(|| forward(WhichFn::Url));
338 let labels_body = concrete
339 .labels
340 .as_ref()
341 .and_then(|x| x.gen_struct(fields))
342 .or_else(|| forward(WhichFn::Labels));
343 let src_body = concrete
344 .source_code
345 .as_ref()
346 .and_then(|x| x.gen_struct(fields))
347 .or_else(|| forward(WhichFn::SourceCode));
348 let diagnostic_source = concrete
349 .diagnostic_source
350 .as_ref()
351 .and_then(|x| x.gen_struct())
352 .or_else(|| forward(WhichFn::DiagnosticSource));
353 quote! {
354 impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
355 #code_body
356 #help_body
357 #sev_body
358 #rel_body
359 #url_body
360 #labels_body
361 #src_body
362 #diagnostic_source
363 }
364 }
365 }
366 }
367 }
368 Self::Enum {
369 ident,
370 generics,
371 variants,
372 } => {
373 let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
374 let code_body = Code::gen_enum(variants);
375 let help_body = Help::gen_enum(variants);
376 let sev_body = Severity::gen_enum(variants);
377 let labels_body = Labels::gen_enum(variants);
378 let src_body = SourceCode::gen_enum(variants);
379 let rel_body = Related::gen_enum(variants);
380 let url_body = Url::gen_enum(ident, variants);
381 let diagnostic_source_body = DiagnosticSource::gen_enum(variants);
382 quote! {
383 impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
384 #code_body
385 #help_body
386 #sev_body
387 #labels_body
388 #src_body
389 #rel_body
390 #url_body
391 #diagnostic_source_body
392 }
393 }
394 }
395 }
396 }
397}