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
7use syn::ext::IdentExt;
8
9pub 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
15impl<I> IterHelpers for I
16where
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
33pub trait SynItemHelpers: SynAttributeHelpers {
34 fn exported_name(&self) -> Option<String>;
35}
36
37impl 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
51impl 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
65impl 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.
82fn 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
113pub 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
233macro_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
258impl 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
267macro_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
277impl_syn_item_helper!(syn::ItemExternCrate);
278impl_syn_item_helper!(syn::ItemUse);
279impl_syn_item_helper!(syn::ItemStatic);
280impl_syn_item_helper!(syn::ItemConst);
281impl_syn_item_helper!(syn::ItemFn);
282impl_syn_item_helper!(syn::ImplItemMethod);
283impl_syn_item_helper!(syn::ItemMod);
284impl_syn_item_helper!(syn::ItemForeignMod);
285impl_syn_item_helper!(syn::ItemType);
286impl_syn_item_helper!(syn::ItemStruct);
287impl_syn_item_helper!(syn::ItemEnum);
288impl_syn_item_helper!(syn::ItemUnion);
289impl_syn_item_helper!(syn::ItemTrait);
290impl_syn_item_helper!(syn::ItemImpl);
291impl_syn_item_helper!(syn::ItemMacro);
292impl_syn_item_helper!(syn::ItemMacro2);
293impl_syn_item_helper!(syn::ItemTraitAlias);
294
295/// Helper function for accessing Abi information
296pub trait SynAbiHelpers {
297 fn is_c(&self) -> bool;
298 fn is_omitted(&self) -> bool;
299}
300
301impl 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
319impl 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
332impl SynAttributeHelpers for [syn::Attribute] {
333 fn attrs(&self) -> &[syn::Attribute] {
334 self
335 }
336}
337
338fn 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