]>
Commit | Line | Data |
---|---|---|
064997fb | 1 | use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError}; |
04454e1e FG |
2 | use proc_macro::Span; |
3 | use proc_macro2::TokenStream; | |
4 | use quote::{format_ident, quote, ToTokens}; | |
064997fb | 5 | use std::collections::{BTreeSet, HashMap}; |
04454e1e | 6 | use std::str::FromStr; |
923072b8 | 7 | use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple}; |
064997fb | 8 | use 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. | |
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, | |
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`. | |
57 | fn 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`. | |
71 | pub(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`. | |
84 | pub(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. | |
98 | pub(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 | ||
107 | impl<'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. | |
166 | pub(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. | |
174 | pub(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 | 180 | impl<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 | ||
199 | pub(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. | |
301 | pub(crate) enum Applicability { | |
302 | MachineApplicable, | |
303 | MaybeIncorrect, | |
304 | HasPlaceholders, | |
305 | Unspecified, | |
306 | } | |
307 | ||
308 | impl 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 | ||
322 | impl 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. | |
343 | pub(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 | } |