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
5use std::borrow::Cow;
6use std::collections::hash_map::Entry;
7use std::collections::HashMap;
8use std::str::FromStr;
9
10use crate::bindgen::config::{Config, Language};
11use crate::bindgen::utilities::SynAttributeHelpers;
12
13// A system for specifying properties on items. Annotations are
14// given through document comments and parsed by this code.
15//
16// An annotation is in the form cbindgen:PROPERTY=VALUE
17// Where PROPERTY depends on the item
18// Where VALUE can be
19// * list - [Item1, Item2, Item3, ...]
20// * atom - Foo
21// * bool - true,false
22// Examples:
23// * cbindgen:field-names=[mHandle, mNamespace]
24// * cbindgen:function-postfix=WR_DESTRUCTOR_SAFE
25
26/// A value specified by an annotation.
27#[derive(Debug, Clone)]
28pub enum AnnotationValue {
29 List(Vec<String>),
30 Atom(Option<String>),
31 Bool(bool),
32}
33
34/// A set of annotations specified by a document comment.
35#[derive(Debug, Default, Clone)]
36pub struct AnnotationSet {
37 annotations: HashMap<String, AnnotationValue>,
38 pub must_use: bool,
39 pub deprecated: Option<String>,
40}
41
42pub enum DeprecatedNoteKind {
43 Function,
44 Struct,
45 Enum,
46}
47
48impl AnnotationSet {
49 pub fn new() -> AnnotationSet {
50 AnnotationSet {
51 annotations: HashMap::new(),
52 must_use: false,
53 deprecated: None,
54 }
55 }
56
57 pub fn is_empty(&self) -> bool {
58 self.annotations.is_empty() && !self.must_use
59 }
60
61 pub(crate) fn must_use(&self, config: &Config) -> bool {
62 self.must_use && config.language != Language::Cython
63 }
64
65 pub(crate) fn deprecated_note<'c>(
66 &self,
67 config: &'c Config,
68 kind: DeprecatedNoteKind,
69 ) -> Option<Cow<'c, str>> {
70 let note = self.deprecated.as_deref()?;
71
72 if config.language == Language::Cython {
73 return None;
74 }
75
76 if note.is_empty() {
77 return Some(Cow::Borrowed(match kind {
78 DeprecatedNoteKind::Enum => config.enumeration.deprecated.as_deref()?,
79 DeprecatedNoteKind::Function => config.function.deprecated.as_deref()?,
80 DeprecatedNoteKind::Struct => config.structure.deprecated.as_deref()?,
81 }));
82 }
83
84 let format = match kind {
85 DeprecatedNoteKind::Enum => &config.enumeration.deprecated_with_note,
86 DeprecatedNoteKind::Function => &config.function.deprecated_with_note,
87 DeprecatedNoteKind::Struct => &config.structure.deprecated_with_note,
88 }
89 .as_ref()?;
90 Some(Cow::Owned(format.replace("{}", &format!("{:?}", note))))
91 }
92
93 pub fn load(attrs: &[syn::Attribute]) -> Result<AnnotationSet, String> {
94 let lines = attrs.get_comment_lines();
95 let lines: Vec<&str> = lines
96 .iter()
97 .filter_map(|line| {
98 let line = line.trim_start();
99 if !line.starts_with("cbindgen:") {
100 return None;
101 }
102
103 Some(line)
104 })
105 .collect();
106
107 let must_use = attrs.has_attr_word("must_use");
108 let deprecated = attrs.find_deprecated_note();
109 let mut annotations = HashMap::new();
110
111 // Look at each line for an annotation
112 for line in lines {
113 debug_assert!(line.starts_with("cbindgen:"));
114
115 // Remove the "cbindgen:" prefix
116 let annotation = &line[9..];
117
118 // Split the annotation in two
119 let parts: Vec<&str> = annotation.split('=').map(|x| x.trim()).collect();
120
121 if parts.len() > 2 {
122 return Err(format!("Couldn't parse {}.", line));
123 }
124
125 // Grab the name that this annotation is modifying
126 let name = parts[0];
127
128 // If the annotation only has a name, assume it's setting a bool flag
129 if parts.len() == 1 {
130 annotations.insert(name.to_string(), AnnotationValue::Bool(true));
131 continue;
132 }
133
134 // Parse the value we're setting the name to
135 let value = parts[1];
136
137 if let Some(x) = parse_list(value) {
138 annotations.insert(name.to_string(), AnnotationValue::List(x));
139 continue;
140 }
141 if let Ok(x) = value.parse::<bool>() {
142 annotations.insert(name.to_string(), AnnotationValue::Bool(x));
143 continue;
144 }
145 annotations.insert(
146 name.to_string(),
147 if value.is_empty() {
148 AnnotationValue::Atom(None)
149 } else {
150 AnnotationValue::Atom(Some(value.to_string()))
151 },
152 );
153 }
154
155 Ok(AnnotationSet {
156 annotations,
157 must_use,
158 deprecated,
159 })
160 }
161
162 /// Adds an annotation value if none is specified.
163 pub fn add_default(&mut self, name: &str, value: AnnotationValue) {
164 if let Entry::Vacant(e) = self.annotations.entry(name.to_string()) {
165 e.insert(value);
166 }
167 }
168
169 pub fn list(&self, name: &str) -> Option<Vec<String>> {
170 match self.annotations.get(name) {
171 Some(AnnotationValue::List(x)) => Some(x.clone()),
172 _ => None,
173 }
174 }
175 pub fn atom(&self, name: &str) -> Option<Option<String>> {
176 match self.annotations.get(name) {
177 Some(AnnotationValue::Atom(x)) => Some(x.clone()),
178 _ => None,
179 }
180 }
181 pub fn bool(&self, name: &str) -> Option<bool> {
182 match self.annotations.get(name) {
183 Some(AnnotationValue::Bool(x)) => Some(*x),
184 _ => None,
185 }
186 }
187
188 pub fn parse_atom<T>(&self, name: &str) -> Option<T>
189 where
190 T: Default + FromStr,
191 {
192 match self.annotations.get(name) {
193 Some(AnnotationValue::Atom(x)) => Some(
194 x.as_ref()
195 .map_or(T::default(), |y| y.parse::<T>().ok().unwrap()),
196 ),
197 _ => None,
198 }
199 }
200}
201
202/// Parse lists like "[x, y, z]". This is not implemented efficiently or well.
203fn parse_list(list: &str) -> Option<Vec<String>> {
204 if list.len() < 2 {
205 return None;
206 }
207
208 match (list.chars().next(), list.chars().last()) {
209 (Some('['), Some(']')) => Some(
210 listimpl Iterator[1..list.len() - 1]
211 .split(',')
212 .map(|x: &str| x.trim().to_string())
213 .collect(),
214 ),
215 _ => None,
216 }
217}
218