]>
Commit | Line | Data |
---|---|---|
04454e1e FG |
1 | #![deny(unused_must_use)] |
2 | ||
3 | use crate::diagnostics::error::{ | |
353b0b11 | 4 | invalid_attr, span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError, |
04454e1e FG |
5 | }; |
6 | use crate::diagnostics::utils::{ | |
49aad941 FG |
7 | build_field_mapping, build_suggestion_code, is_doc_comment, new_code_ident, |
8 | report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span, | |
9 | should_generate_set_arg, AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap, | |
10 | HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind, | |
04454e1e FG |
11 | }; |
12 | use proc_macro2::TokenStream; | |
13 | use quote::{format_ident, quote}; | |
353b0b11 | 14 | use syn::{spanned::Spanned, Attribute, Meta, MetaList, Path}; |
04454e1e FG |
15 | use synstructure::{BindingInfo, Structure, VariantInfo}; |
16 | ||
fe692bf9 FG |
17 | use super::utils::SubdiagnosticVariant; |
18 | ||
04454e1e | 19 | /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct. |
2b03887a | 20 | pub(crate) struct SubdiagnosticDeriveBuilder { |
04454e1e | 21 | diag: syn::Ident, |
2b03887a | 22 | f: syn::Ident, |
04454e1e FG |
23 | } |
24 | ||
2b03887a FG |
25 | impl SubdiagnosticDeriveBuilder { |
26 | pub(crate) fn new() -> Self { | |
04454e1e | 27 | let diag = format_ident!("diag"); |
2b03887a FG |
28 | let f = format_ident!("f"); |
29 | Self { diag, f } | |
04454e1e FG |
30 | } |
31 | ||
9c376795 | 32 | pub(crate) fn into_tokens(self, mut structure: Structure<'_>) -> TokenStream { |
04454e1e FG |
33 | let implementation = { |
34 | let ast = structure.ast(); | |
35 | let span = ast.span().unwrap(); | |
36 | match ast.data { | |
37 | syn::Data::Struct(..) | syn::Data::Enum(..) => (), | |
38 | syn::Data::Union(..) => { | |
39 | span_err( | |
40 | span, | |
2b03887a | 41 | "`#[derive(Subdiagnostic)]` can only be used on structs and enums", |
353b0b11 FG |
42 | ) |
43 | .emit(); | |
04454e1e FG |
44 | } |
45 | } | |
46 | ||
2b03887a FG |
47 | let is_enum = matches!(ast.data, syn::Data::Enum(..)); |
48 | if is_enum { | |
04454e1e | 49 | for attr in &ast.attrs { |
2b03887a FG |
50 | // Always allow documentation comments. |
51 | if is_doc_comment(attr) { | |
52 | continue; | |
53 | } | |
54 | ||
04454e1e FG |
55 | span_err( |
56 | attr.span().unwrap(), | |
57 | "unsupported type attribute for subdiagnostic enum", | |
58 | ) | |
59 | .emit(); | |
60 | } | |
61 | } | |
62 | ||
63 | structure.bind_with(|_| synstructure::BindStyle::Move); | |
64 | let variants_ = structure.each_variant(|variant| { | |
2b03887a FG |
65 | let mut builder = SubdiagnosticDeriveVariantBuilder { |
66 | parent: &self, | |
04454e1e FG |
67 | variant, |
68 | span, | |
2b03887a FG |
69 | formatting_init: TokenStream::new(), |
70 | fields: build_field_mapping(variant), | |
04454e1e FG |
71 | span_field: None, |
72 | applicability: None, | |
f2b60f7d | 73 | has_suggestion_parts: false, |
2b03887a | 74 | is_enum, |
04454e1e FG |
75 | }; |
76 | builder.into_tokens().unwrap_or_else(|v| v.to_compile_error()) | |
77 | }); | |
78 | ||
79 | quote! { | |
80 | match self { | |
81 | #variants_ | |
82 | } | |
83 | } | |
84 | }; | |
85 | ||
2b03887a FG |
86 | let diag = &self.diag; |
87 | let f = &self.f; | |
04454e1e | 88 | let ret = structure.gen_impl(quote! { |
2b03887a FG |
89 | gen impl rustc_errors::AddToDiagnostic for @Self { |
90 | fn add_to_diagnostic_with<__F>(self, #diag: &mut rustc_errors::Diagnostic, #f: __F) | |
91 | where | |
92 | __F: core::ops::Fn( | |
93 | &mut rustc_errors::Diagnostic, | |
94 | rustc_errors::SubdiagnosticMessage | |
95 | ) -> rustc_errors::SubdiagnosticMessage, | |
96 | { | |
04454e1e FG |
97 | use rustc_errors::{Applicability, IntoDiagnosticArg}; |
98 | #implementation | |
99 | } | |
100 | } | |
101 | }); | |
102 | ret | |
103 | } | |
104 | } | |
105 | ||
106 | /// Tracks persistent information required for building up the call to add to the diagnostic | |
2b03887a | 107 | /// for the final generated method. This is a separate struct to `SubdiagnosticDerive` |
04454e1e FG |
108 | /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a |
109 | /// double mut borrow later on. | |
2b03887a | 110 | struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> { |
04454e1e | 111 | /// The identifier to use for the generated `DiagnosticBuilder` instance. |
2b03887a | 112 | parent: &'parent SubdiagnosticDeriveBuilder, |
04454e1e FG |
113 | |
114 | /// Info for the current variant (or the type if not an enum). | |
115 | variant: &'a VariantInfo<'a>, | |
116 | /// Span for the entire type. | |
117 | span: proc_macro::Span, | |
118 | ||
2b03887a FG |
119 | /// Initialization of format strings for code suggestions. |
120 | formatting_init: TokenStream, | |
121 | ||
04454e1e FG |
122 | /// Store a map of field name to its corresponding field. This is built on construction of the |
123 | /// derive builder. | |
2b03887a | 124 | fields: FieldMap, |
04454e1e | 125 | |
04454e1e | 126 | /// Identifier for the binding to the `#[primary_span]` field. |
2b03887a FG |
127 | span_field: SpannedOption<proc_macro2::Ident>, |
128 | ||
129 | /// The binding to the `#[applicability]` field, if present. | |
130 | applicability: SpannedOption<TokenStream>, | |
f2b60f7d FG |
131 | |
132 | /// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error | |
133 | /// during finalization if still `false`. | |
134 | has_suggestion_parts: bool, | |
2b03887a FG |
135 | |
136 | /// Set to true when this variant is an enum variant rather than just the body of a struct. | |
137 | is_enum: bool, | |
04454e1e FG |
138 | } |
139 | ||
2b03887a | 140 | impl<'parent, 'a> HasFieldMap for SubdiagnosticDeriveVariantBuilder<'parent, 'a> { |
04454e1e FG |
141 | fn get_field_binding(&self, field: &String) -> Option<&TokenStream> { |
142 | self.fields.get(field) | |
143 | } | |
144 | } | |
145 | ||
f2b60f7d FG |
146 | /// Provides frequently-needed information about the diagnostic kinds being derived for this type. |
147 | #[derive(Clone, Copy, Debug)] | |
148 | struct KindsStatistics { | |
149 | has_multipart_suggestion: bool, | |
150 | all_multipart_suggestions: bool, | |
151 | has_normal_suggestion: bool, | |
2b03887a | 152 | all_applicabilities_static: bool, |
f2b60f7d FG |
153 | } |
154 | ||
155 | impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics { | |
156 | fn from_iter<T: IntoIterator<Item = &'a SubdiagnosticKind>>(kinds: T) -> Self { | |
157 | let mut ret = Self { | |
158 | has_multipart_suggestion: false, | |
159 | all_multipart_suggestions: true, | |
160 | has_normal_suggestion: false, | |
2b03887a | 161 | all_applicabilities_static: true, |
f2b60f7d | 162 | }; |
2b03887a | 163 | |
f2b60f7d | 164 | for kind in kinds { |
2b03887a FG |
165 | if let SubdiagnosticKind::MultipartSuggestion { applicability: None, .. } |
166 | | SubdiagnosticKind::Suggestion { applicability: None, .. } = kind | |
167 | { | |
168 | ret.all_applicabilities_static = false; | |
169 | } | |
f2b60f7d FG |
170 | if let SubdiagnosticKind::MultipartSuggestion { .. } = kind { |
171 | ret.has_multipart_suggestion = true; | |
172 | } else { | |
173 | ret.all_multipart_suggestions = false; | |
174 | } | |
175 | ||
176 | if let SubdiagnosticKind::Suggestion { .. } = kind { | |
177 | ret.has_normal_suggestion = true; | |
178 | } | |
179 | } | |
180 | ret | |
181 | } | |
182 | } | |
183 | ||
2b03887a | 184 | impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { |
fe692bf9 FG |
185 | fn identify_kind( |
186 | &mut self, | |
187 | ) -> Result<Vec<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> { | |
f2b60f7d FG |
188 | let mut kind_slugs = vec![]; |
189 | ||
04454e1e | 190 | for attr in self.variant.ast().attrs { |
add651ee FG |
191 | let Some(SubdiagnosticVariant { kind, slug, no_span }) = |
192 | SubdiagnosticVariant::from_attr(attr, self)? | |
193 | else { | |
2b03887a FG |
194 | // Some attributes aren't errors - like documentation comments - but also aren't |
195 | // subdiagnostics. | |
196 | continue; | |
f2b60f7d | 197 | }; |
064997fb | 198 | |
2b03887a | 199 | let Some(slug) = slug else { |
353b0b11 | 200 | let name = attr.path().segments.last().unwrap().ident.to_string(); |
2b03887a | 201 | let name = name.as_str(); |
064997fb | 202 | |
04454e1e | 203 | throw_span_err!( |
2b03887a | 204 | attr.span().unwrap(), |
fe692bf9 | 205 | format!( |
9c376795 | 206 | "diagnostic slug must be first argument of a `#[{name}(...)]` attribute" |
064997fb | 207 | ) |
04454e1e | 208 | ); |
f2b60f7d FG |
209 | }; |
210 | ||
fe692bf9 | 211 | kind_slugs.push((kind, slug, no_span)); |
04454e1e FG |
212 | } |
213 | ||
f2b60f7d | 214 | Ok(kind_slugs) |
04454e1e FG |
215 | } |
216 | ||
f2b60f7d | 217 | /// Generates the code for a field with no attributes. |
49aad941 | 218 | fn generate_field_set_arg(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { |
2b03887a | 219 | let diag = &self.parent.diag; |
49aad941 FG |
220 | |
221 | let field = binding_info.ast(); | |
222 | let mut field_binding = binding_info.binding.clone(); | |
223 | field_binding.set_span(field.ty.span()); | |
224 | ||
225 | let ident = field.ident.as_ref().unwrap(); | |
226 | let ident = format_ident!("{}", ident); // strip `r#` prefix, if present | |
2b03887a | 227 | |
f2b60f7d FG |
228 | quote! { |
229 | #diag.set_arg( | |
230 | stringify!(#ident), | |
49aad941 | 231 | #field_binding |
f2b60f7d FG |
232 | ); |
233 | } | |
234 | } | |
235 | ||
236 | /// Generates the necessary code for all attributes on a field. | |
237 | fn generate_field_attr_code( | |
04454e1e FG |
238 | &mut self, |
239 | binding: &BindingInfo<'_>, | |
f2b60f7d FG |
240 | kind_stats: KindsStatistics, |
241 | ) -> TokenStream { | |
04454e1e | 242 | let ast = binding.ast(); |
f2b60f7d | 243 | assert!(ast.attrs.len() > 0, "field without attributes generating attr code"); |
04454e1e | 244 | |
f2b60f7d FG |
245 | // Abstract over `Vec<T>` and `Option<T>` fields using `FieldInnerTy`, which will |
246 | // apply the generated code on each element in the `Vec` or `Option`. | |
04454e1e | 247 | let inner_ty = FieldInnerTy::from_type(&ast.ty); |
f2b60f7d FG |
248 | ast.attrs |
249 | .iter() | |
250 | .map(|attr| { | |
2b03887a FG |
251 | // Always allow documentation comments. |
252 | if is_doc_comment(attr) { | |
253 | return quote! {}; | |
254 | } | |
255 | ||
9ffffee4 | 256 | let info = FieldInfo { binding, ty: inner_ty, span: &ast.span() }; |
04454e1e | 257 | |
f2b60f7d | 258 | let generated = self |
2b03887a | 259 | .generate_field_code_inner(kind_stats, attr, info, inner_ty.will_iterate()) |
f2b60f7d | 260 | .unwrap_or_else(|v| v.to_compile_error()); |
04454e1e | 261 | |
f2b60f7d FG |
262 | inner_ty.with(binding, generated) |
263 | }) | |
264 | .collect() | |
265 | } | |
266 | ||
267 | fn generate_field_code_inner( | |
268 | &mut self, | |
269 | kind_stats: KindsStatistics, | |
270 | attr: &Attribute, | |
271 | info: FieldInfo<'_>, | |
2b03887a | 272 | clone_suggestion_code: bool, |
f2b60f7d | 273 | ) -> Result<TokenStream, DiagnosticDeriveError> { |
353b0b11 FG |
274 | match &attr.meta { |
275 | Meta::Path(path) => { | |
276 | self.generate_field_code_inner_path(kind_stats, attr, info, path.clone()) | |
277 | } | |
278 | Meta::List(list) => self.generate_field_code_inner_list( | |
2b03887a FG |
279 | kind_stats, |
280 | attr, | |
281 | info, | |
282 | list, | |
283 | clone_suggestion_code, | |
284 | ), | |
353b0b11 | 285 | _ => throw_invalid_attr!(attr), |
f2b60f7d FG |
286 | } |
287 | } | |
288 | ||
289 | /// Generates the code for a `[Meta::Path]`-like attribute on a field (e.g. `#[primary_span]`). | |
290 | fn generate_field_code_inner_path( | |
291 | &mut self, | |
292 | kind_stats: KindsStatistics, | |
293 | attr: &Attribute, | |
294 | info: FieldInfo<'_>, | |
295 | path: Path, | |
296 | ) -> Result<TokenStream, DiagnosticDeriveError> { | |
297 | let span = attr.span().unwrap(); | |
298 | let ident = &path.segments.last().unwrap().ident; | |
299 | let name = ident.to_string(); | |
300 | let name = name.as_str(); | |
301 | ||
302 | match name { | |
303 | "skip_arg" => Ok(quote! {}), | |
304 | "primary_span" => { | |
305 | if kind_stats.has_multipart_suggestion { | |
353b0b11 | 306 | invalid_attr(attr) |
2b03887a | 307 | .help( |
f2b60f7d FG |
308 | "multipart suggestions use one or more `#[suggestion_part]`s rather \ |
309 | than one `#[primary_span]`", | |
064997fb | 310 | ) |
2b03887a FG |
311 | .emit(); |
312 | } else { | |
313 | report_error_if_not_applied_to_span(attr, &info)?; | |
f2b60f7d | 314 | |
2b03887a FG |
315 | let binding = info.binding.binding.clone(); |
316 | // FIXME(#100717): support `Option<Span>` on `primary_span` like in the | |
317 | // diagnostic derive | |
9ffffee4 | 318 | if !matches!(info.ty, FieldInnerTy::Plain(_)) { |
353b0b11 | 319 | throw_invalid_attr!(attr, |diag| { |
9ffffee4 FG |
320 | let diag = diag.note("there must be exactly one primary span"); |
321 | ||
322 | if kind_stats.has_normal_suggestion { | |
323 | diag.help( | |
324 | "to create a suggestion with multiple spans, \ | |
325 | use `#[multipart_suggestion]` instead", | |
326 | ) | |
327 | } else { | |
328 | diag | |
329 | } | |
330 | }); | |
331 | } | |
332 | ||
2b03887a FG |
333 | self.span_field.set_once(binding, span); |
334 | } | |
f2b60f7d FG |
335 | |
336 | Ok(quote! {}) | |
337 | } | |
338 | "suggestion_part" => { | |
339 | self.has_suggestion_parts = true; | |
340 | ||
341 | if kind_stats.has_multipart_suggestion { | |
342 | span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") | |
343 | .emit(); | |
f2b60f7d | 344 | } else { |
353b0b11 | 345 | invalid_attr(attr) |
2b03887a FG |
346 | .help( |
347 | "`#[suggestion_part(...)]` is only valid in multipart suggestions, \ | |
348 | use `#[primary_span]` instead", | |
349 | ) | |
350 | .emit(); | |
f2b60f7d | 351 | } |
2b03887a FG |
352 | |
353 | Ok(quote! {}) | |
f2b60f7d FG |
354 | } |
355 | "applicability" => { | |
356 | if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion { | |
357 | report_error_if_not_applied_to_applicability(attr, &info)?; | |
358 | ||
2b03887a FG |
359 | if kind_stats.all_applicabilities_static { |
360 | span_err( | |
361 | span, | |
362 | "`#[applicability]` has no effect if all `#[suggestion]`/\ | |
363 | `#[multipart_suggestion]` attributes have a static \ | |
364 | `applicability = \"...\"`", | |
365 | ) | |
366 | .emit(); | |
367 | } | |
f2b60f7d | 368 | let binding = info.binding.binding.clone(); |
2b03887a | 369 | self.applicability.set_once(quote! { #binding }, span); |
f2b60f7d FG |
370 | } else { |
371 | span_err(span, "`#[applicability]` is only valid on suggestions").emit(); | |
372 | } | |
373 | ||
374 | Ok(quote! {}) | |
04454e1e | 375 | } |
2b03887a | 376 | _ => { |
f2b60f7d FG |
377 | let mut span_attrs = vec![]; |
378 | if kind_stats.has_multipart_suggestion { | |
379 | span_attrs.push("suggestion_part"); | |
380 | } | |
381 | if !kind_stats.all_multipart_suggestions { | |
382 | span_attrs.push("primary_span") | |
383 | } | |
2b03887a | 384 | |
353b0b11 | 385 | invalid_attr(attr) |
2b03887a FG |
386 | .help(format!( |
387 | "only `{}`, `applicability` and `skip_arg` are valid field attributes", | |
388 | span_attrs.join(", ") | |
389 | )) | |
390 | .emit(); | |
391 | ||
392 | Ok(quote! {}) | |
393 | } | |
04454e1e | 394 | } |
f2b60f7d | 395 | } |
04454e1e | 396 | |
f2b60f7d FG |
397 | /// Generates the code for a `[Meta::List]`-like attribute on a field (e.g. |
398 | /// `#[suggestion_part(code = "...")]`). | |
399 | fn generate_field_code_inner_list( | |
400 | &mut self, | |
401 | kind_stats: KindsStatistics, | |
402 | attr: &Attribute, | |
403 | info: FieldInfo<'_>, | |
353b0b11 | 404 | list: &MetaList, |
2b03887a | 405 | clone_suggestion_code: bool, |
f2b60f7d FG |
406 | ) -> Result<TokenStream, DiagnosticDeriveError> { |
407 | let span = attr.span().unwrap(); | |
49aad941 FG |
408 | let mut ident = list.path.segments.last().unwrap().ident.clone(); |
409 | ident.set_span(info.ty.span()); | |
f2b60f7d FG |
410 | let name = ident.to_string(); |
411 | let name = name.as_str(); | |
412 | ||
413 | match name { | |
414 | "suggestion_part" => { | |
415 | if !kind_stats.has_multipart_suggestion { | |
353b0b11 | 416 | throw_invalid_attr!(attr, |diag| { |
f2b60f7d FG |
417 | diag.help( |
418 | "`#[suggestion_part(...)]` is only valid in multipart suggestions", | |
419 | ) | |
420 | }) | |
421 | } | |
04454e1e | 422 | |
f2b60f7d FG |
423 | self.has_suggestion_parts = true; |
424 | ||
425 | report_error_if_not_applied_to_span(attr, &info)?; | |
426 | ||
427 | let mut code = None; | |
353b0b11 FG |
428 | |
429 | list.parse_nested_meta(|nested| { | |
430 | if nested.path.is_ident("code") { | |
431 | let code_field = new_code_ident(); | |
432 | let span = nested.path.span().unwrap(); | |
433 | let formatting_init = build_suggestion_code( | |
434 | &code_field, | |
435 | nested, | |
436 | self, | |
437 | AllowMultipleAlternatives::No, | |
438 | ); | |
439 | code.set_once((code_field, formatting_init), span); | |
440 | } else { | |
441 | span_err( | |
442 | nested.path.span().unwrap(), | |
443 | "`code` is the only valid nested attribute", | |
444 | ) | |
445 | .emit(); | |
f2b60f7d | 446 | } |
353b0b11 FG |
447 | Ok(()) |
448 | })?; | |
04454e1e | 449 | |
2b03887a | 450 | let Some((code_field, formatting_init)) = code.value() else { |
f2b60f7d FG |
451 | span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") |
452 | .emit(); | |
453 | return Ok(quote! {}); | |
454 | }; | |
455 | let binding = info.binding; | |
456 | ||
2b03887a FG |
457 | self.formatting_init.extend(formatting_init); |
458 | let code_field = if clone_suggestion_code { | |
459 | quote! { #code_field.clone() } | |
460 | } else { | |
461 | quote! { #code_field } | |
462 | }; | |
463 | Ok(quote! { suggestions.push((#binding, #code_field)); }) | |
f2b60f7d | 464 | } |
353b0b11 | 465 | _ => throw_invalid_attr!(attr, |diag| { |
f2b60f7d FG |
466 | let mut span_attrs = vec![]; |
467 | if kind_stats.has_multipart_suggestion { | |
468 | span_attrs.push("suggestion_part"); | |
469 | } | |
470 | if !kind_stats.all_multipart_suggestions { | |
471 | span_attrs.push("primary_span") | |
472 | } | |
473 | diag.help(format!( | |
474 | "only `{}`, `applicability` and `skip_arg` are valid field attributes", | |
475 | span_attrs.join(", ") | |
476 | )) | |
477 | }), | |
478 | } | |
04454e1e FG |
479 | } |
480 | ||
f2b60f7d FG |
481 | pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> { |
482 | let kind_slugs = self.identify_kind()?; | |
483 | if kind_slugs.is_empty() { | |
2b03887a FG |
484 | if self.is_enum { |
485 | // It's okay for a variant to not be a subdiagnostic at all.. | |
486 | return Ok(quote! {}); | |
487 | } else { | |
488 | // ..but structs should always be _something_. | |
489 | throw_span_err!( | |
490 | self.variant.ast().ident.span().unwrap(), | |
491 | "subdiagnostic kind not specified" | |
492 | ); | |
493 | } | |
04454e1e FG |
494 | }; |
495 | ||
fe692bf9 FG |
496 | let kind_stats: KindsStatistics = |
497 | kind_slugs.iter().map(|(kind, _slug, _no_span)| kind).collect(); | |
04454e1e | 498 | |
f2b60f7d FG |
499 | let init = if kind_stats.has_multipart_suggestion { |
500 | quote! { let mut suggestions = Vec::new(); } | |
501 | } else { | |
502 | quote! {} | |
04454e1e FG |
503 | }; |
504 | ||
f2b60f7d FG |
505 | let attr_args: TokenStream = self |
506 | .variant | |
507 | .bindings() | |
508 | .iter() | |
49aad941 | 509 | .filter(|binding| !should_generate_set_arg(binding.ast())) |
f2b60f7d FG |
510 | .map(|binding| self.generate_field_attr_code(binding, kind_stats)) |
511 | .collect(); | |
512 | ||
2b03887a | 513 | let span_field = self.span_field.value_ref(); |
04454e1e | 514 | |
2b03887a FG |
515 | let diag = &self.parent.diag; |
516 | let f = &self.parent.f; | |
f2b60f7d | 517 | let mut calls = TokenStream::new(); |
fe692bf9 | 518 | for (kind, slug, no_span) in kind_slugs { |
2b03887a | 519 | let message = format_ident!("__message"); |
9ffffee4 FG |
520 | calls.extend( |
521 | quote! { let #message = #f(#diag, crate::fluent_generated::#slug.into()); }, | |
522 | ); | |
2b03887a | 523 | |
fe692bf9 FG |
524 | let name = format_ident!( |
525 | "{}{}", | |
526 | if span_field.is_some() && !no_span { "span_" } else { "" }, | |
527 | kind | |
528 | ); | |
f2b60f7d | 529 | let call = match kind { |
2b03887a FG |
530 | SubdiagnosticKind::Suggestion { |
531 | suggestion_kind, | |
532 | applicability, | |
533 | code_init, | |
534 | code_field, | |
535 | } => { | |
536 | self.formatting_init.extend(code_init); | |
537 | ||
538 | let applicability = applicability | |
539 | .value() | |
540 | .map(|a| quote! { #a }) | |
541 | .or_else(|| self.applicability.take().value()) | |
542 | .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified }); | |
543 | ||
f2b60f7d FG |
544 | if let Some(span) = span_field { |
545 | let style = suggestion_kind.to_suggestion_style(); | |
2b03887a | 546 | quote! { #diag.#name(#span, #message, #code_field, #applicability, #style); } |
f2b60f7d FG |
547 | } else { |
548 | span_err(self.span, "suggestion without `#[primary_span]` field").emit(); | |
549 | quote! { unreachable!(); } | |
550 | } | |
551 | } | |
2b03887a FG |
552 | SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability } => { |
553 | let applicability = applicability | |
554 | .value() | |
555 | .map(|a| quote! { #a }) | |
556 | .or_else(|| self.applicability.take().value()) | |
557 | .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified }); | |
558 | ||
f2b60f7d FG |
559 | if !self.has_suggestion_parts { |
560 | span_err( | |
561 | self.span, | |
562 | "multipart suggestion without any `#[suggestion_part(...)]` fields", | |
563 | ) | |
564 | .emit(); | |
565 | } | |
566 | ||
567 | let style = suggestion_kind.to_suggestion_style(); | |
568 | ||
569 | quote! { #diag.#name(#message, suggestions, #applicability, #style); } | |
570 | } | |
571 | SubdiagnosticKind::Label => { | |
572 | if let Some(span) = span_field { | |
573 | quote! { #diag.#name(#span, #message); } | |
574 | } else { | |
575 | span_err(self.span, "label without `#[primary_span]` field").emit(); | |
576 | quote! { unreachable!(); } | |
577 | } | |
578 | } | |
579 | _ => { | |
fe692bf9 | 580 | if let Some(span) = span_field && !no_span { |
f2b60f7d FG |
581 | quote! { #diag.#name(#span, #message); } |
582 | } else { | |
583 | quote! { #diag.#name(#message); } | |
584 | } | |
585 | } | |
586 | }; | |
2b03887a | 587 | |
f2b60f7d FG |
588 | calls.extend(call); |
589 | } | |
590 | ||
591 | let plain_args: TokenStream = self | |
592 | .variant | |
593 | .bindings() | |
594 | .iter() | |
49aad941 | 595 | .filter(|binding| should_generate_set_arg(binding.ast())) |
f2b60f7d FG |
596 | .map(|binding| self.generate_field_set_arg(binding)) |
597 | .collect(); | |
04454e1e | 598 | |
2b03887a | 599 | let formatting_init = &self.formatting_init; |
04454e1e | 600 | Ok(quote! { |
f2b60f7d | 601 | #init |
2b03887a | 602 | #formatting_init |
f2b60f7d | 603 | #attr_args |
f2b60f7d | 604 | #plain_args |
2b03887a | 605 | #calls |
04454e1e FG |
606 | }) |
607 | } | |
608 | } |