| 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 | use std::borrow::Cow; |
| 6 | use std::collections::hash_map::Entry; |
| 7 | use std::collections::HashMap; |
| 8 | use std::str::FromStr; |
| 9 | |
| 10 | use crate::bindgen::config::{Config, Language}; |
| 11 | use 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)] |
| 28 | pub 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)] |
| 36 | pub struct AnnotationSet { |
| 37 | annotations: HashMap<String, AnnotationValue>, |
| 38 | pub must_use: bool, |
| 39 | pub deprecated: Option<String>, |
| 40 | } |
| 41 | |
| 42 | pub enum DeprecatedNoteKind { |
| 43 | Function, |
| 44 | Struct, |
| 45 | Enum, |
| 46 | } |
| 47 | |
| 48 | impl 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. |
| 203 | fn 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 | |