]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_macros/src/diagnostics/utils.rs
New upstream version 1.64.0+dfsg1
[rustc.git] / compiler / rustc_macros / src / diagnostics / utils.rs
CommitLineData
064997fb 1use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError};
04454e1e
FG
2use proc_macro::Span;
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote, ToTokens};
064997fb 5use std::collections::{BTreeSet, HashMap};
04454e1e 6use std::str::FromStr;
923072b8 7use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
064997fb 8use synstructure::{BindingInfo, Structure};
04454e1e
FG
9
10/// Checks whether the type name of `ty` matches `name`.
11///
12/// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
13/// `a::b::c::Foo`. This reasonably allows qualified names to be used in the macro.
14pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool {
15 if let Type::Path(ty) = ty {
16 ty.path
17 .segments
18 .iter()
19 .map(|s| s.ident.to_string())
20 .rev()
21 .zip(name.iter().rev())
22 .all(|(x, y)| &x.as_str() == y)
23 } else {
24 false
25 }
26}
27
28/// Checks whether the type `ty` is `()`.
29pub(crate) fn type_is_unit(ty: &Type) -> bool {
30 if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false }
31}
32
33/// Reports a type error for field with `attr`.
34pub(crate) fn report_type_error(
35 attr: &Attribute,
36 ty_name: &str,
064997fb 37) -> Result<!, DiagnosticDeriveError> {
04454e1e
FG
38 let name = attr.path.segments.last().unwrap().ident.to_string();
39 let meta = attr.parse_meta()?;
40
41 throw_span_err!(
42 attr.span().unwrap(),
43 &format!(
44 "the `#[{}{}]` attribute can only be applied to fields of type {}",
45 name,
46 match meta {
47 Meta::Path(_) => "",
48 Meta::NameValue(_) => " = ...",
49 Meta::List(_) => "(...)",
50 },
51 ty_name
52 )
53 );
54}
55
56/// Reports an error if the field's type does not match `path`.
57fn report_error_if_not_applied_to_ty(
58 attr: &Attribute,
59 info: &FieldInfo<'_>,
60 path: &[&str],
61 ty_name: &str,
064997fb 62) -> Result<(), DiagnosticDeriveError> {
04454e1e
FG
63 if !type_matches_path(&info.ty, path) {
64 report_type_error(attr, ty_name)?;
65 }
66
67 Ok(())
68}
69
70/// Reports an error if the field's type is not `Applicability`.
71pub(crate) fn report_error_if_not_applied_to_applicability(
72 attr: &Attribute,
73 info: &FieldInfo<'_>,
064997fb 74) -> Result<(), DiagnosticDeriveError> {
04454e1e
FG
75 report_error_if_not_applied_to_ty(
76 attr,
77 info,
78 &["rustc_errors", "Applicability"],
79 "`Applicability`",
80 )
81}
82
83/// Reports an error if the field's type is not `Span`.
84pub(crate) fn report_error_if_not_applied_to_span(
85 attr: &Attribute,
86 info: &FieldInfo<'_>,
064997fb
FG
87) -> Result<(), DiagnosticDeriveError> {
88 if !type_matches_path(&info.ty, &["rustc_span", "Span"])
89 && !type_matches_path(&info.ty, &["rustc_errors", "MultiSpan"])
90 {
91 report_type_error(attr, "`Span` or `MultiSpan`")?;
92 }
93
94 Ok(())
04454e1e
FG
95}
96
97/// Inner type of a field and type of wrapper.
98pub(crate) enum FieldInnerTy<'ty> {
99 /// Field is wrapped in a `Option<$inner>`.
100 Option(&'ty Type),
101 /// Field is wrapped in a `Vec<$inner>`.
102 Vec(&'ty Type),
103 /// Field isn't wrapped in an outer type.
104 None,
105}
106
107impl<'ty> FieldInnerTy<'ty> {
108 /// Returns inner type for a field, if there is one.
109 ///
110 /// - If `ty` is an `Option`, returns `FieldInnerTy::Option { inner: (inner type) }`.
111 /// - If `ty` is a `Vec`, returns `FieldInnerTy::Vec { inner: (inner type) }`.
112 /// - Otherwise returns `None`.
113 pub(crate) fn from_type(ty: &'ty Type) -> Self {
114 let variant: &dyn Fn(&'ty Type) -> FieldInnerTy<'ty> =
115 if type_matches_path(ty, &["std", "option", "Option"]) {
116 &FieldInnerTy::Option
117 } else if type_matches_path(ty, &["std", "vec", "Vec"]) {
118 &FieldInnerTy::Vec
119 } else {
120 return FieldInnerTy::None;
121 };
122
123 if let Type::Path(ty_path) = ty {
124 let path = &ty_path.path;
125 let ty = path.segments.iter().last().unwrap();
126 if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
127 if bracketed.args.len() == 1 {
128 if let syn::GenericArgument::Type(ty) = &bracketed.args[0] {
129 return variant(ty);
130 }
131 }
132 }
133 }
134
135 unreachable!();
136 }
137
138 /// Returns `Option` containing inner type if there is one.
139 pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
140 match self {
141 FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) => Some(inner),
142 FieldInnerTy::None => None,
143 }
144 }
145
146 /// Surrounds `inner` with destructured wrapper type, exposing inner type as `binding`.
147 pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream {
148 match self {
149 FieldInnerTy::Option(..) => quote! {
150 if let Some(#binding) = #binding {
151 #inner
152 }
153 },
154 FieldInnerTy::Vec(..) => quote! {
155 for #binding in #binding {
156 #inner
157 }
158 },
159 FieldInnerTy::None => quote! { #inner },
160 }
161 }
162}
163
164/// Field information passed to the builder. Deliberately omits attrs to discourage the
165/// `generate_*` methods from walking the attributes themselves.
166pub(crate) struct FieldInfo<'a> {
04454e1e
FG
167 pub(crate) binding: &'a BindingInfo<'a>,
168 pub(crate) ty: &'a Type,
169 pub(crate) span: &'a proc_macro2::Span,
170}
171
172/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
173/// for error reporting if they are set more than once.
174pub(crate) trait SetOnce<T> {
064997fb
FG
175 fn set_once(&mut self, _: (T, Span));
176
177 fn value(self) -> Option<T>;
04454e1e
FG
178}
179
064997fb 180impl<T> SetOnce<T> for Option<(T, Span)> {
04454e1e
FG
181 fn set_once(&mut self, (value, span): (T, Span)) {
182 match self {
183 None => {
184 *self = Some((value, span));
185 }
186 Some((_, prev_span)) => {
187 span_err(span, "specified multiple times")
188 .span_note(*prev_span, "previously specified here")
189 .emit();
190 }
191 }
192 }
064997fb
FG
193
194 fn value(self) -> Option<T> {
195 self.map(|(v, _)| v)
196 }
04454e1e
FG
197}
198
199pub(crate) trait HasFieldMap {
200 /// Returns the binding for the field with the given name, if it exists on the type.
201 fn get_field_binding(&self, field: &String) -> Option<&TokenStream>;
202
203 /// In the strings in the attributes supplied to this macro, we want callers to be able to
204 /// reference fields in the format string. For example:
205 ///
206 /// ```ignore (not-usage-example)
207 /// /// Suggest `==` when users wrote `===`.
208 /// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")]
209 /// struct NotJavaScriptEq {
210 /// #[primary_span]
211 /// span: Span,
212 /// lhs: Ident,
213 /// rhs: Ident,
214 /// }
215 /// ```
216 ///
217 /// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to
218 /// `self.rhs`, then generate this call to `format!`:
219 ///
220 /// ```ignore (not-usage-example)
221 /// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs)
222 /// ```
223 ///
224 /// This function builds the entire call to `format!`.
225 fn build_format(&self, input: &str, span: proc_macro2::Span) -> TokenStream {
226 // This set is used later to generate the final format string. To keep builds reproducible,
227 // the iteration order needs to be deterministic, hence why we use a `BTreeSet` here
228 // instead of a `HashSet`.
229 let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
230
231 // At this point, we can start parsing the format string.
232 let mut it = input.chars().peekable();
233
234 // Once the start of a format string has been found, process the format string and spit out
235 // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so
236 // the next call to `it.next()` retrieves the next character.
237 while let Some(c) = it.next() {
238 if c == '{' && *it.peek().unwrap_or(&'\0') != '{' {
239 let mut eat_argument = || -> Option<String> {
240 let mut result = String::new();
241 // Format specifiers look like:
242 //
243 // format := '{' [ argument ] [ ':' format_spec ] '}' .
244 //
245 // Therefore, we only need to eat until ':' or '}' to find the argument.
246 while let Some(c) = it.next() {
247 result.push(c);
248 let next = *it.peek().unwrap_or(&'\0');
249 if next == '}' {
250 break;
251 } else if next == ':' {
252 // Eat the ':' character.
253 assert_eq!(it.next().unwrap(), ':');
254 break;
255 }
256 }
257 // Eat until (and including) the matching '}'
258 while it.next()? != '}' {
259 continue;
260 }
261 Some(result)
262 };
263
264 if let Some(referenced_field) = eat_argument() {
265 referenced_fields.insert(referenced_field);
266 }
267 }
268 }
269
270 // At this point, `referenced_fields` contains a set of the unique fields that were
271 // referenced in the format string. Generate the corresponding "x = self.x" format
272 // string parameters:
273 let args = referenced_fields.into_iter().map(|field: String| {
274 let field_ident = format_ident!("{}", field);
275 let value = match self.get_field_binding(&field) {
276 Some(value) => value.clone(),
277 // This field doesn't exist. Emit a diagnostic.
278 None => {
279 span_err(
280 span.unwrap(),
281 &format!("`{}` doesn't refer to a field on this type", field),
282 )
283 .emit();
284 quote! {
285 "{#field}"
286 }
287 }
288 };
289 quote! {
290 #field_ident = #value
291 }
292 });
293 quote! {
294 format!(#input #(,#args)*)
295 }
296 }
297}
298
299/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
300/// the user's selection of applicability if specified in an attribute.
301pub(crate) enum Applicability {
302 MachineApplicable,
303 MaybeIncorrect,
304 HasPlaceholders,
305 Unspecified,
306}
307
308impl FromStr for Applicability {
309 type Err = ();
310
311 fn from_str(s: &str) -> Result<Self, Self::Err> {
312 match s {
313 "machine-applicable" => Ok(Applicability::MachineApplicable),
314 "maybe-incorrect" => Ok(Applicability::MaybeIncorrect),
315 "has-placeholders" => Ok(Applicability::HasPlaceholders),
316 "unspecified" => Ok(Applicability::Unspecified),
317 _ => Err(()),
318 }
319 }
320}
321
322impl quote::ToTokens for Applicability {
323 fn to_tokens(&self, tokens: &mut TokenStream) {
324 tokens.extend(match self {
325 Applicability::MachineApplicable => {
326 quote! { rustc_errors::Applicability::MachineApplicable }
327 }
328 Applicability::MaybeIncorrect => {
329 quote! { rustc_errors::Applicability::MaybeIncorrect }
330 }
331 Applicability::HasPlaceholders => {
332 quote! { rustc_errors::Applicability::HasPlaceholders }
333 }
334 Applicability::Unspecified => {
335 quote! { rustc_errors::Applicability::Unspecified }
336 }
337 });
338 }
339}
064997fb
FG
340
341/// Build the mapping of field names to fields. This allows attributes to peek values from
342/// other fields.
343pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<String, TokenStream> {
344 let mut fields_map = HashMap::new();
345
346 let ast = structure.ast();
347 if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
348 for field in fields.iter() {
349 if let Some(ident) = &field.ident {
350 fields_map.insert(ident.to_string(), quote! { &self.#ident });
351 }
352 }
353 }
354
355 fields_map
356}