]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | #![deny(unused_must_use)] |
2 | ||
3 | use crate::diagnostics::error::{ | |
353b0b11 | 4 | span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError, |
064997fb FG |
5 | }; |
6 | use crate::diagnostics::utils::{ | |
2b03887a | 7 | build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error, |
353b0b11 FG |
8 | should_generate_set_arg, type_is_bool, type_is_unit, type_matches_path, FieldInfo, |
9 | FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind, | |
064997fb FG |
10 | }; |
11 | use proc_macro2::{Ident, Span, TokenStream}; | |
49aad941 | 12 | use quote::{format_ident, quote, quote_spanned}; |
353b0b11 FG |
13 | use syn::Token; |
14 | use syn::{parse_quote, spanned::Spanned, Attribute, Meta, Path, Type}; | |
2b03887a | 15 | use synstructure::{BindingInfo, Structure, VariantInfo}; |
064997fb | 16 | |
fe692bf9 FG |
17 | use super::utils::SubdiagnosticVariant; |
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(..) => { | |
353b0b11 | 80 | span_err(span, "diagnostic derives can only be used on structs and enums").emit(); |
2b03887a FG |
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. | |
353b0b11 | 124 | pub fn preamble(&mut self, variant: &VariantInfo<'_>) -> TokenStream { |
2b03887a | 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. | |
353b0b11 | 138 | pub fn body(&mut self, variant: &VariantInfo<'_>) -> TokenStream { |
2b03887a FG |
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, | |
fe692bf9 FG |
155 | ) -> Result<Option<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> { |
156 | let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, self)? else { | |
2b03887a FG |
157 | // Some attributes aren't errors - like documentation comments - but also aren't |
158 | // subdiagnostics. | |
159 | return Ok(None); | |
160 | }; | |
064997fb | 161 | |
fe692bf9 | 162 | if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag.kind { |
353b0b11 | 163 | throw_invalid_attr!(attr, |diag| diag |
2b03887a FG |
164 | .help("consider creating a `Subdiagnostic` instead")); |
165 | } | |
166 | ||
fe692bf9 | 167 | let slug = subdiag.slug.unwrap_or_else(|| match subdiag.kind { |
2b03887a FG |
168 | SubdiagnosticKind::Label => parse_quote! { _subdiag::label }, |
169 | SubdiagnosticKind::Note => parse_quote! { _subdiag::note }, | |
170 | SubdiagnosticKind::Help => parse_quote! { _subdiag::help }, | |
171 | SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn }, | |
172 | SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion }, | |
173 | SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), | |
174 | }); | |
175 | ||
fe692bf9 | 176 | Ok(Some((subdiag.kind, slug, subdiag.no_span))) |
064997fb FG |
177 | } |
178 | ||
179 | /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct | |
f2b60f7d | 180 | /// attributes like `#[diag(..)]`, such as the slug and error code. Generates |
064997fb FG |
181 | /// diagnostic builder calls for setting error code and creating note/help messages. |
182 | fn generate_structure_code_for_attr( | |
183 | &mut self, | |
184 | attr: &Attribute, | |
185 | ) -> Result<TokenStream, DiagnosticDeriveError> { | |
2b03887a FG |
186 | let diag = &self.parent.diag; |
187 | ||
188 | // Always allow documentation comments. | |
189 | if is_doc_comment(attr) { | |
190 | return Ok(quote! {}); | |
191 | } | |
064997fb | 192 | |
353b0b11 | 193 | let name = attr.path().segments.last().unwrap().ident.to_string(); |
064997fb | 194 | let name = name.as_str(); |
064997fb | 195 | |
353b0b11 | 196 | let mut first = true; |
064997fb | 197 | |
353b0b11 FG |
198 | if name == "diag" { |
199 | let mut tokens = TokenStream::new(); | |
200 | attr.parse_nested_meta(|nested| { | |
201 | let path = &nested.path; | |
064997fb | 202 | |
353b0b11 FG |
203 | if first && (nested.input.is_empty() || nested.input.peek(Token![,])) { |
204 | self.slug.set_once(path.clone(), path.span().unwrap()); | |
205 | first = false; | |
206 | return Ok(()) | |
064997fb | 207 | } |
064997fb | 208 | |
353b0b11 FG |
209 | first = false; |
210 | ||
211 | let Ok(nested) = nested.value() else { | |
212 | span_err(nested.input.span().unwrap(), "diagnostic slug must be the first argument").emit(); | |
213 | return Ok(()) | |
2b03887a | 214 | }; |
064997fb | 215 | |
353b0b11 FG |
216 | if path.is_ident("code") { |
217 | self.code.set_once((), path.span().unwrap()); | |
218 | ||
219 | let code = nested.parse::<syn::LitStr>()?; | |
220 | tokens.extend(quote! { | |
221 | #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); | |
222 | }); | |
223 | } else { | |
224 | span_err(path.span().unwrap(), "unknown argument").note("only the `code` parameter is valid after the slug").emit(); | |
225 | ||
226 | // consume the buffer so we don't have syntax errors from syn | |
227 | let _ = nested.parse::<TokenStream>(); | |
064997fb | 228 | } |
353b0b11 FG |
229 | Ok(()) |
230 | })?; | |
2b03887a | 231 | return Ok(tokens); |
064997fb FG |
232 | } |
233 | ||
fe692bf9 | 234 | let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else { |
2b03887a FG |
235 | // Some attributes aren't errors - like documentation comments - but also aren't |
236 | // subdiagnostics. | |
237 | return Ok(quote! {}); | |
238 | }; | |
239 | let fn_ident = format_ident!("{}", subdiag); | |
240 | match subdiag { | |
241 | SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => { | |
242 | Ok(self.add_subdiagnostic(&fn_ident, slug)) | |
243 | } | |
244 | SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => { | |
353b0b11 | 245 | throw_invalid_attr!(attr, |diag| diag |
2b03887a FG |
246 | .help("`#[label]` and `#[suggestion]` can only be applied to fields")); |
247 | } | |
248 | SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), | |
249 | } | |
064997fb FG |
250 | } |
251 | ||
2b03887a FG |
252 | fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { |
253 | let diag = &self.parent.diag; | |
254 | ||
064997fb | 255 | let field = binding_info.ast(); |
49aad941 FG |
256 | let mut field_binding = binding_info.binding.clone(); |
257 | field_binding.set_span(field.ty.span()); | |
064997fb | 258 | |
2b03887a FG |
259 | let ident = field.ident.as_ref().unwrap(); |
260 | let ident = format_ident!("{}", ident); // strip `r#` prefix, if present | |
261 | ||
262 | quote! { | |
263 | #diag.set_arg( | |
264 | stringify!(#ident), | |
265 | #field_binding | |
266 | ); | |
064997fb | 267 | } |
2b03887a FG |
268 | } |
269 | ||
270 | fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { | |
271 | let field = binding_info.ast(); | |
272 | let field_binding = &binding_info.binding; | |
064997fb | 273 | |
064997fb FG |
274 | let inner_ty = FieldInnerTy::from_type(&field.ty); |
275 | ||
276 | field | |
277 | .attrs | |
278 | .iter() | |
279 | .map(move |attr| { | |
2b03887a FG |
280 | // Always allow documentation comments. |
281 | if is_doc_comment(attr) { | |
282 | return quote! {}; | |
283 | } | |
284 | ||
353b0b11 | 285 | let name = attr.path().segments.last().unwrap().ident.to_string(); |
064997fb FG |
286 | let needs_clone = |
287 | name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_)); | |
288 | let (binding, needs_destructure) = if needs_clone { | |
289 | // `primary_span` can accept a `Vec<Span>` so don't destructure that. | |
49aad941 | 290 | (quote_spanned! {inner_ty.span()=> #field_binding.clone() }, false) |
064997fb | 291 | } else { |
49aad941 | 292 | (quote_spanned! {inner_ty.span()=> #field_binding }, true) |
064997fb FG |
293 | }; |
294 | ||
295 | let generated_code = self | |
296 | .generate_inner_field_code( | |
297 | attr, | |
9ffffee4 | 298 | FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() }, |
064997fb FG |
299 | binding, |
300 | ) | |
301 | .unwrap_or_else(|v| v.to_compile_error()); | |
302 | ||
303 | if needs_destructure { | |
304 | inner_ty.with(field_binding, generated_code) | |
305 | } else { | |
306 | generated_code | |
307 | } | |
308 | }) | |
309 | .collect() | |
310 | } | |
311 | ||
312 | fn generate_inner_field_code( | |
313 | &mut self, | |
314 | attr: &Attribute, | |
315 | info: FieldInfo<'_>, | |
316 | binding: TokenStream, | |
317 | ) -> Result<TokenStream, DiagnosticDeriveError> { | |
2b03887a | 318 | let diag = &self.parent.diag; |
064997fb | 319 | |
353b0b11 | 320 | let ident = &attr.path().segments.last().unwrap().ident; |
064997fb | 321 | let name = ident.to_string(); |
353b0b11 | 322 | match (&attr.meta, name.as_str()) { |
2b03887a FG |
323 | // Don't need to do anything - by virtue of the attribute existing, the |
324 | // `set_arg` call will not be generated. | |
325 | (Meta::Path(_), "skip_arg") => return Ok(quote! {}), | |
326 | (Meta::Path(_), "primary_span") => { | |
327 | match self.parent.kind { | |
328 | DiagnosticDeriveKind::Diagnostic { .. } => { | |
f2b60f7d FG |
329 | report_error_if_not_applied_to_span(attr, &info)?; |
330 | ||
2b03887a | 331 | return Ok(quote! { |
f2b60f7d | 332 | #diag.set_span(#binding); |
2b03887a | 333 | }); |
f2b60f7d FG |
334 | } |
335 | DiagnosticDeriveKind::LintDiagnostic => { | |
353b0b11 | 336 | throw_invalid_attr!(attr, |diag| { |
f2b60f7d FG |
337 | diag.help("the `primary_span` field attribute is not valid for lint diagnostics") |
338 | }) | |
339 | } | |
340 | } | |
064997fb | 341 | } |
2b03887a | 342 | (Meta::Path(_), "subdiagnostic") => { |
9c376795 FG |
343 | if FieldInnerTy::from_type(&info.binding.ast().ty).will_iterate() { |
344 | let DiagnosticDeriveKind::Diagnostic { handler } = &self.parent.kind else { | |
345 | // No eager translation for lints. | |
346 | return Ok(quote! { #diag.subdiagnostic(#binding); }); | |
347 | }; | |
348 | return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); }); | |
349 | } else { | |
350 | return Ok(quote! { #diag.subdiagnostic(#binding); }); | |
351 | } | |
064997fb | 352 | } |
353b0b11 FG |
353 | (Meta::List(meta_list), "subdiagnostic") => { |
354 | let err = || { | |
355 | span_err( | |
356 | meta_list.span().unwrap(), | |
357 | "`eager` is the only supported nested attribute for `subdiagnostic`", | |
358 | ) | |
359 | .emit(); | |
360 | }; | |
361 | ||
362 | let Ok(p): Result<Path, _> = meta_list.parse_args() else { | |
363 | err(); | |
364 | return Ok(quote! {}); | |
365 | }; | |
366 | ||
367 | if !p.is_ident("eager") { | |
368 | err(); | |
369 | return Ok(quote! {}); | |
2b03887a | 370 | } |
353b0b11 FG |
371 | |
372 | let handler = match &self.parent.kind { | |
373 | DiagnosticDeriveKind::Diagnostic { handler } => handler, | |
374 | DiagnosticDeriveKind::LintDiagnostic => { | |
375 | throw_invalid_attr!(attr, |diag| { | |
376 | diag.help("eager subdiagnostics are not supported on lints") | |
377 | }) | |
378 | } | |
379 | }; | |
380 | return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); }); | |
064997fb | 381 | } |
2b03887a | 382 | _ => (), |
064997fb FG |
383 | } |
384 | ||
fe692bf9 | 385 | let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else { |
2b03887a FG |
386 | // Some attributes aren't errors - like documentation comments - but also aren't |
387 | // subdiagnostics. | |
388 | return Ok(quote! {}); | |
064997fb | 389 | }; |
2b03887a FG |
390 | let fn_ident = format_ident!("{}", subdiag); |
391 | match subdiag { | |
392 | SubdiagnosticKind::Label => { | |
064997fb | 393 | report_error_if_not_applied_to_span(attr, &info)?; |
2b03887a | 394 | Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) |
064997fb | 395 | } |
2b03887a | 396 | SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => { |
353b0b11 FG |
397 | let inner = info.ty.inner_type(); |
398 | if type_matches_path(inner, &["rustc_span", "Span"]) | |
399 | || type_matches_path(inner, &["rustc_span", "MultiSpan"]) | |
400 | { | |
2b03887a | 401 | Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug)) |
353b0b11 FG |
402 | } else if type_is_unit(inner) |
403 | || (matches!(info.ty, FieldInnerTy::Plain(_)) && type_is_bool(inner)) | |
404 | { | |
2b03887a FG |
405 | Ok(self.add_subdiagnostic(&fn_ident, slug)) |
406 | } else { | |
353b0b11 | 407 | report_type_error(attr, "`Span`, `MultiSpan`, `bool` or `()`")? |
2b03887a | 408 | } |
064997fb | 409 | } |
2b03887a FG |
410 | SubdiagnosticKind::Suggestion { |
411 | suggestion_kind, | |
412 | applicability: static_applicability, | |
413 | code_field, | |
414 | code_init, | |
415 | } => { | |
9ffffee4 | 416 | if let FieldInnerTy::Vec(_) = info.ty { |
353b0b11 | 417 | throw_invalid_attr!(attr, |diag| { |
9ffffee4 FG |
418 | diag |
419 | .note("`#[suggestion(...)]` applied to `Vec` field is ambiguous") | |
420 | .help("to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]`") | |
421 | .help("to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]`") | |
422 | }); | |
423 | } | |
424 | ||
2b03887a FG |
425 | let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?; |
426 | ||
427 | if let Some((static_applicability, span)) = static_applicability { | |
428 | applicability.set_once(quote! { #static_applicability }, span); | |
064997fb | 429 | } |
2b03887a FG |
430 | |
431 | let applicability = applicability | |
432 | .value() | |
433 | .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified }); | |
434 | let style = suggestion_kind.to_suggestion_style(); | |
435 | ||
436 | self.formatting_init.extend(code_init); | |
437 | Ok(quote! { | |
438 | #diag.span_suggestions_with_style( | |
439 | #span_field, | |
9ffffee4 | 440 | crate::fluent_generated::#slug, |
2b03887a FG |
441 | #code_field, |
442 | #applicability, | |
443 | #style | |
444 | ); | |
445 | }) | |
064997fb | 446 | } |
2b03887a | 447 | SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), |
064997fb | 448 | } |
064997fb FG |
449 | } |
450 | ||
451 | /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug | |
452 | /// and `fluent_attr_identifier`. | |
453 | fn add_spanned_subdiagnostic( | |
454 | &self, | |
455 | field_binding: TokenStream, | |
456 | kind: &Ident, | |
457 | fluent_attr_identifier: Path, | |
458 | ) -> TokenStream { | |
2b03887a | 459 | let diag = &self.parent.diag; |
064997fb FG |
460 | let fn_name = format_ident!("span_{}", kind); |
461 | quote! { | |
462 | #diag.#fn_name( | |
463 | #field_binding, | |
9ffffee4 | 464 | crate::fluent_generated::#fluent_attr_identifier |
064997fb FG |
465 | ); |
466 | } | |
467 | } | |
468 | ||
469 | /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug | |
470 | /// and `fluent_attr_identifier`. | |
471 | fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream { | |
2b03887a | 472 | let diag = &self.parent.diag; |
064997fb | 473 | quote! { |
9ffffee4 | 474 | #diag.#kind(crate::fluent_generated::#fluent_attr_identifier); |
064997fb FG |
475 | } |
476 | } | |
477 | ||
478 | fn span_and_applicability_of_ty( | |
479 | &self, | |
480 | info: FieldInfo<'_>, | |
2b03887a | 481 | ) -> Result<(TokenStream, SpannedOption<TokenStream>), DiagnosticDeriveError> { |
9ffffee4 | 482 | match &info.ty.inner_type() { |
064997fb FG |
483 | // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`. |
484 | ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => { | |
485 | let binding = &info.binding.binding; | |
2b03887a | 486 | Ok((quote!(#binding), None)) |
064997fb FG |
487 | } |
488 | // If `ty` is `(Span, Applicability)` then return tokens accessing those. | |
489 | Type::Tuple(tup) => { | |
490 | let mut span_idx = None; | |
491 | let mut applicability_idx = None; | |
492 | ||
2b03887a FG |
493 | fn type_err(span: &Span) -> Result<!, DiagnosticDeriveError> { |
494 | span_err(span.unwrap(), "wrong types for suggestion") | |
495 | .help( | |
496 | "`#[suggestion(...)]` on a tuple field must be applied to fields \ | |
497 | of type `(Span, Applicability)`", | |
498 | ) | |
499 | .emit(); | |
500 | Err(DiagnosticDeriveError::ErrorHandled) | |
501 | } | |
502 | ||
064997fb FG |
503 | for (idx, elem) in tup.elems.iter().enumerate() { |
504 | if type_matches_path(elem, &["rustc_span", "Span"]) { | |
2b03887a | 505 | span_idx.set_once(syn::Index::from(idx), elem.span().unwrap()); |
064997fb | 506 | } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) { |
2b03887a FG |
507 | applicability_idx.set_once(syn::Index::from(idx), elem.span().unwrap()); |
508 | } else { | |
509 | type_err(&elem.span())?; | |
064997fb FG |
510 | } |
511 | } | |
512 | ||
2b03887a FG |
513 | let Some((span_idx, _)) = span_idx else { |
514 | type_err(&tup.span())?; | |
515 | }; | |
516 | let Some((applicability_idx, applicability_span)) = applicability_idx else { | |
517 | type_err(&tup.span())?; | |
518 | }; | |
519 | let binding = &info.binding.binding; | |
520 | let span = quote!(#binding.#span_idx); | |
521 | let applicability = quote!(#binding.#applicability_idx); | |
064997fb | 522 | |
2b03887a | 523 | Ok((span, Some((applicability, applicability_span)))) |
064997fb FG |
524 | } |
525 | // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error. | |
526 | _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| { | |
527 | diag.help( | |
528 | "`#[suggestion(...)]` should be applied to fields of type `Span` or \ | |
529 | `(Span, Applicability)`", | |
530 | ) | |
531 | }), | |
532 | } | |
533 | } | |
534 | } |