]>
Commit | Line | Data |
---|---|---|
2b03887a | 1 | use crate::diagnostics::error::{ |
353b0b11 | 2 | span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError, |
2b03887a | 3 | }; |
04454e1e | 4 | use proc_macro::Span; |
2b03887a | 5 | use proc_macro2::{Ident, TokenStream}; |
04454e1e | 6 | use quote::{format_ident, quote, ToTokens}; |
2b03887a | 7 | use std::cell::RefCell; |
064997fb | 8 | use std::collections::{BTreeSet, HashMap}; |
2b03887a | 9 | use std::fmt; |
04454e1e | 10 | use std::str::FromStr; |
353b0b11 FG |
11 | use syn::meta::ParseNestedMeta; |
12 | use syn::punctuated::Punctuated; | |
13 | use syn::{parenthesized, LitStr, Path, Token}; | |
2b03887a | 14 | use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple}; |
2b03887a FG |
15 | use synstructure::{BindingInfo, VariantInfo}; |
16 | ||
353b0b11 | 17 | use super::error::invalid_attr; |
2b03887a FG |
18 | |
19 | thread_local! { | |
20 | pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0); | |
21 | } | |
22 | ||
23 | /// Returns an ident of the form `__code_N` where `N` is incremented once with every call. | |
24 | pub(crate) fn new_code_ident() -> syn::Ident { | |
25 | CODE_IDENT_COUNT.with(|count| { | |
26 | let ident = format_ident!("__code_{}", *count.borrow()); | |
27 | *count.borrow_mut() += 1; | |
28 | ident | |
29 | }) | |
30 | } | |
04454e1e FG |
31 | |
32 | /// Checks whether the type name of `ty` matches `name`. | |
33 | /// | |
34 | /// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or | |
35 | /// `a::b::c::Foo`. This reasonably allows qualified names to be used in the macro. | |
36 | pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool { | |
37 | if let Type::Path(ty) = ty { | |
38 | ty.path | |
39 | .segments | |
40 | .iter() | |
41 | .map(|s| s.ident.to_string()) | |
42 | .rev() | |
43 | .zip(name.iter().rev()) | |
44 | .all(|(x, y)| &x.as_str() == y) | |
45 | } else { | |
46 | false | |
47 | } | |
48 | } | |
49 | ||
50 | /// Checks whether the type `ty` is `()`. | |
51 | pub(crate) fn type_is_unit(ty: &Type) -> bool { | |
52 | if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false } | |
53 | } | |
54 | ||
353b0b11 FG |
55 | /// Checks whether the type `ty` is `bool`. |
56 | pub(crate) fn type_is_bool(ty: &Type) -> bool { | |
57 | type_matches_path(ty, &["bool"]) | |
58 | } | |
59 | ||
04454e1e FG |
60 | /// Reports a type error for field with `attr`. |
61 | pub(crate) fn report_type_error( | |
62 | attr: &Attribute, | |
63 | ty_name: &str, | |
064997fb | 64 | ) -> Result<!, DiagnosticDeriveError> { |
353b0b11 FG |
65 | let name = attr.path().segments.last().unwrap().ident.to_string(); |
66 | let meta = &attr.meta; | |
04454e1e FG |
67 | |
68 | throw_span_err!( | |
69 | attr.span().unwrap(), | |
70 | &format!( | |
71 | "the `#[{}{}]` attribute can only be applied to fields of type {}", | |
72 | name, | |
73 | match meta { | |
74 | Meta::Path(_) => "", | |
75 | Meta::NameValue(_) => " = ...", | |
76 | Meta::List(_) => "(...)", | |
77 | }, | |
78 | ty_name | |
79 | ) | |
80 | ); | |
81 | } | |
82 | ||
83 | /// Reports an error if the field's type does not match `path`. | |
84 | fn report_error_if_not_applied_to_ty( | |
85 | attr: &Attribute, | |
86 | info: &FieldInfo<'_>, | |
87 | path: &[&str], | |
88 | ty_name: &str, | |
064997fb | 89 | ) -> Result<(), DiagnosticDeriveError> { |
9ffffee4 | 90 | if !type_matches_path(info.ty.inner_type(), path) { |
04454e1e FG |
91 | report_type_error(attr, ty_name)?; |
92 | } | |
93 | ||
94 | Ok(()) | |
95 | } | |
96 | ||
97 | /// Reports an error if the field's type is not `Applicability`. | |
98 | pub(crate) fn report_error_if_not_applied_to_applicability( | |
99 | attr: &Attribute, | |
100 | info: &FieldInfo<'_>, | |
064997fb | 101 | ) -> Result<(), DiagnosticDeriveError> { |
04454e1e FG |
102 | report_error_if_not_applied_to_ty( |
103 | attr, | |
104 | info, | |
105 | &["rustc_errors", "Applicability"], | |
106 | "`Applicability`", | |
107 | ) | |
108 | } | |
109 | ||
110 | /// Reports an error if the field's type is not `Span`. | |
111 | pub(crate) fn report_error_if_not_applied_to_span( | |
112 | attr: &Attribute, | |
113 | info: &FieldInfo<'_>, | |
064997fb | 114 | ) -> Result<(), DiagnosticDeriveError> { |
9ffffee4 FG |
115 | if !type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) |
116 | && !type_matches_path(info.ty.inner_type(), &["rustc_errors", "MultiSpan"]) | |
064997fb FG |
117 | { |
118 | report_type_error(attr, "`Span` or `MultiSpan`")?; | |
119 | } | |
120 | ||
121 | Ok(()) | |
04454e1e FG |
122 | } |
123 | ||
124 | /// Inner type of a field and type of wrapper. | |
9ffffee4 | 125 | #[derive(Copy, Clone)] |
04454e1e FG |
126 | pub(crate) enum FieldInnerTy<'ty> { |
127 | /// Field is wrapped in a `Option<$inner>`. | |
128 | Option(&'ty Type), | |
129 | /// Field is wrapped in a `Vec<$inner>`. | |
130 | Vec(&'ty Type), | |
131 | /// Field isn't wrapped in an outer type. | |
9ffffee4 | 132 | Plain(&'ty Type), |
04454e1e FG |
133 | } |
134 | ||
135 | impl<'ty> FieldInnerTy<'ty> { | |
136 | /// Returns inner type for a field, if there is one. | |
137 | /// | |
9ffffee4 FG |
138 | /// - If `ty` is an `Option<Inner>`, returns `FieldInnerTy::Option(Inner)`. |
139 | /// - If `ty` is a `Vec<Inner>`, returns `FieldInnerTy::Vec(Inner)`. | |
140 | /// - Otherwise returns `FieldInnerTy::Plain(ty)`. | |
04454e1e | 141 | pub(crate) fn from_type(ty: &'ty Type) -> Self { |
9ffffee4 FG |
142 | fn single_generic_type(ty: &Type) -> &Type { |
143 | let Type::Path(ty_path) = ty else { | |
144 | panic!("expected path type"); | |
04454e1e FG |
145 | }; |
146 | ||
04454e1e FG |
147 | let path = &ty_path.path; |
148 | let ty = path.segments.iter().last().unwrap(); | |
9ffffee4 FG |
149 | let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments else { |
150 | panic!("expected bracketed generic arguments"); | |
151 | }; | |
152 | ||
153 | assert_eq!(bracketed.args.len(), 1); | |
154 | ||
155 | let syn::GenericArgument::Type(ty) = &bracketed.args[0] else { | |
156 | panic!("expected generic parameter to be a type generic"); | |
157 | }; | |
158 | ||
159 | ty | |
04454e1e FG |
160 | } |
161 | ||
9ffffee4 FG |
162 | if type_matches_path(ty, &["std", "option", "Option"]) { |
163 | FieldInnerTy::Option(single_generic_type(ty)) | |
164 | } else if type_matches_path(ty, &["std", "vec", "Vec"]) { | |
165 | FieldInnerTy::Vec(single_generic_type(ty)) | |
166 | } else { | |
167 | FieldInnerTy::Plain(ty) | |
168 | } | |
04454e1e FG |
169 | } |
170 | ||
2b03887a FG |
171 | /// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e. |
172 | /// that cloning might be required for values moved in the loop body). | |
173 | pub(crate) fn will_iterate(&self) -> bool { | |
174 | match self { | |
175 | FieldInnerTy::Vec(..) => true, | |
9ffffee4 | 176 | FieldInnerTy::Option(..) | FieldInnerTy::Plain(_) => false, |
2b03887a FG |
177 | } |
178 | } | |
179 | ||
9ffffee4 FG |
180 | /// Returns the inner type. |
181 | pub(crate) fn inner_type(&self) -> &'ty Type { | |
04454e1e | 182 | match self { |
9ffffee4 FG |
183 | FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) | FieldInnerTy::Plain(inner) => { |
184 | inner | |
185 | } | |
04454e1e FG |
186 | } |
187 | } | |
188 | ||
189 | /// Surrounds `inner` with destructured wrapper type, exposing inner type as `binding`. | |
190 | pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream { | |
191 | match self { | |
192 | FieldInnerTy::Option(..) => quote! { | |
193 | if let Some(#binding) = #binding { | |
194 | #inner | |
195 | } | |
196 | }, | |
197 | FieldInnerTy::Vec(..) => quote! { | |
198 | for #binding in #binding { | |
199 | #inner | |
200 | } | |
201 | }, | |
353b0b11 FG |
202 | FieldInnerTy::Plain(t) if type_is_bool(t) => quote! { |
203 | if #binding { | |
204 | #inner | |
205 | } | |
206 | }, | |
9ffffee4 | 207 | FieldInnerTy::Plain(..) => quote! { #inner }, |
04454e1e FG |
208 | } |
209 | } | |
210 | } | |
211 | ||
212 | /// Field information passed to the builder. Deliberately omits attrs to discourage the | |
213 | /// `generate_*` methods from walking the attributes themselves. | |
214 | pub(crate) struct FieldInfo<'a> { | |
04454e1e | 215 | pub(crate) binding: &'a BindingInfo<'a>, |
9ffffee4 | 216 | pub(crate) ty: FieldInnerTy<'a>, |
04454e1e FG |
217 | pub(crate) span: &'a proc_macro2::Span, |
218 | } | |
219 | ||
220 | /// Small helper trait for abstracting over `Option` fields that contain a value and a `Span` | |
221 | /// for error reporting if they are set more than once. | |
222 | pub(crate) trait SetOnce<T> { | |
2b03887a | 223 | fn set_once(&mut self, value: T, span: Span); |
064997fb FG |
224 | |
225 | fn value(self) -> Option<T>; | |
2b03887a | 226 | fn value_ref(&self) -> Option<&T>; |
04454e1e FG |
227 | } |
228 | ||
2b03887a FG |
229 | /// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`]. |
230 | pub(super) type SpannedOption<T> = Option<(T, Span)>; | |
231 | ||
232 | impl<T> SetOnce<T> for SpannedOption<T> { | |
233 | fn set_once(&mut self, value: T, span: Span) { | |
04454e1e FG |
234 | match self { |
235 | None => { | |
236 | *self = Some((value, span)); | |
237 | } | |
238 | Some((_, prev_span)) => { | |
239 | span_err(span, "specified multiple times") | |
240 | .span_note(*prev_span, "previously specified here") | |
241 | .emit(); | |
242 | } | |
243 | } | |
244 | } | |
064997fb FG |
245 | |
246 | fn value(self) -> Option<T> { | |
247 | self.map(|(v, _)| v) | |
248 | } | |
2b03887a FG |
249 | |
250 | fn value_ref(&self) -> Option<&T> { | |
251 | self.as_ref().map(|(v, _)| v) | |
252 | } | |
04454e1e FG |
253 | } |
254 | ||
2b03887a FG |
255 | pub(super) type FieldMap = HashMap<String, TokenStream>; |
256 | ||
04454e1e FG |
257 | pub(crate) trait HasFieldMap { |
258 | /// Returns the binding for the field with the given name, if it exists on the type. | |
259 | fn get_field_binding(&self, field: &String) -> Option<&TokenStream>; | |
260 | ||
261 | /// In the strings in the attributes supplied to this macro, we want callers to be able to | |
262 | /// reference fields in the format string. For example: | |
263 | /// | |
264 | /// ```ignore (not-usage-example) | |
265 | /// /// Suggest `==` when users wrote `===`. | |
266 | /// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")] | |
267 | /// struct NotJavaScriptEq { | |
268 | /// #[primary_span] | |
269 | /// span: Span, | |
270 | /// lhs: Ident, | |
271 | /// rhs: Ident, | |
272 | /// } | |
273 | /// ``` | |
274 | /// | |
275 | /// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to | |
276 | /// `self.rhs`, then generate this call to `format!`: | |
277 | /// | |
278 | /// ```ignore (not-usage-example) | |
279 | /// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs) | |
280 | /// ``` | |
281 | /// | |
282 | /// This function builds the entire call to `format!`. | |
283 | fn build_format(&self, input: &str, span: proc_macro2::Span) -> TokenStream { | |
284 | // This set is used later to generate the final format string. To keep builds reproducible, | |
285 | // the iteration order needs to be deterministic, hence why we use a `BTreeSet` here | |
286 | // instead of a `HashSet`. | |
287 | let mut referenced_fields: BTreeSet<String> = BTreeSet::new(); | |
288 | ||
289 | // At this point, we can start parsing the format string. | |
290 | let mut it = input.chars().peekable(); | |
291 | ||
292 | // Once the start of a format string has been found, process the format string and spit out | |
293 | // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so | |
294 | // the next call to `it.next()` retrieves the next character. | |
295 | while let Some(c) = it.next() { | |
f2b60f7d FG |
296 | if c != '{' { |
297 | continue; | |
298 | } | |
299 | if *it.peek().unwrap_or(&'\0') == '{' { | |
300 | assert_eq!(it.next().unwrap(), '{'); | |
301 | continue; | |
302 | } | |
303 | let mut eat_argument = || -> Option<String> { | |
304 | let mut result = String::new(); | |
305 | // Format specifiers look like: | |
306 | // | |
307 | // format := '{' [ argument ] [ ':' format_spec ] '}' . | |
308 | // | |
309 | // Therefore, we only need to eat until ':' or '}' to find the argument. | |
310 | while let Some(c) = it.next() { | |
311 | result.push(c); | |
312 | let next = *it.peek().unwrap_or(&'\0'); | |
313 | if next == '}' { | |
314 | break; | |
315 | } else if next == ':' { | |
316 | // Eat the ':' character. | |
317 | assert_eq!(it.next().unwrap(), ':'); | |
318 | break; | |
04454e1e | 319 | } |
04454e1e | 320 | } |
f2b60f7d FG |
321 | // Eat until (and including) the matching '}' |
322 | while it.next()? != '}' { | |
323 | continue; | |
324 | } | |
325 | Some(result) | |
326 | }; | |
327 | ||
328 | if let Some(referenced_field) = eat_argument() { | |
329 | referenced_fields.insert(referenced_field); | |
04454e1e FG |
330 | } |
331 | } | |
332 | ||
333 | // At this point, `referenced_fields` contains a set of the unique fields that were | |
334 | // referenced in the format string. Generate the corresponding "x = self.x" format | |
335 | // string parameters: | |
336 | let args = referenced_fields.into_iter().map(|field: String| { | |
337 | let field_ident = format_ident!("{}", field); | |
338 | let value = match self.get_field_binding(&field) { | |
339 | Some(value) => value.clone(), | |
340 | // This field doesn't exist. Emit a diagnostic. | |
341 | None => { | |
342 | span_err( | |
343 | span.unwrap(), | |
9c376795 | 344 | &format!("`{field}` doesn't refer to a field on this type"), |
04454e1e FG |
345 | ) |
346 | .emit(); | |
347 | quote! { | |
348 | "{#field}" | |
349 | } | |
350 | } | |
351 | }; | |
352 | quote! { | |
353 | #field_ident = #value | |
354 | } | |
355 | }); | |
356 | quote! { | |
357 | format!(#input #(,#args)*) | |
358 | } | |
359 | } | |
360 | } | |
361 | ||
362 | /// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent | |
363 | /// the user's selection of applicability if specified in an attribute. | |
2b03887a | 364 | #[derive(Clone, Copy)] |
04454e1e FG |
365 | pub(crate) enum Applicability { |
366 | MachineApplicable, | |
367 | MaybeIncorrect, | |
368 | HasPlaceholders, | |
369 | Unspecified, | |
370 | } | |
371 | ||
372 | impl FromStr for Applicability { | |
373 | type Err = (); | |
374 | ||
375 | fn from_str(s: &str) -> Result<Self, Self::Err> { | |
376 | match s { | |
377 | "machine-applicable" => Ok(Applicability::MachineApplicable), | |
378 | "maybe-incorrect" => Ok(Applicability::MaybeIncorrect), | |
379 | "has-placeholders" => Ok(Applicability::HasPlaceholders), | |
380 | "unspecified" => Ok(Applicability::Unspecified), | |
381 | _ => Err(()), | |
382 | } | |
383 | } | |
384 | } | |
385 | ||
386 | impl quote::ToTokens for Applicability { | |
387 | fn to_tokens(&self, tokens: &mut TokenStream) { | |
388 | tokens.extend(match self { | |
389 | Applicability::MachineApplicable => { | |
390 | quote! { rustc_errors::Applicability::MachineApplicable } | |
391 | } | |
392 | Applicability::MaybeIncorrect => { | |
393 | quote! { rustc_errors::Applicability::MaybeIncorrect } | |
394 | } | |
395 | Applicability::HasPlaceholders => { | |
396 | quote! { rustc_errors::Applicability::HasPlaceholders } | |
397 | } | |
398 | Applicability::Unspecified => { | |
399 | quote! { rustc_errors::Applicability::Unspecified } | |
400 | } | |
401 | }); | |
402 | } | |
403 | } | |
064997fb FG |
404 | |
405 | /// Build the mapping of field names to fields. This allows attributes to peek values from | |
406 | /// other fields. | |
9c376795 | 407 | pub(super) fn build_field_mapping(variant: &VariantInfo<'_>) -> HashMap<String, TokenStream> { |
2b03887a FG |
408 | let mut fields_map = FieldMap::new(); |
409 | for binding in variant.bindings() { | |
410 | if let Some(ident) = &binding.ast().ident { | |
411 | fields_map.insert(ident.to_string(), quote! { #binding }); | |
412 | } | |
413 | } | |
414 | fields_map | |
415 | } | |
416 | ||
417 | #[derive(Copy, Clone, Debug)] | |
418 | pub(super) enum AllowMultipleAlternatives { | |
419 | No, | |
420 | Yes, | |
421 | } | |
422 | ||
353b0b11 FG |
423 | fn parse_suggestion_values( |
424 | nested: ParseNestedMeta<'_>, | |
425 | allow_multiple: AllowMultipleAlternatives, | |
426 | ) -> syn::Result<Vec<LitStr>> { | |
427 | let values = if let Ok(val) = nested.value() { | |
428 | vec![val.parse()?] | |
429 | } else { | |
430 | let content; | |
431 | parenthesized!(content in nested.input); | |
432 | ||
433 | if let AllowMultipleAlternatives::No = allow_multiple { | |
434 | span_err( | |
435 | nested.input.span().unwrap(), | |
436 | "expected exactly one string literal for `code = ...`", | |
437 | ) | |
438 | .emit(); | |
439 | vec![] | |
440 | } else { | |
441 | let literals = Punctuated::<LitStr, Token![,]>::parse_terminated(&content); | |
442 | ||
443 | match literals { | |
444 | Ok(p) if p.is_empty() => { | |
445 | span_err( | |
446 | content.span().unwrap(), | |
447 | "expected at least one string literal for `code(...)`", | |
448 | ) | |
449 | .emit(); | |
450 | vec![] | |
451 | } | |
452 | Ok(p) => p.into_iter().collect(), | |
453 | Err(_) => { | |
454 | span_err( | |
455 | content.span().unwrap(), | |
456 | "`code(...)` must contain only string literals", | |
457 | ) | |
458 | .emit(); | |
459 | vec![] | |
460 | } | |
461 | } | |
462 | } | |
463 | }; | |
464 | ||
465 | Ok(values) | |
466 | } | |
467 | ||
2b03887a FG |
468 | /// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or |
469 | /// `#[suggestion*(code("foo", "bar"))]` attribute field | |
470 | pub(super) fn build_suggestion_code( | |
471 | code_field: &Ident, | |
353b0b11 | 472 | nested: ParseNestedMeta<'_>, |
2b03887a FG |
473 | fields: &impl HasFieldMap, |
474 | allow_multiple: AllowMultipleAlternatives, | |
475 | ) -> TokenStream { | |
353b0b11 FG |
476 | let values = match parse_suggestion_values(nested, allow_multiple) { |
477 | Ok(x) => x, | |
478 | Err(e) => return e.into_compile_error(), | |
2b03887a FG |
479 | }; |
480 | ||
481 | if let AllowMultipleAlternatives::Yes = allow_multiple { | |
482 | let formatted_strings: Vec<_> = values | |
483 | .into_iter() | |
484 | .map(|value| fields.build_format(&value.value(), value.span())) | |
485 | .collect(); | |
486 | quote! { let #code_field = [#(#formatted_strings),*].into_iter(); } | |
487 | } else if let [value] = values.as_slice() { | |
488 | let formatted_str = fields.build_format(&value.value(), value.span()); | |
489 | quote! { let #code_field = #formatted_str; } | |
490 | } else { | |
491 | // error handled previously | |
492 | quote! { let #code_field = String::new(); } | |
064997fb | 493 | } |
2b03887a | 494 | } |
064997fb | 495 | |
2b03887a | 496 | /// Possible styles for suggestion subdiagnostics. |
487cf647 | 497 | #[derive(Clone, Copy, PartialEq)] |
2b03887a | 498 | pub(super) enum SuggestionKind { |
2b03887a | 499 | Normal, |
2b03887a | 500 | Short, |
2b03887a | 501 | Hidden, |
2b03887a | 502 | Verbose, |
487cf647 | 503 | ToolOnly, |
2b03887a FG |
504 | } |
505 | ||
506 | impl FromStr for SuggestionKind { | |
507 | type Err = (); | |
508 | ||
509 | fn from_str(s: &str) -> Result<Self, Self::Err> { | |
510 | match s { | |
487cf647 FG |
511 | "normal" => Ok(SuggestionKind::Normal), |
512 | "short" => Ok(SuggestionKind::Short), | |
513 | "hidden" => Ok(SuggestionKind::Hidden), | |
514 | "verbose" => Ok(SuggestionKind::Verbose), | |
515 | "tool-only" => Ok(SuggestionKind::ToolOnly), | |
2b03887a FG |
516 | _ => Err(()), |
517 | } | |
518 | } | |
519 | } | |
520 | ||
487cf647 FG |
521 | impl fmt::Display for SuggestionKind { |
522 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
523 | match self { | |
524 | SuggestionKind::Normal => write!(f, "normal"), | |
525 | SuggestionKind::Short => write!(f, "short"), | |
526 | SuggestionKind::Hidden => write!(f, "hidden"), | |
527 | SuggestionKind::Verbose => write!(f, "verbose"), | |
528 | SuggestionKind::ToolOnly => write!(f, "tool-only"), | |
529 | } | |
530 | } | |
531 | } | |
532 | ||
2b03887a FG |
533 | impl SuggestionKind { |
534 | pub fn to_suggestion_style(&self) -> TokenStream { | |
535 | match self { | |
536 | SuggestionKind::Normal => { | |
537 | quote! { rustc_errors::SuggestionStyle::ShowCode } | |
538 | } | |
539 | SuggestionKind::Short => { | |
540 | quote! { rustc_errors::SuggestionStyle::HideCodeInline } | |
541 | } | |
542 | SuggestionKind::Hidden => { | |
543 | quote! { rustc_errors::SuggestionStyle::HideCodeAlways } | |
544 | } | |
545 | SuggestionKind::Verbose => { | |
546 | quote! { rustc_errors::SuggestionStyle::ShowAlways } | |
547 | } | |
487cf647 FG |
548 | SuggestionKind::ToolOnly => { |
549 | quote! { rustc_errors::SuggestionStyle::CompletelyHidden } | |
550 | } | |
551 | } | |
552 | } | |
553 | ||
554 | fn from_suffix(s: &str) -> Option<Self> { | |
555 | match s { | |
556 | "" => Some(SuggestionKind::Normal), | |
557 | "_short" => Some(SuggestionKind::Short), | |
558 | "_hidden" => Some(SuggestionKind::Hidden), | |
559 | "_verbose" => Some(SuggestionKind::Verbose), | |
560 | _ => None, | |
2b03887a FG |
561 | } |
562 | } | |
563 | } | |
564 | ||
565 | /// Types of subdiagnostics that can be created using attributes | |
566 | #[derive(Clone)] | |
567 | pub(super) enum SubdiagnosticKind { | |
568 | /// `#[label(...)]` | |
569 | Label, | |
570 | /// `#[note(...)]` | |
571 | Note, | |
572 | /// `#[help(...)]` | |
573 | Help, | |
574 | /// `#[warning(...)]` | |
575 | Warn, | |
576 | /// `#[suggestion{,_short,_hidden,_verbose}]` | |
577 | Suggestion { | |
578 | suggestion_kind: SuggestionKind, | |
579 | applicability: SpannedOption<Applicability>, | |
580 | /// Identifier for variable used for formatted code, e.g. `___code_0`. Enables separation | |
581 | /// of formatting and diagnostic emission so that `set_arg` calls can happen in-between.. | |
582 | code_field: syn::Ident, | |
583 | /// Initialization logic for `code_field`'s variable, e.g. | |
584 | /// `let __formatted_code = /* whatever */;` | |
585 | code_init: TokenStream, | |
586 | }, | |
587 | /// `#[multipart_suggestion{,_short,_hidden,_verbose}]` | |
588 | MultipartSuggestion { | |
589 | suggestion_kind: SuggestionKind, | |
590 | applicability: SpannedOption<Applicability>, | |
591 | }, | |
592 | } | |
593 | ||
594 | impl SubdiagnosticKind { | |
595 | /// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`, | |
596 | /// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the | |
597 | /// `SubdiagnosticKind` and the diagnostic slug, if specified. | |
598 | pub(super) fn from_attr( | |
599 | attr: &Attribute, | |
600 | fields: &impl HasFieldMap, | |
601 | ) -> Result<Option<(SubdiagnosticKind, Option<Path>)>, DiagnosticDeriveError> { | |
602 | // Always allow documentation comments. | |
603 | if is_doc_comment(attr) { | |
604 | return Ok(None); | |
605 | } | |
606 | ||
607 | let span = attr.span().unwrap(); | |
608 | ||
353b0b11 | 609 | let name = attr.path().segments.last().unwrap().ident.to_string(); |
2b03887a FG |
610 | let name = name.as_str(); |
611 | ||
2b03887a FG |
612 | let mut kind = match name { |
613 | "label" => SubdiagnosticKind::Label, | |
614 | "note" => SubdiagnosticKind::Note, | |
615 | "help" => SubdiagnosticKind::Help, | |
616 | "warning" => SubdiagnosticKind::Warn, | |
617 | _ => { | |
487cf647 FG |
618 | // Recover old `#[(multipart_)suggestion_*]` syntaxes |
619 | // FIXME(#100717): remove | |
2b03887a | 620 | if let Some(suggestion_kind) = |
487cf647 | 621 | name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix) |
2b03887a | 622 | { |
487cf647 | 623 | if suggestion_kind != SuggestionKind::Normal { |
353b0b11 | 624 | invalid_attr(attr) |
487cf647 | 625 | .help(format!( |
9c376795 | 626 | r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"# |
487cf647 FG |
627 | )) |
628 | .emit(); | |
629 | } | |
630 | ||
2b03887a | 631 | SubdiagnosticKind::Suggestion { |
487cf647 | 632 | suggestion_kind: SuggestionKind::Normal, |
2b03887a FG |
633 | applicability: None, |
634 | code_field: new_code_ident(), | |
635 | code_init: TokenStream::new(), | |
636 | } | |
637 | } else if let Some(suggestion_kind) = | |
487cf647 | 638 | name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix) |
2b03887a | 639 | { |
487cf647 | 640 | if suggestion_kind != SuggestionKind::Normal { |
353b0b11 | 641 | invalid_attr(attr) |
487cf647 | 642 | .help(format!( |
9c376795 | 643 | r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"# |
487cf647 FG |
644 | )) |
645 | .emit(); | |
646 | } | |
647 | ||
648 | SubdiagnosticKind::MultipartSuggestion { | |
649 | suggestion_kind: SuggestionKind::Normal, | |
650 | applicability: None, | |
651 | } | |
2b03887a | 652 | } else { |
353b0b11 | 653 | throw_invalid_attr!(attr); |
2b03887a FG |
654 | } |
655 | } | |
656 | }; | |
657 | ||
353b0b11 FG |
658 | let list = match &attr.meta { |
659 | Meta::List(list) => { | |
2b03887a FG |
660 | // An attribute with properties, such as `#[suggestion(code = "...")]` or |
661 | // `#[error(some::slug)]` | |
353b0b11 | 662 | list |
2b03887a FG |
663 | } |
664 | Meta::Path(_) => { | |
665 | // An attribute without a slug or other properties, such as `#[note]` - return | |
666 | // without further processing. | |
667 | // | |
668 | // Only allow this if there are no mandatory properties, such as `code = "..."` in | |
669 | // `#[suggestion(...)]` | |
670 | match kind { | |
671 | SubdiagnosticKind::Label | |
672 | | SubdiagnosticKind::Note | |
673 | | SubdiagnosticKind::Help | |
674 | | SubdiagnosticKind::Warn | |
675 | | SubdiagnosticKind::MultipartSuggestion { .. } => { | |
676 | return Ok(Some((kind, None))); | |
677 | } | |
678 | SubdiagnosticKind::Suggestion { .. } => { | |
679 | throw_span_err!(span, "suggestion without `code = \"...\"`") | |
680 | } | |
681 | } | |
682 | } | |
683 | _ => { | |
353b0b11 | 684 | throw_invalid_attr!(attr) |
2b03887a FG |
685 | } |
686 | }; | |
687 | ||
688 | let mut code = None; | |
487cf647 | 689 | let mut suggestion_kind = None; |
2b03887a | 690 | |
353b0b11 FG |
691 | let mut first = true; |
692 | let mut slug = None; | |
2b03887a | 693 | |
353b0b11 FG |
694 | list.parse_nested_meta(|nested| { |
695 | if nested.input.is_empty() || nested.input.peek(Token![,]) { | |
696 | if first { | |
697 | slug = Some(nested.path); | |
698 | } else { | |
699 | span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit(); | |
2b03887a | 700 | } |
2b03887a | 701 | |
353b0b11 FG |
702 | first = false; |
703 | return Ok(()); | |
704 | } | |
705 | ||
706 | first = false; | |
707 | ||
708 | let nested_name = nested.path.segments.last().unwrap().ident.to_string(); | |
2b03887a FG |
709 | let nested_name = nested_name.as_str(); |
710 | ||
353b0b11 FG |
711 | let path_span = nested.path.span().unwrap(); |
712 | let val_span = nested.input.span().unwrap(); | |
2b03887a | 713 | |
353b0b11 FG |
714 | macro_rules! get_string { |
715 | () => {{ | |
716 | let Ok(value) = nested.value().and_then(|x| x.parse::<LitStr>()) else { | |
717 | span_err(val_span, "expected `= \"xxx\"`").emit(); | |
718 | return Ok(()); | |
719 | }; | |
720 | value | |
721 | }}; | |
722 | } | |
723 | ||
724 | let mut has_errors = false; | |
725 | let input = nested.input; | |
2b03887a FG |
726 | |
727 | match (nested_name, &mut kind) { | |
728 | ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { | |
729 | let code_init = build_suggestion_code( | |
730 | code_field, | |
353b0b11 | 731 | nested, |
2b03887a FG |
732 | fields, |
733 | AllowMultipleAlternatives::Yes, | |
734 | ); | |
353b0b11 | 735 | code.set_once(code_init, path_span); |
2b03887a FG |
736 | } |
737 | ( | |
738 | "applicability", | |
739 | SubdiagnosticKind::Suggestion { ref mut applicability, .. } | |
740 | | SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. }, | |
741 | ) => { | |
353b0b11 | 742 | let value = get_string!(); |
2b03887a | 743 | let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| { |
353b0b11 FG |
744 | span_err(value.span().unwrap(), "invalid applicability").emit(); |
745 | has_errors = true; | |
2b03887a FG |
746 | Applicability::Unspecified |
747 | }); | |
748 | applicability.set_once(value, span); | |
749 | } | |
487cf647 FG |
750 | ( |
751 | "style", | |
752 | SubdiagnosticKind::Suggestion { .. } | |
753 | | SubdiagnosticKind::MultipartSuggestion { .. }, | |
754 | ) => { | |
353b0b11 | 755 | let value = get_string!(); |
487cf647 FG |
756 | |
757 | let value = value.value().parse().unwrap_or_else(|()| { | |
758 | span_err(value.span().unwrap(), "invalid suggestion style") | |
759 | .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`") | |
760 | .emit(); | |
353b0b11 | 761 | has_errors = true; |
487cf647 FG |
762 | SuggestionKind::Normal |
763 | }); | |
764 | ||
765 | suggestion_kind.set_once(value, span); | |
766 | } | |
2b03887a FG |
767 | |
768 | // Invalid nested attribute | |
769 | (_, SubdiagnosticKind::Suggestion { .. }) => { | |
353b0b11 | 770 | span_err(path_span, "invalid nested attribute") |
487cf647 FG |
771 | .help( |
772 | "only `style`, `code` and `applicability` are valid nested attributes", | |
773 | ) | |
2b03887a | 774 | .emit(); |
353b0b11 | 775 | has_errors = true; |
2b03887a FG |
776 | } |
777 | (_, SubdiagnosticKind::MultipartSuggestion { .. }) => { | |
353b0b11 | 778 | span_err(path_span, "invalid nested attribute") |
487cf647 | 779 | .help("only `style` and `applicability` are valid nested attributes") |
353b0b11 FG |
780 | .emit(); |
781 | has_errors = true; | |
2b03887a FG |
782 | } |
783 | _ => { | |
353b0b11 FG |
784 | span_err(path_span, "invalid nested attribute").emit(); |
785 | has_errors = true; | |
2b03887a FG |
786 | } |
787 | } | |
353b0b11 FG |
788 | |
789 | if has_errors { | |
790 | // Consume the rest of the input to avoid spamming errors | |
791 | let _ = input.parse::<TokenStream>(); | |
792 | } | |
793 | ||
794 | Ok(()) | |
795 | })?; | |
2b03887a FG |
796 | |
797 | match kind { | |
487cf647 FG |
798 | SubdiagnosticKind::Suggestion { |
799 | ref code_field, | |
800 | ref mut code_init, | |
801 | suggestion_kind: ref mut kind_field, | |
802 | .. | |
803 | } => { | |
804 | if let Some(kind) = suggestion_kind.value() { | |
805 | *kind_field = kind; | |
806 | } | |
807 | ||
2b03887a FG |
808 | *code_init = if let Some(init) = code.value() { |
809 | init | |
810 | } else { | |
811 | span_err(span, "suggestion without `code = \"...\"`").emit(); | |
812 | quote! { let #code_field = std::iter::empty(); } | |
813 | }; | |
814 | } | |
487cf647 FG |
815 | SubdiagnosticKind::MultipartSuggestion { |
816 | suggestion_kind: ref mut kind_field, .. | |
817 | } => { | |
818 | if let Some(kind) = suggestion_kind.value() { | |
819 | *kind_field = kind; | |
820 | } | |
821 | } | |
2b03887a FG |
822 | SubdiagnosticKind::Label |
823 | | SubdiagnosticKind::Note | |
824 | | SubdiagnosticKind::Help | |
487cf647 | 825 | | SubdiagnosticKind::Warn => {} |
2b03887a FG |
826 | } |
827 | ||
828 | Ok(Some((kind, slug))) | |
829 | } | |
830 | } | |
831 | ||
832 | impl quote::IdentFragment for SubdiagnosticKind { | |
833 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
834 | match self { | |
835 | SubdiagnosticKind::Label => write!(f, "label"), | |
836 | SubdiagnosticKind::Note => write!(f, "note"), | |
837 | SubdiagnosticKind::Help => write!(f, "help"), | |
838 | SubdiagnosticKind::Warn => write!(f, "warn"), | |
839 | SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestions_with_style"), | |
840 | SubdiagnosticKind::MultipartSuggestion { .. } => { | |
841 | write!(f, "multipart_suggestion_with_style") | |
842 | } | |
843 | } | |
844 | } | |
845 | ||
846 | fn span(&self) -> Option<proc_macro2::Span> { | |
847 | None | |
848 | } | |
849 | } | |
850 | ||
851 | /// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic | |
852 | /// call (like `span_label`). | |
853 | pub(super) fn should_generate_set_arg(field: &Field) -> bool { | |
854 | field.attrs.is_empty() | |
855 | } | |
856 | ||
857 | pub(super) fn is_doc_comment(attr: &Attribute) -> bool { | |
353b0b11 | 858 | attr.path().segments.last().unwrap().ident == "doc" |
064997fb | 859 | } |