1 use crate::clang::{Clang, Node};
2 use crate::syntax::attrs::OtherAttrs;
3 use crate::syntax::namespace::Namespace;
4 use crate::syntax::report::Errors;
5 use crate::syntax::{Api, Discriminant, Doc, Enum, EnumRepr, ForeignName, Pair, Variant};
6 use flate2::write::GzDecoder;
7 use memmap::Mmap;
8 use proc_macro2::{Delimiter, Group, Ident, TokenStream};
9 use quote::{format_ident, quote, quote_spanned};
10 use std::env;
11 use std::fmt::{self, Display};
12 use std::fs::File;
13 use std::io::Write;
14 use std::path::PathBuf;
15 use std::str::FromStr;
16 use syn::{parse_quote, Path};
17
18 const CXX_CLANG_AST: &str = "CXX_CLANG_AST";
19
load(cx: &mut Errors, apis: &mut [Api])20 pub fn load(cx: &mut Errors, apis: &mut [Api]) {
21 let ref mut variants_from_header = Vec::new();
22 for api in apis {
23 if let Api::Enum(enm) = api {
24 if enm.variants_from_header {
25 if enm.variants.is_empty() {
26 variants_from_header.push(enm);
27 } else {
28 let span = span_for_enum_error(enm);
29 cx.error(
30 span,
31 "enum with #![variants_from_header] must be written with no explicit variants",
32 );
33 }
34 }
35 }
36 }
37
38 let span = match variants_from_header.get(0) {
39 None => return,
40 Some(enm) => enm.variants_from_header_attr.clone().unwrap(),
41 };
42
43 let ast_dump_path = match env::var_os(CXX_CLANG_AST) {
44 Some(ast_dump_path) => PathBuf::from(ast_dump_path),
45 None => {
46 let msg = format!(
47 "environment variable ${} has not been provided",
48 CXX_CLANG_AST,
49 );
50 return cx.error(span, msg);
51 }
52 };
53
54 let memmap = File::open(&ast_dump_path).and_then(|file| unsafe { Mmap::map(&file) });
55 let mut gunzipped;
56 let ast_dump_bytes = match match memmap {
57 Ok(ref memmap) => {
58 let is_gzipped = memmap.get(..2) == Some(b"\x1f\x8b");
59 if is_gzipped {
60 gunzipped = Vec::new();
61 let decode_result = GzDecoder::new(&mut gunzipped).write_all(&memmap);
62 decode_result.map(|_| gunzipped.as_slice())
63 } else {
64 Ok(&memmap as &[u8])
65 }
66 }
67 Err(error) => Err(error),
68 } {
69 Ok(bytes) => bytes,
70 Err(error) => {
71 let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
72 return cx.error(span, msg);
73 }
74 };
75
76 let ref root: Node = match serde_json::from_slice(ast_dump_bytes) {
77 Ok(root) => root,
78 Err(error) => {
79 let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
80 return cx.error(span, msg);
81 }
82 };
83
84 let ref mut namespace = Vec::new();
85 traverse(cx, root, namespace, variants_from_header, None);
86
87 for enm in variants_from_header {
88 if enm.variants.is_empty() {
89 let span = &enm.variants_from_header_attr;
90 let name = CxxName(&enm.name);
91 let msg = format!("failed to find any C++ definition of enum {}", name);
92 cx.error(span, msg);
93 }
94 }
95 }
96
traverse<'a>( cx: &mut Errors, node: &'a Node, namespace: &mut Vec<&'a str>, variants_from_header: &mut [&mut Enum], mut idx: Option<usize>, )97 fn traverse<'a>(
98 cx: &mut Errors,
99 node: &'a Node,
100 namespace: &mut Vec<&'a str>,
101 variants_from_header: &mut [&mut Enum],
102 mut idx: Option<usize>,
103 ) {
104 match &node.kind {
105 Clang::NamespaceDecl(decl) => {
106 let name = match &decl.name {
107 Some(name) => name,
108 // Can ignore enums inside an anonymous namespace.
109 None => return,
110 };
111 namespace.push(name);
112 idx = None;
113 }
114 Clang::EnumDecl(decl) => {
115 let name = match &decl.name {
116 Some(name) => name,
117 None => return,
118 };
119 idx = None;
120 for (i, enm) in variants_from_header.iter_mut().enumerate() {
121 if enm.name.cxx == **name && enm.name.namespace.iter().eq(&*namespace) {
122 if !enm.variants.is_empty() {
123 let span = &enm.variants_from_header_attr;
124 let qual_name = CxxName(&enm.name);
125 let msg = format!("found multiple C++ definitions of enum {}", qual_name);
126 cx.error(span, msg);
127 return;
128 }
129 let fixed_underlying_type = match &decl.fixed_underlying_type {
130 Some(fixed_underlying_type) => fixed_underlying_type,
131 None => {
132 let span = &enm.variants_from_header_attr;
133 let name = &enm.name.cxx;
134 let qual_name = CxxName(&enm.name);
135 let msg = format!(
136 "implicit implementation-defined repr for enum {} is not supported yet; consider changing its C++ definition to `enum {}: int {{...}}",
137 qual_name, name,
138 );
139 cx.error(span, msg);
140 return;
141 }
142 };
143 let repr = translate_qual_type(
144 cx,
145 &enm,
146 fixed_underlying_type
147 .desugared_qual_type
148 .as_ref()
149 .unwrap_or(&fixed_underlying_type.qual_type),
150 );
151 enm.repr = EnumRepr::Foreign { rust_type: repr };
152 idx = Some(i);
153 break;
154 }
155 }
156 if idx.is_none() {
157 return;
158 }
159 }
160 Clang::EnumConstantDecl(decl) => {
161 if let Some(idx) = idx {
162 let enm = &mut *variants_from_header[idx];
163 let span = enm
164 .variants_from_header_attr
165 .as_ref()
166 .unwrap()
167 .path
168 .get_ident()
169 .unwrap()
170 .span();
171 let cxx_name = match ForeignName::parse(&decl.name, span) {
172 Ok(foreign_name) => foreign_name,
173 Err(_) => {
174 let span = &enm.variants_from_header_attr;
175 let msg = format!("unsupported C++ variant name: {}", decl.name);
176 return cx.error(span, msg);
177 }
178 };
179 let rust_name: Ident = match syn::parse_str(&decl.name) {
180 Ok(ident) => ident,
181 Err(_) => format_ident!("__Variant{}", enm.variants.len()),
182 };
183 let discriminant = match discriminant_value(&node.inner) {
184 ParsedDiscriminant::Constant(discriminant) => discriminant,
185 ParsedDiscriminant::Successor => match enm.variants.last() {
186 None => Discriminant::zero(),
187 Some(last) => match last.discriminant.checked_succ() {
188 Some(discriminant) => discriminant,
189 None => {
190 let span = &enm.variants_from_header_attr;
191 let msg = format!(
192 "overflow processing discriminant value for variant: {}",
193 decl.name,
194 );
195 return cx.error(span, msg);
196 }
197 },
198 },
199 ParsedDiscriminant::Fail => {
200 let span = &enm.variants_from_header_attr;
201 let msg = format!(
202 "failed to obtain discriminant value for variant: {}",
203 decl.name,
204 );
205 cx.error(span, msg);
206 Discriminant::zero()
207 }
208 };
209 enm.variants.push(Variant {
210 doc: Doc::new(),
211 attrs: OtherAttrs::none(),
212 name: Pair {
213 namespace: Namespace::ROOT,
214 cxx: cxx_name,
215 rust: rust_name,
216 },
217 discriminant,
218 expr: None,
219 });
220 }
221 }
222 _ => {}
223 }
224 for inner in &node.inner {
225 traverse(cx, inner, namespace, variants_from_header, idx);
226 }
227 if let Clang::NamespaceDecl(_) = &node.kind {
228 let _ = namespace.pop().unwrap();
229 }
230 }
231
translate_qual_type(cx: &mut Errors, enm: &Enum, qual_type: &str) -> Path232 fn translate_qual_type(cx: &mut Errors, enm: &Enum, qual_type: &str) -> Path {
233 let rust_std_name = match qual_type {
234 "char" => "c_char",
235 "int" => "c_int",
236 "long" => "c_long",
237 "long long" => "c_longlong",
238 "signed char" => "c_schar",
239 "short" => "c_short",
240 "unsigned char" => "c_uchar",
241 "unsigned int" => "c_uint",
242 "unsigned long" => "c_ulong",
243 "unsigned long long" => "c_ulonglong",
244 "unsigned short" => "c_ushort",
245 unsupported => {
246 let span = &enm.variants_from_header_attr;
247 let qual_name = CxxName(&enm.name);
248 let msg = format!(
249 "unsupported underlying type for {}: {}",
250 qual_name, unsupported,
251 );
252 cx.error(span, msg);
253 "c_int"
254 }
255 };
256 let span = enm
257 .variants_from_header_attr
258 .as_ref()
259 .unwrap()
260 .path
261 .get_ident()
262 .unwrap()
263 .span();
264 let ident = Ident::new(rust_std_name, span);
265 let path = quote_spanned!(span=> ::std::os::raw::#ident);
266 parse_quote!(#path)
267 }
268
269 enum ParsedDiscriminant {
270 Constant(Discriminant),
271 Successor,
272 Fail,
273 }
274
discriminant_value(mut clang: &[Node]) -> ParsedDiscriminant275 fn discriminant_value(mut clang: &[Node]) -> ParsedDiscriminant {
276 if clang.is_empty() {
277 // No discriminant expression provided; use successor of previous
278 // descriminant.
279 return ParsedDiscriminant::Successor;
280 }
281
282 loop {
283 if clang.len() != 1 {
284 return ParsedDiscriminant::Fail;
285 }
286
287 let node = &clang[0];
288 match &node.kind {
289 Clang::ImplicitCastExpr => clang = &node.inner,
290 Clang::ConstantExpr(expr) => match Discriminant::from_str(&expr.value) {
291 Ok(discriminant) => return ParsedDiscriminant::Constant(discriminant),
292 Err(_) => return ParsedDiscriminant::Fail,
293 },
294 _ => return ParsedDiscriminant::Fail,
295 }
296 }
297 }
298
span_for_enum_error(enm: &Enum) -> TokenStream299 fn span_for_enum_error(enm: &Enum) -> TokenStream {
300 let enum_token = enm.enum_token;
301 let mut brace_token = Group::new(Delimiter::Brace, TokenStream::new());
302 brace_token.set_span(enm.brace_token.span);
303 quote!(#enum_token #brace_token)
304 }
305
306 struct CxxName<'a>(&'a Pair);
307
308 impl<'a> Display for CxxName<'a> {
fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result309 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
310 for namespace in &self.0.namespace {
311 write!(formatter, "{}::", namespace)?;
312 }
313 write!(formatter, "{}", self.0.cxx)
314 }
315 }
316