]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | #![deny(unused_must_use)] |
2 | ||
3 | use crate::diagnostics::error::{ | |
4 | invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, | |
5 | DiagnosticDeriveError, | |
6 | }; | |
7 | use crate::diagnostics::utils::{ | |
2b03887a FG |
8 | build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error, |
9 | should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap, | |
10 | HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind, | |
064997fb FG |
11 | }; |
12 | use proc_macro2::{Ident, Span, TokenStream}; | |
13 | use quote::{format_ident, quote}; | |
064997fb | 14 | use syn::{ |
2b03887a | 15 | parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type, |
064997fb | 16 | }; |
2b03887a | 17 | use synstructure::{BindingInfo, Structure, VariantInfo}; |
064997fb | 18 | |
f2b60f7d | 19 | /// What kind of diagnostic is being derived - a fatal/error/warning or a lint? |
2b03887a | 20 | #[derive(Clone, PartialEq, Eq)] |
064997fb | 21 | pub(crate) enum DiagnosticDeriveKind { |
2b03887a | 22 | Diagnostic { handler: syn::Ident }, |
f2b60f7d | 23 | LintDiagnostic, |
064997fb FG |
24 | } |
25 | ||
2b03887a FG |
26 | /// Tracks persistent information required for the entire type when building up individual calls to |
27 | /// diagnostic methods for generated diagnostic derives - both `Diagnostic` for | |
28 | /// fatal/errors/warnings and `LintDiagnostic` for lints. | |
064997fb FG |
29 | pub(crate) struct DiagnosticDeriveBuilder { |
30 | /// The identifier to use for the generated `DiagnosticBuilder` instance. | |
31 | pub diag: syn::Ident, | |
2b03887a FG |
32 | /// Kind of diagnostic that should be derived. |
33 | pub kind: DiagnosticDeriveKind, | |
34 | } | |
35 | ||
36 | /// Tracks persistent information required for a specific variant when building up individual calls | |
37 | /// to diagnostic methods for generated diagnostic derives - both `Diagnostic` for | |
38 | /// fatal/errors/warnings and `LintDiagnostic` for lints. | |
39 | pub(crate) struct DiagnosticDeriveVariantBuilder<'parent> { | |
40 | /// The parent builder for the entire type. | |
41 | pub parent: &'parent DiagnosticDeriveBuilder, | |
42 | ||
43 | /// Initialization of format strings for code suggestions. | |
44 | pub formatting_init: TokenStream, | |
45 | ||
46 | /// Span of the struct or the enum variant. | |
47 | pub span: proc_macro::Span, | |
064997fb FG |
48 | |
49 | /// Store a map of field name to its corresponding field. This is built on construction of the | |
50 | /// derive builder. | |
2b03887a | 51 | pub field_map: FieldMap, |
064997fb | 52 | |
064997fb FG |
53 | /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that |
54 | /// has the actual diagnostic message. | |
2b03887a | 55 | pub slug: SpannedOption<Path>, |
064997fb FG |
56 | /// Error codes are a optional part of the struct attribute - this is only set to detect |
57 | /// multiple specifications. | |
2b03887a | 58 | pub code: SpannedOption<()>, |
064997fb FG |
59 | } |
60 | ||
2b03887a | 61 | impl<'a> HasFieldMap for DiagnosticDeriveVariantBuilder<'a> { |
064997fb | 62 | fn get_field_binding(&self, field: &String) -> Option<&TokenStream> { |
2b03887a | 63 | self.field_map.get(field) |
064997fb FG |
64 | } |
65 | } | |
66 | ||
67 | impl DiagnosticDeriveBuilder { | |
2b03887a FG |
68 | /// Call `f` for the struct or for each variant of the enum, returning a `TokenStream` with the |
69 | /// tokens from `f` wrapped in an `match` expression. Emits errors for use of derive on unions | |
70 | /// or attributes on the type itself when input is an enum. | |
71 | pub fn each_variant<'s, F>(&mut self, structure: &mut Structure<'s>, f: F) -> TokenStream | |
72 | where | |
73 | F: for<'a, 'v> Fn(DiagnosticDeriveVariantBuilder<'a>, &VariantInfo<'v>) -> TokenStream, | |
74 | { | |
064997fb | 75 | let ast = structure.ast(); |
2b03887a FG |
76 | let span = ast.span().unwrap(); |
77 | match ast.data { | |
78 | syn::Data::Struct(..) | syn::Data::Enum(..) => (), | |
79 | syn::Data::Union(..) => { | |
80 | span_err(span, "diagnostic derives can only be used on structs and enums"); | |
81 | } | |
82 | } | |
83 | ||
84 | if matches!(ast.data, syn::Data::Enum(..)) { | |
85 | for attr in &ast.attrs { | |
86 | span_err( | |
87 | attr.span().unwrap(), | |
88 | "unsupported type attribute for diagnostic derive enum", | |
89 | ) | |
90 | .emit(); | |
91 | } | |
92 | } | |
93 | ||
94 | structure.bind_with(|_| synstructure::BindStyle::Move); | |
95 | let variants = structure.each_variant(|variant| { | |
96 | let span = match structure.ast().data { | |
97 | syn::Data::Struct(..) => span, | |
98 | // There isn't a good way to get the span of the variant, so the variant's | |
99 | // name will need to do. | |
100 | _ => variant.ast().ident.span().unwrap(), | |
101 | }; | |
102 | let builder = DiagnosticDeriveVariantBuilder { | |
487cf647 | 103 | parent: self, |
2b03887a FG |
104 | span, |
105 | field_map: build_field_mapping(variant), | |
106 | formatting_init: TokenStream::new(), | |
107 | slug: None, | |
108 | code: None, | |
109 | }; | |
110 | f(builder, variant) | |
111 | }); | |
112 | ||
113 | quote! { | |
114 | match self { | |
115 | #variants | |
116 | } | |
117 | } | |
118 | } | |
119 | } | |
120 | ||
121 | impl<'a> DiagnosticDeriveVariantBuilder<'a> { | |
122 | /// Generates calls to `code` and similar functions based on the attributes on the type or | |
123 | /// variant. | |
124 | pub fn preamble<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream { | |
125 | let ast = variant.ast(); | |
064997fb FG |
126 | let attrs = &ast.attrs; |
127 | let preamble = attrs.iter().map(|attr| { | |
128 | self.generate_structure_code_for_attr(attr).unwrap_or_else(|v| v.to_compile_error()) | |
129 | }); | |
130 | ||
131 | quote! { | |
132 | #(#preamble)*; | |
133 | } | |
134 | } | |
135 | ||
2b03887a FG |
136 | /// Generates calls to `span_label` and similar functions based on the attributes on fields or |
137 | /// calls to `set_arg` when no attributes are present. | |
138 | pub fn body<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream { | |
139 | let mut body = quote! {}; | |
140 | // Generate `set_arg` calls first.. | |
141 | for binding in variant.bindings().iter().filter(|bi| should_generate_set_arg(bi.ast())) { | |
142 | body.extend(self.generate_field_code(binding)); | |
143 | } | |
144 | // ..and then subdiagnostic additions. | |
145 | for binding in variant.bindings().iter().filter(|bi| !should_generate_set_arg(bi.ast())) { | |
146 | body.extend(self.generate_field_attrs_code(binding)); | |
147 | } | |
148 | body | |
064997fb FG |
149 | } |
150 | ||
2b03887a FG |
151 | /// Parse a `SubdiagnosticKind` from an `Attribute`. |
152 | fn parse_subdiag_attribute( | |
153 | &self, | |
154 | attr: &Attribute, | |
155 | ) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> { | |
156 | let Some((subdiag, slug)) = SubdiagnosticKind::from_attr(attr, self)? else { | |
157 | // Some attributes aren't errors - like documentation comments - but also aren't | |
158 | // subdiagnostics. | |
159 | return Ok(None); | |
160 | }; | |
064997fb | 161 | |
2b03887a FG |
162 | if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag { |
163 | let meta = attr.parse_meta()?; | |
164 | throw_invalid_attr!(attr, &meta, |diag| diag | |
165 | .help("consider creating a `Subdiagnostic` instead")); | |
166 | } | |
167 | ||
168 | let slug = slug.unwrap_or_else(|| match subdiag { | |
169 | SubdiagnosticKind::Label => parse_quote! { _subdiag::label }, | |
170 | SubdiagnosticKind::Note => parse_quote! { _subdiag::note }, | |
171 | SubdiagnosticKind::Help => parse_quote! { _subdiag::help }, | |
172 | SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn }, | |
173 | SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion }, | |
174 | SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), | |
175 | }); | |
176 | ||
177 | Ok(Some((subdiag, slug))) | |
064997fb FG |
178 | } |
179 | ||
180 | /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct | |
f2b60f7d | 181 | /// attributes like `#[diag(..)]`, such as the slug and error code. Generates |
064997fb FG |
182 | /// diagnostic builder calls for setting error code and creating note/help messages. |
183 | fn generate_structure_code_for_attr( | |
184 | &mut self, | |
185 | attr: &Attribute, | |
186 | ) -> Result<TokenStream, DiagnosticDeriveError> { | |
2b03887a FG |
187 | let diag = &self.parent.diag; |
188 | ||
189 | // Always allow documentation comments. | |
190 | if is_doc_comment(attr) { | |
191 | return Ok(quote! {}); | |
192 | } | |
064997fb FG |
193 | |
194 | let name = attr.path.segments.last().unwrap().ident.to_string(); | |
195 | let name = name.as_str(); | |
196 | let meta = attr.parse_meta()?; | |
197 | ||
2b03887a FG |
198 | if name == "diag" { |
199 | let Meta::List(MetaList { ref nested, .. }) = meta else { | |
200 | throw_invalid_attr!( | |
201 | attr, | |
202 | &meta | |
203 | ); | |
204 | }; | |
064997fb | 205 | |
2b03887a | 206 | let mut nested_iter = nested.into_iter().peekable(); |
064997fb | 207 | |
2b03887a FG |
208 | match nested_iter.peek() { |
209 | Some(NestedMeta::Meta(Meta::Path(slug))) => { | |
210 | self.slug.set_once(slug.clone(), slug.span().unwrap()); | |
211 | nested_iter.next(); | |
064997fb | 212 | } |
2b03887a | 213 | Some(NestedMeta::Meta(Meta::NameValue { .. })) => {} |
487cf647 | 214 | Some(nested_attr) => throw_invalid_nested_attr!(attr, nested_attr, |diag| diag |
2b03887a FG |
215 | .help("a diagnostic slug is required as the first argument")), |
216 | None => throw_invalid_attr!(attr, &meta, |diag| diag | |
217 | .help("a diagnostic slug is required as the first argument")), | |
064997fb | 218 | }; |
064997fb | 219 | |
2b03887a FG |
220 | // Remaining attributes are optional, only `code = ".."` at the moment. |
221 | let mut tokens = TokenStream::new(); | |
222 | for nested_attr in nested_iter { | |
223 | let (value, path) = match nested_attr { | |
224 | NestedMeta::Meta(Meta::NameValue(MetaNameValue { | |
225 | lit: syn::Lit::Str(value), | |
226 | path, | |
227 | .. | |
228 | })) => (value, path), | |
229 | NestedMeta::Meta(Meta::Path(_)) => { | |
487cf647 | 230 | invalid_nested_attr(attr, nested_attr) |
2b03887a FG |
231 | .help("diagnostic slug must be the first argument") |
232 | .emit(); | |
233 | continue; | |
234 | } | |
235 | _ => { | |
487cf647 | 236 | invalid_nested_attr(attr, nested_attr).emit(); |
2b03887a FG |
237 | continue; |
238 | } | |
239 | }; | |
064997fb | 240 | |
2b03887a FG |
241 | let nested_name = path.segments.last().unwrap().ident.to_string(); |
242 | // Struct attributes are only allowed to be applied once, and the diagnostic | |
243 | // changes will be set in the initialisation code. | |
244 | let span = value.span().unwrap(); | |
064997fb FG |
245 | match nested_name.as_str() { |
246 | "code" => { | |
2b03887a FG |
247 | self.code.set_once((), span); |
248 | ||
249 | let code = value.value(); | |
250 | tokens.extend(quote! { | |
064997fb FG |
251 | #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); |
252 | }); | |
253 | } | |
487cf647 | 254 | _ => invalid_nested_attr(attr, nested_attr) |
064997fb FG |
255 | .help("only `code` is a valid nested attributes following the slug") |
256 | .emit(), | |
257 | } | |
064997fb | 258 | } |
2b03887a | 259 | return Ok(tokens); |
064997fb FG |
260 | } |
261 | ||
2b03887a FG |
262 | let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else { |
263 | // Some attributes aren't errors - like documentation comments - but also aren't | |
264 | // subdiagnostics. | |
265 | return Ok(quote! {}); | |
266 | }; | |
267 | let fn_ident = format_ident!("{}", subdiag); | |
268 | match subdiag { | |
269 | SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => { | |
270 | Ok(self.add_subdiagnostic(&fn_ident, slug)) | |
271 | } | |
272 | SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => { | |
273 | throw_invalid_attr!(attr, &meta, |diag| diag | |
274 | .help("`#[label]` and `#[suggestion]` can only be applied to fields")); | |
275 | } | |
276 | SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), | |
277 | } | |
064997fb FG |
278 | } |
279 | ||
2b03887a FG |
280 | fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { |
281 | let diag = &self.parent.diag; | |
282 | ||
064997fb FG |
283 | let field = binding_info.ast(); |
284 | let field_binding = &binding_info.binding; | |
285 | ||
2b03887a FG |
286 | let ident = field.ident.as_ref().unwrap(); |
287 | let ident = format_ident!("{}", ident); // strip `r#` prefix, if present | |
288 | ||
289 | quote! { | |
290 | #diag.set_arg( | |
291 | stringify!(#ident), | |
292 | #field_binding | |
293 | ); | |
064997fb | 294 | } |
2b03887a FG |
295 | } |
296 | ||
297 | fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { | |
298 | let field = binding_info.ast(); | |
299 | let field_binding = &binding_info.binding; | |
064997fb | 300 | |
064997fb FG |
301 | let inner_ty = FieldInnerTy::from_type(&field.ty); |
302 | ||
303 | field | |
304 | .attrs | |
305 | .iter() | |
306 | .map(move |attr| { | |
2b03887a FG |
307 | // Always allow documentation comments. |
308 | if is_doc_comment(attr) { | |
309 | return quote! {}; | |
310 | } | |
311 | ||
064997fb FG |
312 | let name = attr.path.segments.last().unwrap().ident.to_string(); |
313 | let needs_clone = | |
314 | name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_)); | |
315 | let (binding, needs_destructure) = if needs_clone { | |
316 | // `primary_span` can accept a `Vec<Span>` so don't destructure that. | |
317 | (quote! { #field_binding.clone() }, false) | |
064997fb | 318 | } else { |
2b03887a | 319 | (quote! { #field_binding }, true) |
064997fb FG |
320 | }; |
321 | ||
322 | let generated_code = self | |
323 | .generate_inner_field_code( | |
324 | attr, | |
9ffffee4 | 325 | FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() }, |
064997fb FG |
326 | binding, |
327 | ) | |
328 | .unwrap_or_else(|v| v.to_compile_error()); | |
329 | ||
330 | if needs_destructure { | |
331 | inner_ty.with(field_binding, generated_code) | |
332 | } else { | |
333 | generated_code | |
334 | } | |
335 | }) | |
336 | .collect() | |
337 | } | |
338 | ||
339 | fn generate_inner_field_code( | |
340 | &mut self, | |
341 | attr: &Attribute, | |
342 | info: FieldInfo<'_>, | |
343 | binding: TokenStream, | |
344 | ) -> Result<TokenStream, DiagnosticDeriveError> { | |
2b03887a | 345 | let diag = &self.parent.diag; |
064997fb FG |
346 | let meta = attr.parse_meta()?; |
347 | ||
348 | let ident = &attr.path.segments.last().unwrap().ident; | |
349 | let name = ident.to_string(); | |
2b03887a FG |
350 | match (&meta, name.as_str()) { |
351 | // Don't need to do anything - by virtue of the attribute existing, the | |
352 | // `set_arg` call will not be generated. | |
353 | (Meta::Path(_), "skip_arg") => return Ok(quote! {}), | |
354 | (Meta::Path(_), "primary_span") => { | |
355 | match self.parent.kind { | |
356 | DiagnosticDeriveKind::Diagnostic { .. } => { | |
f2b60f7d FG |
357 | report_error_if_not_applied_to_span(attr, &info)?; |
358 | ||
2b03887a | 359 | return Ok(quote! { |
f2b60f7d | 360 | #diag.set_span(#binding); |
2b03887a | 361 | }); |
f2b60f7d FG |
362 | } |
363 | DiagnosticDeriveKind::LintDiagnostic => { | |
364 | throw_invalid_attr!(attr, &meta, |diag| { | |
365 | diag.help("the `primary_span` field attribute is not valid for lint diagnostics") | |
366 | }) | |
367 | } | |
368 | } | |
064997fb | 369 | } |
2b03887a | 370 | (Meta::Path(_), "subdiagnostic") => { |
9c376795 FG |
371 | if FieldInnerTy::from_type(&info.binding.ast().ty).will_iterate() { |
372 | let DiagnosticDeriveKind::Diagnostic { handler } = &self.parent.kind else { | |
373 | // No eager translation for lints. | |
374 | return Ok(quote! { #diag.subdiagnostic(#binding); }); | |
375 | }; | |
376 | return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); }); | |
377 | } else { | |
378 | return Ok(quote! { #diag.subdiagnostic(#binding); }); | |
379 | } | |
064997fb | 380 | } |
2b03887a | 381 | (Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => { |
9c376795 FG |
382 | if nested.len() == 1 |
383 | && let Some(NestedMeta::Meta(Meta::Path(path))) = nested.first() | |
384 | && path.is_ident("eager") { | |
385 | let handler = match &self.parent.kind { | |
386 | DiagnosticDeriveKind::Diagnostic { handler } => handler, | |
387 | DiagnosticDeriveKind::LintDiagnostic => { | |
388 | throw_invalid_attr!(attr, &meta, |diag| { | |
389 | diag.help("eager subdiagnostics are not supported on lints") | |
390 | }) | |
391 | } | |
392 | }; | |
393 | return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); }); | |
394 | } else { | |
2b03887a FG |
395 | throw_invalid_attr!(attr, &meta, |diag| { |
396 | diag.help( | |
397 | "`eager` is the only supported nested attribute for `subdiagnostic`", | |
398 | ) | |
399 | }) | |
400 | } | |
064997fb | 401 | } |
2b03887a | 402 | _ => (), |
064997fb FG |
403 | } |
404 | ||
2b03887a FG |
405 | let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else { |
406 | // Some attributes aren't errors - like documentation comments - but also aren't | |
407 | // subdiagnostics. | |
408 | return Ok(quote! {}); | |
064997fb | 409 | }; |
2b03887a FG |
410 | let fn_ident = format_ident!("{}", subdiag); |
411 | match subdiag { | |
412 | SubdiagnosticKind::Label => { | |
064997fb | 413 | report_error_if_not_applied_to_span(attr, &info)?; |
2b03887a | 414 | Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) |
064997fb | 415 | } |
2b03887a | 416 | SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => { |
9ffffee4 | 417 | if type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) { |
2b03887a | 418 | Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) |
9ffffee4 | 419 | } else if type_is_unit(info.ty.inner_type()) { |
2b03887a FG |
420 | Ok(self.add_subdiagnostic(&fn_ident, slug)) |
421 | } else { | |
422 | report_type_error(attr, "`Span` or `()`")? | |
423 | } | |
064997fb | 424 | } |
2b03887a FG |
425 | SubdiagnosticKind::Suggestion { |
426 | suggestion_kind, | |
427 | applicability: static_applicability, | |
428 | code_field, | |
429 | code_init, | |
430 | } => { | |
9ffffee4 FG |
431 | if let FieldInnerTy::Vec(_) = info.ty { |
432 | throw_invalid_attr!(attr, &meta, |diag| { | |
433 | diag | |
434 | .note("`#[suggestion(...)]` applied to `Vec` field is ambiguous") | |
435 | .help("to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]`") | |
436 | .help("to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]`") | |
437 | }); | |
438 | } | |
439 | ||
2b03887a FG |
440 | let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?; |
441 | ||
442 | if let Some((static_applicability, span)) = static_applicability { | |
443 | applicability.set_once(quote! { #static_applicability }, span); | |
064997fb | 444 | } |
2b03887a FG |
445 | |
446 | let applicability = applicability | |
447 | .value() | |
448 | .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified }); | |
449 | let style = suggestion_kind.to_suggestion_style(); | |
450 | ||
451 | self.formatting_init.extend(code_init); | |
452 | Ok(quote! { | |
453 | #diag.span_suggestions_with_style( | |
454 | #span_field, | |
9ffffee4 | 455 | crate::fluent_generated::#slug, |
2b03887a FG |
456 | #code_field, |
457 | #applicability, | |
458 | #style | |
459 | ); | |
460 | }) | |
064997fb | 461 | } |
2b03887a | 462 | SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), |
064997fb | 463 | } |
064997fb FG |
464 | } |
465 | ||
466 | /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug | |
467 | /// and `fluent_attr_identifier`. | |
468 | fn add_spanned_subdiagnostic( | |
469 | &self, | |
470 | field_binding: TokenStream, | |
471 | kind: &Ident, | |
472 | fluent_attr_identifier: Path, | |
473 | ) -> TokenStream { | |
2b03887a | 474 | let diag = &self.parent.diag; |
064997fb FG |
475 | let fn_name = format_ident!("span_{}", kind); |
476 | quote! { | |
477 | #diag.#fn_name( | |
478 | #field_binding, | |
9ffffee4 | 479 | crate::fluent_generated::#fluent_attr_identifier |
064997fb FG |
480 | ); |
481 | } | |
482 | } | |
483 | ||
484 | /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug | |
485 | /// and `fluent_attr_identifier`. | |
486 | fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream { | |
2b03887a | 487 | let diag = &self.parent.diag; |
064997fb | 488 | quote! { |
9ffffee4 | 489 | #diag.#kind(crate::fluent_generated::#fluent_attr_identifier); |
064997fb FG |
490 | } |
491 | } | |
492 | ||
493 | fn span_and_applicability_of_ty( | |
494 | &self, | |
495 | info: FieldInfo<'_>, | |
2b03887a | 496 | ) -> Result<(TokenStream, SpannedOption<TokenStream>), DiagnosticDeriveError> { |
9ffffee4 | 497 | match &info.ty.inner_type() { |
064997fb FG |
498 | // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`. |
499 | ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => { | |
500 | let binding = &info.binding.binding; | |
2b03887a | 501 | Ok((quote!(#binding), None)) |
064997fb FG |
502 | } |
503 | // If `ty` is `(Span, Applicability)` then return tokens accessing those. | |
504 | Type::Tuple(tup) => { | |
505 | let mut span_idx = None; | |
506 | let mut applicability_idx = None; | |
507 | ||
2b03887a FG |
508 | fn type_err(span: &Span) -> Result<!, DiagnosticDeriveError> { |
509 | span_err(span.unwrap(), "wrong types for suggestion") | |
510 | .help( | |
511 | "`#[suggestion(...)]` on a tuple field must be applied to fields \ | |
512 | of type `(Span, Applicability)`", | |
513 | ) | |
514 | .emit(); | |
515 | Err(DiagnosticDeriveError::ErrorHandled) | |
516 | } | |
517 | ||
064997fb FG |
518 | for (idx, elem) in tup.elems.iter().enumerate() { |
519 | if type_matches_path(elem, &["rustc_span", "Span"]) { | |
2b03887a | 520 | span_idx.set_once(syn::Index::from(idx), elem.span().unwrap()); |
064997fb | 521 | } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) { |
2b03887a FG |
522 | applicability_idx.set_once(syn::Index::from(idx), elem.span().unwrap()); |
523 | } else { | |
524 | type_err(&elem.span())?; | |
064997fb FG |
525 | } |
526 | } | |
527 | ||
2b03887a FG |
528 | let Some((span_idx, _)) = span_idx else { |
529 | type_err(&tup.span())?; | |
530 | }; | |
531 | let Some((applicability_idx, applicability_span)) = applicability_idx else { | |
532 | type_err(&tup.span())?; | |
533 | }; | |
534 | let binding = &info.binding.binding; | |
535 | let span = quote!(#binding.#span_idx); | |
536 | let applicability = quote!(#binding.#applicability_idx); | |
064997fb | 537 | |
2b03887a | 538 | Ok((span, Some((applicability, applicability_span)))) |
064997fb FG |
539 | } |
540 | // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error. | |
541 | _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| { | |
542 | diag.help( | |
543 | "`#[suggestion(...)]` should be applied to fields of type `Span` or \ | |
544 | `(Span, Applicability)`", | |
545 | ) | |
546 | }), | |
547 | } | |
548 | } | |
549 | } |