]>
Commit | Line | Data |
---|---|---|
04454e1e 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 | SessionDiagnosticDeriveError, | |
6 | }; | |
7 | use crate::diagnostics::utils::{ | |
8 | report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path, | |
9 | Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce, | |
10 | }; | |
11 | use proc_macro2::{Ident, TokenStream}; | |
12 | use quote::{format_ident, quote}; | |
13 | use std::collections::HashMap; | |
14 | use std::str::FromStr; | |
15 | use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type}; | |
923072b8 | 16 | use synstructure::{BindingInfo, Structure}; |
04454e1e FG |
17 | |
18 | /// The central struct for constructing the `into_diagnostic` method from an annotated struct. | |
19 | pub(crate) struct SessionDiagnosticDerive<'a> { | |
20 | structure: Structure<'a>, | |
21 | builder: SessionDiagnosticDeriveBuilder, | |
22 | } | |
23 | ||
24 | impl<'a> SessionDiagnosticDerive<'a> { | |
25 | pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self { | |
26 | // Build the mapping of field names to fields. This allows attributes to peek values from | |
27 | // other fields. | |
28 | let mut fields_map = HashMap::new(); | |
29 | ||
30 | // Convenience bindings. | |
31 | let ast = structure.ast(); | |
32 | ||
33 | if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data { | |
34 | for field in fields.iter() { | |
35 | if let Some(ident) = &field.ident { | |
36 | fields_map.insert(ident.to_string(), quote! { &self.#ident }); | |
37 | } | |
38 | } | |
39 | } | |
40 | ||
41 | Self { | |
42 | builder: SessionDiagnosticDeriveBuilder { | |
43 | diag, | |
44 | sess, | |
45 | fields: fields_map, | |
46 | kind: None, | |
47 | code: None, | |
48 | slug: None, | |
49 | }, | |
50 | structure, | |
51 | } | |
52 | } | |
53 | ||
54 | pub(crate) fn into_tokens(self) -> TokenStream { | |
55 | let SessionDiagnosticDerive { mut structure, mut builder } = self; | |
56 | ||
57 | let ast = structure.ast(); | |
58 | let attrs = &ast.attrs; | |
59 | ||
60 | let (implementation, param_ty) = { | |
61 | if let syn::Data::Struct(..) = ast.data { | |
62 | let preamble = { | |
63 | let preamble = attrs.iter().map(|attr| { | |
64 | builder | |
65 | .generate_structure_code(attr) | |
66 | .unwrap_or_else(|v| v.to_compile_error()) | |
67 | }); | |
68 | ||
69 | quote! { | |
70 | #(#preamble)*; | |
71 | } | |
72 | }; | |
73 | ||
923072b8 FG |
74 | // Keep track of which fields are subdiagnostics or have no attributes. |
75 | let mut subdiagnostics_or_empty = std::collections::HashSet::new(); | |
76 | ||
04454e1e FG |
77 | // Generates calls to `span_label` and similar functions based on the attributes |
78 | // on fields. Code for suggestions uses formatting machinery and the value of | |
79 | // other fields - because any given field can be referenced multiple times, it | |
923072b8 FG |
80 | // should be accessed through a borrow. When passing fields to `add_subdiagnostic` |
81 | // or `set_arg` (which happens below) for Fluent, we want to move the data, so that | |
82 | // has to happen in a separate pass over the fields. | |
83 | let attrs = structure | |
84 | .clone() | |
85 | .filter(|field_binding| { | |
86 | let attrs = &field_binding.ast().attrs; | |
87 | ||
88 | (!attrs.is_empty() | |
89 | && attrs.iter().all(|attr| { | |
90 | "subdiagnostic" | |
91 | != attr.path.segments.last().unwrap().ident.to_string() | |
92 | })) | |
93 | || { | |
94 | subdiagnostics_or_empty.insert(field_binding.binding.clone()); | |
95 | false | |
96 | } | |
97 | }) | |
98 | .each(|field_binding| builder.generate_field_attrs_code(field_binding)); | |
04454e1e | 99 | |
04454e1e | 100 | structure.bind_with(|_| synstructure::BindStyle::Move); |
923072b8 FG |
101 | // When a field has attributes like `#[label]` or `#[note]` then it doesn't |
102 | // need to be passed as an argument to the diagnostic. But when a field has no | |
103 | // attributes or a `#[subdiagnostic]` attribute then it must be passed as an | |
104 | // argument to the diagnostic so that it can be referred to by Fluent messages. | |
105 | let args = structure | |
106 | .filter(|field_binding| { | |
107 | subdiagnostics_or_empty.contains(&field_binding.binding) | |
108 | }) | |
109 | .each(|field_binding| builder.generate_field_attrs_code(field_binding)); | |
04454e1e FG |
110 | |
111 | let span = ast.span().unwrap(); | |
112 | let (diag, sess) = (&builder.diag, &builder.sess); | |
113 | let init = match (builder.kind, builder.slug) { | |
114 | (None, _) => { | |
115 | span_err(span, "diagnostic kind not specified") | |
116 | .help("use the `#[error(...)]` attribute to create an error") | |
117 | .emit(); | |
118 | return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error(); | |
119 | } | |
120 | (Some((kind, _)), None) => { | |
121 | span_err(span, "`slug` not specified") | |
122 | .help(&format!("use the `#[{}(slug = \"...\")]` attribute to set this diagnostic's slug", kind.descr())) | |
123 | .emit(); | |
124 | return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error(); | |
125 | } | |
126 | (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => { | |
127 | quote! { | |
128 | let mut #diag = #sess.struct_err( | |
923072b8 | 129 | rustc_errors::DiagnosticMessage::new(#slug), |
04454e1e FG |
130 | ); |
131 | } | |
132 | } | |
133 | (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => { | |
134 | quote! { | |
135 | let mut #diag = #sess.struct_warn( | |
923072b8 | 136 | rustc_errors::DiagnosticMessage::new(#slug), |
04454e1e FG |
137 | ); |
138 | } | |
139 | } | |
140 | }; | |
141 | ||
142 | let implementation = quote! { | |
143 | #init | |
144 | #preamble | |
145 | match self { | |
146 | #attrs | |
147 | } | |
148 | match self { | |
149 | #args | |
150 | } | |
151 | #diag | |
152 | }; | |
153 | let param_ty = match builder.kind { | |
154 | Some((SessionDiagnosticKind::Error, _)) => { | |
155 | quote! { rustc_errors::ErrorGuaranteed } | |
156 | } | |
157 | Some((SessionDiagnosticKind::Warn, _)) => quote! { () }, | |
158 | _ => unreachable!(), | |
159 | }; | |
160 | ||
161 | (implementation, param_ty) | |
162 | } else { | |
163 | span_err( | |
164 | ast.span().unwrap(), | |
165 | "`#[derive(SessionDiagnostic)]` can only be used on structs", | |
166 | ) | |
167 | .emit(); | |
168 | ||
169 | let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error(); | |
170 | let param_ty = quote! { rustc_errors::ErrorGuaranteed }; | |
171 | (implementation, param_ty) | |
172 | } | |
173 | }; | |
174 | ||
175 | let sess = &builder.sess; | |
176 | structure.gen_impl(quote! { | |
177 | gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty> | |
178 | for @Self | |
179 | { | |
180 | fn into_diagnostic( | |
181 | self, | |
182 | #sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess | |
183 | ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> { | |
184 | use rustc_errors::IntoDiagnosticArg; | |
185 | #implementation | |
186 | } | |
187 | } | |
188 | }) | |
189 | } | |
190 | } | |
191 | ||
192 | /// What kind of session diagnostic is being derived - an error or a warning? | |
193 | #[derive(Copy, Clone)] | |
194 | enum SessionDiagnosticKind { | |
195 | /// `#[error(..)]` | |
196 | Error, | |
197 | /// `#[warn(..)]` | |
198 | Warn, | |
199 | } | |
200 | ||
201 | impl SessionDiagnosticKind { | |
202 | /// Returns human-readable string corresponding to the kind. | |
203 | fn descr(&self) -> &'static str { | |
204 | match self { | |
205 | SessionDiagnosticKind::Error => "error", | |
206 | SessionDiagnosticKind::Warn => "warning", | |
207 | } | |
208 | } | |
209 | } | |
210 | ||
211 | /// Tracks persistent information required for building up the individual calls to diagnostic | |
212 | /// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive` | |
213 | /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a | |
214 | /// double mut borrow later on. | |
215 | struct SessionDiagnosticDeriveBuilder { | |
216 | /// Name of the session parameter that's passed in to the `as_error` method. | |
217 | sess: syn::Ident, | |
218 | /// The identifier to use for the generated `DiagnosticBuilder` instance. | |
219 | diag: syn::Ident, | |
220 | ||
221 | /// Store a map of field name to its corresponding field. This is built on construction of the | |
222 | /// derive builder. | |
223 | fields: HashMap<String, TokenStream>, | |
224 | ||
225 | /// Kind of diagnostic requested via the struct attribute. | |
226 | kind: Option<(SessionDiagnosticKind, proc_macro::Span)>, | |
227 | /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that | |
228 | /// has the actual diagnostic message. | |
229 | slug: Option<(String, proc_macro::Span)>, | |
230 | /// Error codes are a optional part of the struct attribute - this is only set to detect | |
231 | /// multiple specifications. | |
232 | code: Option<(String, proc_macro::Span)>, | |
233 | } | |
234 | ||
235 | impl HasFieldMap for SessionDiagnosticDeriveBuilder { | |
236 | fn get_field_binding(&self, field: &String) -> Option<&TokenStream> { | |
237 | self.fields.get(field) | |
238 | } | |
239 | } | |
240 | ||
241 | impl SessionDiagnosticDeriveBuilder { | |
242 | /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct | |
243 | /// attributes like `#[error(..)#`, such as the diagnostic kind and slug. Generates | |
244 | /// diagnostic builder calls for setting error code and creating note/help messages. | |
245 | fn generate_structure_code( | |
246 | &mut self, | |
247 | attr: &Attribute, | |
248 | ) -> Result<TokenStream, SessionDiagnosticDeriveError> { | |
249 | let span = attr.span().unwrap(); | |
250 | ||
251 | let name = attr.path.segments.last().unwrap().ident.to_string(); | |
252 | let name = name.as_str(); | |
253 | let meta = attr.parse_meta()?; | |
254 | ||
255 | if matches!(name, "help" | "note") && matches!(meta, Meta::Path(_) | Meta::NameValue(_)) { | |
256 | let diag = &self.diag; | |
04454e1e FG |
257 | let id = match meta { |
258 | Meta::Path(..) => quote! { #name }, | |
259 | Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => { | |
260 | quote! { #s } | |
261 | } | |
262 | _ => unreachable!(), | |
263 | }; | |
264 | let fn_name = proc_macro2::Ident::new(name, attr.span()); | |
265 | ||
266 | return Ok(quote! { | |
923072b8 | 267 | #diag.#fn_name(rustc_errors::SubdiagnosticMessage::attr(#id)); |
04454e1e FG |
268 | }); |
269 | } | |
270 | ||
271 | let nested = match meta { | |
272 | Meta::List(MetaList { ref nested, .. }) => nested, | |
273 | _ => throw_invalid_attr!(attr, &meta), | |
274 | }; | |
275 | ||
276 | let kind = match name { | |
277 | "error" => SessionDiagnosticKind::Error, | |
278 | "warning" => SessionDiagnosticKind::Warn, | |
279 | _ => throw_invalid_attr!(attr, &meta, |diag| { | |
280 | diag.help("only `error` and `warning` are valid attributes") | |
281 | }), | |
282 | }; | |
283 | self.kind.set_once((kind, span)); | |
284 | ||
285 | let mut tokens = Vec::new(); | |
286 | for nested_attr in nested { | |
287 | let meta = match nested_attr { | |
288 | syn::NestedMeta::Meta(meta) => meta, | |
289 | _ => throw_invalid_nested_attr!(attr, &nested_attr), | |
290 | }; | |
291 | ||
292 | let path = meta.path(); | |
293 | let nested_name = path.segments.last().unwrap().ident.to_string(); | |
294 | match &meta { | |
295 | // Struct attributes are only allowed to be applied once, and the diagnostic | |
296 | // changes will be set in the initialisation code. | |
297 | Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => { | |
298 | let span = s.span().unwrap(); | |
299 | match nested_name.as_str() { | |
300 | "slug" => { | |
301 | self.slug.set_once((s.value(), span)); | |
302 | } | |
303 | "code" => { | |
304 | self.code.set_once((s.value(), span)); | |
305 | let (diag, code) = (&self.diag, &self.code.as_ref().map(|(v, _)| v)); | |
306 | tokens.push(quote! { | |
307 | #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); | |
308 | }); | |
309 | } | |
310 | _ => invalid_nested_attr(attr, &nested_attr) | |
311 | .help("only `slug` and `code` are valid nested attributes") | |
312 | .emit(), | |
313 | } | |
314 | } | |
315 | _ => invalid_nested_attr(attr, &nested_attr).emit(), | |
316 | } | |
317 | } | |
318 | ||
319 | Ok(tokens.drain(..).collect()) | |
320 | } | |
321 | ||
923072b8 FG |
322 | fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { |
323 | let field = binding_info.ast(); | |
324 | let field_binding = &binding_info.binding; | |
04454e1e | 325 | |
923072b8 | 326 | let inner_ty = FieldInnerTy::from_type(&field.ty); |
04454e1e | 327 | |
923072b8 FG |
328 | // When generating `set_arg` or `add_subdiagnostic` calls, move data rather than |
329 | // borrow it to avoid requiring clones - this must therefore be the last use of | |
330 | // each field (for example, any formatting machinery that might refer to a field | |
331 | // should be generated already). | |
332 | if field.attrs.is_empty() { | |
333 | let diag = &self.diag; | |
334 | let ident = field.ident.as_ref().unwrap(); | |
335 | quote! { | |
336 | #diag.set_arg( | |
337 | stringify!(#ident), | |
338 | #field_binding | |
339 | ); | |
340 | } | |
04454e1e | 341 | } else { |
923072b8 FG |
342 | field |
343 | .attrs | |
344 | .iter() | |
345 | .map(move |attr| { | |
346 | let name = attr.path.segments.last().unwrap().ident.to_string(); | |
347 | let (binding, needs_destructure) = match (name.as_str(), &inner_ty) { | |
348 | // `primary_span` can accept a `Vec<Span>` so don't destructure that. | |
349 | ("primary_span", FieldInnerTy::Vec(_)) => { | |
350 | (quote! { #field_binding.clone() }, false) | |
351 | } | |
352 | // `subdiagnostics` are not derefed because they are bound by value. | |
353 | ("subdiagnostic", _) => (quote! { #field_binding }, true), | |
354 | _ => (quote! { *#field_binding }, true), | |
355 | }; | |
356 | ||
357 | let generated_code = self | |
358 | .generate_inner_field_code( | |
359 | attr, | |
360 | FieldInfo { | |
361 | binding: binding_info, | |
362 | ty: inner_ty.inner_type().unwrap_or(&field.ty), | |
363 | span: &field.span(), | |
364 | }, | |
365 | binding, | |
366 | ) | |
367 | .unwrap_or_else(|v| v.to_compile_error()); | |
368 | ||
369 | if needs_destructure { | |
370 | inner_ty.with(field_binding, generated_code) | |
371 | } else { | |
372 | generated_code | |
373 | } | |
374 | }) | |
375 | .collect() | |
04454e1e FG |
376 | } |
377 | } | |
378 | ||
379 | fn generate_inner_field_code( | |
380 | &mut self, | |
381 | attr: &Attribute, | |
382 | info: FieldInfo<'_>, | |
383 | binding: TokenStream, | |
384 | ) -> Result<TokenStream, SessionDiagnosticDeriveError> { | |
385 | let diag = &self.diag; | |
386 | ||
387 | let ident = &attr.path.segments.last().unwrap().ident; | |
388 | let name = ident.to_string(); | |
389 | let name = name.as_str(); | |
390 | ||
391 | let meta = attr.parse_meta()?; | |
392 | match meta { | |
393 | Meta::Path(_) => match name { | |
394 | "skip_arg" => { | |
395 | // Don't need to do anything - by virtue of the attribute existing, the | |
396 | // `set_arg` call will not be generated. | |
397 | Ok(quote! {}) | |
398 | } | |
399 | "primary_span" => { | |
400 | report_error_if_not_applied_to_span(attr, &info)?; | |
401 | Ok(quote! { | |
402 | #diag.set_span(#binding); | |
403 | }) | |
404 | } | |
405 | "label" => { | |
406 | report_error_if_not_applied_to_span(attr, &info)?; | |
407 | Ok(self.add_spanned_subdiagnostic(binding, ident, name)) | |
408 | } | |
409 | "note" | "help" => { | |
410 | if type_matches_path(&info.ty, &["rustc_span", "Span"]) { | |
411 | Ok(self.add_spanned_subdiagnostic(binding, ident, name)) | |
412 | } else if type_is_unit(&info.ty) { | |
413 | Ok(self.add_subdiagnostic(ident, name)) | |
414 | } else { | |
415 | report_type_error(attr, "`Span` or `()`")?; | |
416 | } | |
417 | } | |
418 | "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }), | |
419 | _ => throw_invalid_attr!(attr, &meta, |diag| { | |
420 | diag | |
421 | .help("only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes") | |
422 | }), | |
423 | }, | |
424 | Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name { | |
425 | "label" => { | |
426 | report_error_if_not_applied_to_span(attr, &info)?; | |
427 | Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value())) | |
428 | } | |
429 | "note" | "help" => { | |
430 | if type_matches_path(&info.ty, &["rustc_span", "Span"]) { | |
431 | Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value())) | |
432 | } else if type_is_unit(&info.ty) { | |
433 | Ok(self.add_subdiagnostic(ident, &s.value())) | |
434 | } else { | |
435 | report_type_error(attr, "`Span` or `()`")?; | |
436 | } | |
437 | } | |
438 | _ => throw_invalid_attr!(attr, &meta, |diag| { | |
439 | diag.help("only `label`, `note` and `help` are valid field attributes") | |
440 | }), | |
441 | }, | |
442 | Meta::List(MetaList { ref path, ref nested, .. }) => { | |
443 | let name = path.segments.last().unwrap().ident.to_string(); | |
444 | let name = name.as_ref(); | |
445 | ||
446 | match name { | |
447 | "suggestion" | "suggestion_short" | "suggestion_hidden" | |
448 | | "suggestion_verbose" => (), | |
449 | _ => throw_invalid_attr!(attr, &meta, |diag| { | |
450 | diag | |
451 | .help("only `suggestion{,_short,_hidden,_verbose}` are valid field attributes") | |
452 | }), | |
453 | }; | |
454 | ||
455 | let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?; | |
456 | ||
457 | let mut msg = None; | |
458 | let mut code = None; | |
459 | ||
460 | for nested_attr in nested { | |
461 | let meta = match nested_attr { | |
462 | syn::NestedMeta::Meta(ref meta) => meta, | |
463 | syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr), | |
464 | }; | |
465 | ||
466 | let nested_name = meta.path().segments.last().unwrap().ident.to_string(); | |
467 | let nested_name = nested_name.as_str(); | |
468 | match meta { | |
469 | Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => { | |
470 | let span = meta.span().unwrap(); | |
471 | match nested_name { | |
472 | "message" => { | |
473 | msg = Some(s.value()); | |
474 | } | |
475 | "code" => { | |
476 | let formatted_str = self.build_format(&s.value(), s.span()); | |
477 | code = Some(formatted_str); | |
478 | } | |
479 | "applicability" => { | |
480 | applicability = match applicability { | |
481 | Some(v) => { | |
482 | span_err( | |
483 | span, | |
484 | "applicability cannot be set in both the field and attribute" | |
485 | ).emit(); | |
486 | Some(v) | |
487 | } | |
488 | None => match Applicability::from_str(&s.value()) { | |
489 | Ok(v) => Some(quote! { #v }), | |
490 | Err(()) => { | |
491 | span_err(span, "invalid applicability").emit(); | |
492 | None | |
493 | } | |
494 | }, | |
495 | } | |
496 | } | |
497 | _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { | |
498 | diag.help( | |
499 | "only `message`, `code` and `applicability` are valid field attributes", | |
500 | ) | |
501 | }), | |
502 | } | |
503 | } | |
504 | _ => throw_invalid_nested_attr!(attr, &nested_attr), | |
505 | } | |
506 | } | |
507 | ||
508 | let applicability = applicability | |
509 | .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified)); | |
510 | ||
511 | let method = format_ident!("span_{}", name); | |
512 | ||
04454e1e | 513 | let msg = msg.as_deref().unwrap_or("suggestion"); |
923072b8 | 514 | let msg = quote! { rustc_errors::SubdiagnosticMessage::attr(#msg) }; |
04454e1e FG |
515 | let code = code.unwrap_or_else(|| quote! { String::new() }); |
516 | ||
517 | Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); }) | |
518 | } | |
519 | _ => throw_invalid_attr!(attr, &meta), | |
520 | } | |
521 | } | |
522 | ||
523 | /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug | |
524 | /// and `fluent_attr_identifier`. | |
525 | fn add_spanned_subdiagnostic( | |
526 | &self, | |
527 | field_binding: TokenStream, | |
528 | kind: &Ident, | |
529 | fluent_attr_identifier: &str, | |
530 | ) -> TokenStream { | |
531 | let diag = &self.diag; | |
04454e1e FG |
532 | let fn_name = format_ident!("span_{}", kind); |
533 | quote! { | |
534 | #diag.#fn_name( | |
535 | #field_binding, | |
923072b8 | 536 | rustc_errors::SubdiagnosticMessage::attr(#fluent_attr_identifier) |
04454e1e FG |
537 | ); |
538 | } | |
539 | } | |
540 | ||
541 | /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug | |
542 | /// and `fluent_attr_identifier`. | |
543 | fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: &str) -> TokenStream { | |
544 | let diag = &self.diag; | |
04454e1e | 545 | quote! { |
923072b8 | 546 | #diag.#kind(rustc_errors::SubdiagnosticMessage::attr(#fluent_attr_identifier)); |
04454e1e FG |
547 | } |
548 | } | |
549 | ||
550 | fn span_and_applicability_of_ty( | |
551 | &self, | |
552 | info: FieldInfo<'_>, | |
553 | ) -> Result<(TokenStream, Option<TokenStream>), SessionDiagnosticDeriveError> { | |
554 | match &info.ty { | |
555 | // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`. | |
556 | ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => { | |
557 | let binding = &info.binding.binding; | |
558 | Ok((quote!(*#binding), None)) | |
559 | } | |
560 | // If `ty` is `(Span, Applicability)` then return tokens accessing those. | |
561 | Type::Tuple(tup) => { | |
562 | let mut span_idx = None; | |
563 | let mut applicability_idx = None; | |
564 | ||
565 | for (idx, elem) in tup.elems.iter().enumerate() { | |
566 | if type_matches_path(elem, &["rustc_span", "Span"]) { | |
567 | if span_idx.is_none() { | |
568 | span_idx = Some(syn::Index::from(idx)); | |
569 | } else { | |
570 | throw_span_err!( | |
571 | info.span.unwrap(), | |
572 | "type of field annotated with `#[suggestion(...)]` contains more than one `Span`" | |
573 | ); | |
574 | } | |
575 | } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) { | |
576 | if applicability_idx.is_none() { | |
577 | applicability_idx = Some(syn::Index::from(idx)); | |
578 | } else { | |
579 | throw_span_err!( | |
580 | info.span.unwrap(), | |
581 | "type of field annotated with `#[suggestion(...)]` contains more than one Applicability" | |
582 | ); | |
583 | } | |
584 | } | |
585 | } | |
586 | ||
587 | if let Some(span_idx) = span_idx { | |
588 | let binding = &info.binding.binding; | |
589 | let span = quote!(#binding.#span_idx); | |
590 | let applicability = applicability_idx | |
591 | .map(|applicability_idx| quote!(#binding.#applicability_idx)) | |
592 | .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified)); | |
593 | ||
594 | return Ok((span, Some(applicability))); | |
595 | } | |
596 | ||
597 | throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| { | |
598 | diag.help("`#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`") | |
599 | }); | |
600 | } | |
601 | // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error. | |
602 | _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| { | |
603 | diag.help("`#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`") | |
604 | }), | |
605 | } | |
606 | } | |
607 | } |