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