1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | |
5 | #![allow (clippy::redundant_closure_call)] |
6 | |
7 | use syn::ext::IdentExt; |
8 | |
9 | pub trait IterHelpers: Iterator { |
10 | fn try_skip_map<F, T, E>(&mut self, f: F) -> Result<Vec<T>, E> |
11 | where |
12 | F: FnMut(&Self::Item) -> Result<Option<T>, E>; |
13 | } |
14 | |
15 | impl<I> IterHelpers for I |
16 | where |
17 | I: Iterator, |
18 | { |
19 | fn try_skip_map<F, T, E>(&mut self, mut f: F) -> Result<Vec<T>, E> |
20 | where |
21 | F: FnMut(&Self::Item) -> Result<Option<T>, E>, |
22 | { |
23 | let mut out: Vec = Vec::new(); |
24 | for item: ::Item in self { |
25 | if let Some(x: T) = f(&item)? { |
26 | out.push(x); |
27 | } |
28 | } |
29 | Ok(out) |
30 | } |
31 | } |
32 | |
33 | pub trait SynItemHelpers: SynAttributeHelpers { |
34 | fn exported_name(&self) -> Option<String>; |
35 | } |
36 | |
37 | impl SynItemHelpers for syn::ItemFn { |
38 | fn exported_name(&self) -> Option<String> { |
39 | self.attrs |
40 | .attr_name_value_lookup(name:"export_name" ) |
41 | .or_else(|| { |
42 | if self.is_no_mangle() { |
43 | Some(self.sig.ident.unraw().to_string()) |
44 | } else { |
45 | None |
46 | } |
47 | }) |
48 | } |
49 | } |
50 | |
51 | impl SynItemHelpers for syn::ImplItemMethod { |
52 | fn exported_name(&self) -> Option<String> { |
53 | self.attrs |
54 | .attr_name_value_lookup(name:"export_name" ) |
55 | .or_else(|| { |
56 | if self.is_no_mangle() { |
57 | Some(self.sig.ident.unraw().to_string()) |
58 | } else { |
59 | None |
60 | } |
61 | }) |
62 | } |
63 | } |
64 | |
65 | impl SynItemHelpers for syn::ItemStatic { |
66 | fn exported_name(&self) -> Option<String> { |
67 | self.attrs |
68 | .attr_name_value_lookup(name:"export_name" ) |
69 | .or_else(|| { |
70 | if self.is_no_mangle() { |
71 | Some(self.ident.unraw().to_string()) |
72 | } else { |
73 | None |
74 | } |
75 | }) |
76 | } |
77 | } |
78 | |
79 | /// Returns whether this attribute causes us to skip at item. This basically |
80 | /// checks for `#[cfg(test)]`, `#[test]`, `/// cbindgen::ignore` and |
81 | /// variations thereof. |
82 | fn is_skip_item_attr(attr: &syn::Meta) -> bool { |
83 | match *attr { |
84 | syn::Meta::Path(ref path) => { |
85 | // TODO(emilio): It'd be great if rustc allowed us to use a syntax |
86 | // like `#[cbindgen::ignore]` or such. |
87 | path.is_ident("test" ) |
88 | } |
89 | syn::Meta::List(ref list) => { |
90 | if !list.path.is_ident("cfg" ) { |
91 | return false; |
92 | } |
93 | list.nested.iter().any(|nested| match *nested { |
94 | syn::NestedMeta::Meta(ref meta) => is_skip_item_attr(meta), |
95 | syn::NestedMeta::Lit(..) => false, |
96 | }) |
97 | } |
98 | syn::Meta::NameValue(ref name_value) => { |
99 | if name_value.path.is_ident("doc" ) { |
100 | if let syn::Lit::Str(ref content) = name_value.lit { |
101 | // FIXME(emilio): Maybe should use the general annotation |
102 | // mechanism, but it seems overkill for this. |
103 | if content.value().trim() == "cbindgen:ignore" { |
104 | return true; |
105 | } |
106 | } |
107 | } |
108 | false |
109 | } |
110 | } |
111 | } |
112 | |
113 | pub trait SynAttributeHelpers { |
114 | /// Returns the list of attributes for an item. |
115 | fn attrs(&self) -> &[syn::Attribute]; |
116 | |
117 | /// Searches for attributes like `#[test]`. |
118 | /// Example: |
119 | /// - `item.has_attr_word("test")` => `#[test]` |
120 | fn has_attr_word(&self, name: &str) -> bool { |
121 | self.attrs() |
122 | .iter() |
123 | .filter_map(|x| x.parse_meta().ok()) |
124 | .any(|attr| { |
125 | if let syn::Meta::Path(ref path) = attr { |
126 | path.is_ident(name) |
127 | } else { |
128 | false |
129 | } |
130 | }) |
131 | } |
132 | |
133 | fn find_deprecated_note(&self) -> Option<String> { |
134 | let attrs = self.attrs(); |
135 | // #[deprecated = ""] |
136 | if let Some(note) = attrs.attr_name_value_lookup("deprecated" ) { |
137 | return Some(note); |
138 | } |
139 | |
140 | // #[deprecated] |
141 | if attrs.has_attr_word("deprecated" ) { |
142 | return Some(String::new()); |
143 | } |
144 | |
145 | // #[deprecated(note = "")] |
146 | let attr = attrs.iter().find(|attr| { |
147 | if let Ok(syn::Meta::List(list)) = attr.parse_meta() { |
148 | list.path.is_ident("deprecated" ) |
149 | } else { |
150 | false |
151 | } |
152 | })?; |
153 | |
154 | let args: syn::punctuated::Punctuated<syn::MetaNameValue, Token![,]> = |
155 | match attr.parse_args_with(syn::punctuated::Punctuated::parse_terminated) { |
156 | Ok(args) => args, |
157 | Err(_) => { |
158 | warn!("couldn't parse deprecated attribute" ); |
159 | return None; |
160 | } |
161 | }; |
162 | |
163 | let arg = args.iter().find(|arg| arg.path.is_ident("note" ))?; |
164 | if let syn::Lit::Str(ref lit) = arg.lit { |
165 | Some(lit.value()) |
166 | } else { |
167 | warn!("deprecated attribute must be a string" ); |
168 | None |
169 | } |
170 | } |
171 | |
172 | fn is_no_mangle(&self) -> bool { |
173 | self.has_attr_word("no_mangle" ) |
174 | } |
175 | |
176 | /// Sees whether we should skip parsing a given item. |
177 | fn should_skip_parsing(&self) -> bool { |
178 | for attr in self.attrs() { |
179 | let meta = match attr.parse_meta() { |
180 | Ok(attr) => attr, |
181 | Err(..) => return false, |
182 | }; |
183 | if is_skip_item_attr(&meta) { |
184 | return true; |
185 | } |
186 | } |
187 | |
188 | false |
189 | } |
190 | |
191 | fn attr_name_value_lookup(&self, name: &str) -> Option<String> { |
192 | self.attrs() |
193 | .iter() |
194 | .filter_map(|attr| { |
195 | let attr = attr.parse_meta().ok()?; |
196 | if let syn::Meta::NameValue(syn::MetaNameValue { |
197 | path, |
198 | lit: syn::Lit::Str(lit), |
199 | .. |
200 | }) = attr |
201 | { |
202 | if path.is_ident(name) { |
203 | return Some(lit.value()); |
204 | } |
205 | } |
206 | None |
207 | }) |
208 | .next() |
209 | } |
210 | |
211 | fn get_comment_lines(&self) -> Vec<String> { |
212 | let mut comment = Vec::new(); |
213 | |
214 | for attr in self.attrs() { |
215 | if attr.style == syn::AttrStyle::Outer { |
216 | if let Ok(syn::Meta::NameValue(syn::MetaNameValue { |
217 | path, |
218 | lit: syn::Lit::Str(content), |
219 | .. |
220 | })) = attr.parse_meta() |
221 | { |
222 | if path.is_ident("doc" ) { |
223 | comment.extend(split_doc_attr(&content.value())); |
224 | } |
225 | } |
226 | } |
227 | } |
228 | |
229 | comment |
230 | } |
231 | } |
232 | |
233 | macro_rules! syn_item_match_helper { |
234 | ($s:ident => has_attrs: |$i:ident| $a:block, otherwise: || $b:block) => { |
235 | match *$s { |
236 | syn::Item::Const(ref $i) => $a, |
237 | syn::Item::Enum(ref $i) => $a, |
238 | syn::Item::ExternCrate(ref $i) => $a, |
239 | syn::Item::Fn(ref $i) => $a, |
240 | syn::Item::ForeignMod(ref $i) => $a, |
241 | syn::Item::Impl(ref $i) => $a, |
242 | syn::Item::Macro(ref $i) => $a, |
243 | syn::Item::Macro2(ref $i) => $a, |
244 | syn::Item::Mod(ref $i) => $a, |
245 | syn::Item::Static(ref $i) => $a, |
246 | syn::Item::Struct(ref $i) => $a, |
247 | syn::Item::Trait(ref $i) => $a, |
248 | syn::Item::Type(ref $i) => $a, |
249 | syn::Item::Union(ref $i) => $a, |
250 | syn::Item::Use(ref $i) => $a, |
251 | syn::Item::TraitAlias(ref $i) => $a, |
252 | syn::Item::Verbatim(_) => $b, |
253 | _ => panic!("Unhandled syn::Item: {:?}" , $s), |
254 | } |
255 | }; |
256 | } |
257 | |
258 | impl SynAttributeHelpers for syn::Item { |
259 | fn attrs(&self) -> &[syn::Attribute] { |
260 | syn_item_match_helper!(self => |
261 | has_attrs: |item| { &item.attrs }, |
262 | otherwise: || { &[] } |
263 | ) |
264 | } |
265 | } |
266 | |
267 | macro_rules! impl_syn_item_helper { |
268 | ($t:ty) => { |
269 | impl SynAttributeHelpers for $t { |
270 | fn attrs(&self) -> &[syn::Attribute] { |
271 | &self.attrs |
272 | } |
273 | } |
274 | }; |
275 | } |
276 | |
277 | impl_syn_item_helper!(syn::ItemExternCrate); |
278 | impl_syn_item_helper!(syn::ItemUse); |
279 | impl_syn_item_helper!(syn::ItemStatic); |
280 | impl_syn_item_helper!(syn::ItemConst); |
281 | impl_syn_item_helper!(syn::ItemFn); |
282 | impl_syn_item_helper!(syn::ImplItemMethod); |
283 | impl_syn_item_helper!(syn::ItemMod); |
284 | impl_syn_item_helper!(syn::ItemForeignMod); |
285 | impl_syn_item_helper!(syn::ItemType); |
286 | impl_syn_item_helper!(syn::ItemStruct); |
287 | impl_syn_item_helper!(syn::ItemEnum); |
288 | impl_syn_item_helper!(syn::ItemUnion); |
289 | impl_syn_item_helper!(syn::ItemTrait); |
290 | impl_syn_item_helper!(syn::ItemImpl); |
291 | impl_syn_item_helper!(syn::ItemMacro); |
292 | impl_syn_item_helper!(syn::ItemMacro2); |
293 | impl_syn_item_helper!(syn::ItemTraitAlias); |
294 | |
295 | /// Helper function for accessing Abi information |
296 | pub trait SynAbiHelpers { |
297 | fn is_c(&self) -> bool; |
298 | fn is_omitted(&self) -> bool; |
299 | } |
300 | |
301 | impl SynAbiHelpers for Option<syn::Abi> { |
302 | fn is_c(&self) -> bool { |
303 | if let Some(ref abi: &Abi) = *self { |
304 | if let Some(ref lit_string: &LitStr) = abi.name { |
305 | return matches!(lit_string.value().as_str(), "C" | "C-unwind" ); |
306 | } |
307 | } |
308 | false |
309 | } |
310 | fn is_omitted(&self) -> bool { |
311 | if let Some(ref abi: &Abi) = *self { |
312 | abi.name.is_none() |
313 | } else { |
314 | false |
315 | } |
316 | } |
317 | } |
318 | |
319 | impl SynAbiHelpers for syn::Abi { |
320 | fn is_c(&self) -> bool { |
321 | if let Some(ref lit_string: &LitStr) = self.name { |
322 | matches!(lit_string.value().as_str(), "C" | "C-unwind" ) |
323 | } else { |
324 | false |
325 | } |
326 | } |
327 | fn is_omitted(&self) -> bool { |
328 | self.name.is_none() |
329 | } |
330 | } |
331 | |
332 | impl SynAttributeHelpers for [syn::Attribute] { |
333 | fn attrs(&self) -> &[syn::Attribute] { |
334 | self |
335 | } |
336 | } |
337 | |
338 | fn split_doc_attr(input: &str) -> Vec<String> { |
339 | inputimpl Iterator |
340 | // Convert two newline (indicate "new paragraph") into two line break. |
341 | .replace(from:" \n\n" , to:" \n \n" ) |
342 | // Convert newline after two spaces (indicate "line break") into line break. |
343 | .split(" \n" ) |
344 | // Convert single newline (indicate hard-wrapped) into space. |
345 | .map(|s: &str| s.replace(from:' \n' , to:" " )) |
346 | .map(|s: String| s.trim_end().to_string()) |
347 | .collect() |
348 | } |
349 | |