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