aboutsummaryrefslogtreecommitdiff
path: root/macro/src/load.rs
diff options
context:
space:
mode:
Diffstat (limited to 'macro/src/load.rs')
-rw-r--r--macro/src/load.rs315
1 files changed, 315 insertions, 0 deletions
diff --git a/macro/src/load.rs b/macro/src/load.rs
new file mode 100644
index 00000000..d769ebf6
--- /dev/null
+++ b/macro/src/load.rs
@@ -0,0 +1,315 @@
+use crate::clang::{Clang, Node};
+use crate::syntax::attrs::OtherAttrs;
+use crate::syntax::namespace::Namespace;
+use crate::syntax::report::Errors;
+use crate::syntax::{Api, Discriminant, Doc, Enum, EnumRepr, ForeignName, Pair, Variant};
+use flate2::write::GzDecoder;
+use memmap::Mmap;
+use proc_macro2::{Delimiter, Group, Ident, TokenStream};
+use quote::{format_ident, quote, quote_spanned};
+use std::env;
+use std::fmt::{self, Display};
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+use std::str::FromStr;
+use syn::{parse_quote, Path};
+
+const CXX_CLANG_AST: &str = "CXX_CLANG_AST";
+
+pub fn load(cx: &mut Errors, apis: &mut [Api]) {
+ let ref mut variants_from_header = Vec::new();
+ for api in apis {
+ if let Api::Enum(enm) = api {
+ if enm.variants_from_header {
+ if enm.variants.is_empty() {
+ variants_from_header.push(enm);
+ } else {
+ let span = span_for_enum_error(enm);
+ cx.error(
+ span,
+ "enum with #![variants_from_header] must be written with no explicit variants",
+ );
+ }
+ }
+ }
+ }
+
+ let span = match variants_from_header.get(0) {
+ None => return,
+ Some(enm) => enm.variants_from_header_attr.clone().unwrap(),
+ };
+
+ let ast_dump_path = match env::var_os(CXX_CLANG_AST) {
+ Some(ast_dump_path) => PathBuf::from(ast_dump_path),
+ None => {
+ let msg = format!(
+ "environment variable ${} has not been provided",
+ CXX_CLANG_AST,
+ );
+ return cx.error(span, msg);
+ }
+ };
+
+ let memmap = File::open(&ast_dump_path).and_then(|file| unsafe { Mmap::map(&file) });
+ let mut gunzipped;
+ let ast_dump_bytes = match match memmap {
+ Ok(ref memmap) => {
+ let is_gzipped = memmap.get(..2) == Some(b"\x1f\x8b");
+ if is_gzipped {
+ gunzipped = Vec::new();
+ let decode_result = GzDecoder::new(&mut gunzipped).write_all(&memmap);
+ decode_result.map(|_| gunzipped.as_slice())
+ } else {
+ Ok(&memmap as &[u8])
+ }
+ }
+ Err(error) => Err(error),
+ } {
+ Ok(bytes) => bytes,
+ Err(error) => {
+ let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
+ return cx.error(span, msg);
+ }
+ };
+
+ let ref root: Node = match serde_json::from_slice(ast_dump_bytes) {
+ Ok(root) => root,
+ Err(error) => {
+ let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
+ return cx.error(span, msg);
+ }
+ };
+
+ let ref mut namespace = Vec::new();
+ traverse(cx, root, namespace, variants_from_header, None);
+
+ for enm in variants_from_header {
+ if enm.variants.is_empty() {
+ let span = &enm.variants_from_header_attr;
+ let name = CxxName(&enm.name);
+ let msg = format!("failed to find any C++ definition of enum {}", name);
+ cx.error(span, msg);
+ }
+ }
+}
+
+fn traverse<'a>(
+ cx: &mut Errors,
+ node: &'a Node,
+ namespace: &mut Vec<&'a str>,
+ variants_from_header: &mut [&mut Enum],
+ mut idx: Option<usize>,
+) {
+ match &node.kind {
+ Clang::NamespaceDecl(decl) => {
+ let name = match &decl.name {
+ Some(name) => name,
+ // Can ignore enums inside an anonymous namespace.
+ None => return,
+ };
+ namespace.push(name);
+ idx = None;
+ }
+ Clang::EnumDecl(decl) => {
+ let name = match &decl.name {
+ Some(name) => name,
+ None => return,
+ };
+ idx = None;
+ for (i, enm) in variants_from_header.iter_mut().enumerate() {
+ if enm.name.cxx == **name && enm.name.namespace.iter().eq(&*namespace) {
+ if !enm.variants.is_empty() {
+ let span = &enm.variants_from_header_attr;
+ let qual_name = CxxName(&enm.name);
+ let msg = format!("found multiple C++ definitions of enum {}", qual_name);
+ cx.error(span, msg);
+ return;
+ }
+ let fixed_underlying_type = match &decl.fixed_underlying_type {
+ Some(fixed_underlying_type) => fixed_underlying_type,
+ None => {
+ let span = &enm.variants_from_header_attr;
+ let name = &enm.name.cxx;
+ let qual_name = CxxName(&enm.name);
+ let msg = format!(
+ "implicit implementation-defined repr for enum {} is not supported yet; consider changing its C++ definition to `enum {}: int {{...}}",
+ qual_name, name,
+ );
+ cx.error(span, msg);
+ return;
+ }
+ };
+ let repr = translate_qual_type(
+ cx,
+ &enm,
+ fixed_underlying_type
+ .desugared_qual_type
+ .as_ref()
+ .unwrap_or(&fixed_underlying_type.qual_type),
+ );
+ enm.repr = EnumRepr::Foreign { rust_type: repr };
+ idx = Some(i);
+ break;
+ }
+ }
+ if idx.is_none() {
+ return;
+ }
+ }
+ Clang::EnumConstantDecl(decl) => {
+ if let Some(idx) = idx {
+ let enm = &mut *variants_from_header[idx];
+ let span = enm
+ .variants_from_header_attr
+ .as_ref()
+ .unwrap()
+ .path
+ .get_ident()
+ .unwrap()
+ .span();
+ let cxx_name = match ForeignName::parse(&decl.name, span) {
+ Ok(foreign_name) => foreign_name,
+ Err(_) => {
+ let span = &enm.variants_from_header_attr;
+ let msg = format!("unsupported C++ variant name: {}", decl.name);
+ return cx.error(span, msg);
+ }
+ };
+ let rust_name: Ident = match syn::parse_str(&decl.name) {
+ Ok(ident) => ident,
+ Err(_) => format_ident!("__Variant{}", enm.variants.len()),
+ };
+ let discriminant = match discriminant_value(&node.inner) {
+ ParsedDiscriminant::Constant(discriminant) => discriminant,
+ ParsedDiscriminant::Successor => match enm.variants.last() {
+ None => Discriminant::zero(),
+ Some(last) => match last.discriminant.checked_succ() {
+ Some(discriminant) => discriminant,
+ None => {
+ let span = &enm.variants_from_header_attr;
+ let msg = format!(
+ "overflow processing discriminant value for variant: {}",
+ decl.name,
+ );
+ return cx.error(span, msg);
+ }
+ },
+ },
+ ParsedDiscriminant::Fail => {
+ let span = &enm.variants_from_header_attr;
+ let msg = format!(
+ "failed to obtain discriminant value for variant: {}",
+ decl.name,
+ );
+ cx.error(span, msg);
+ Discriminant::zero()
+ }
+ };
+ enm.variants.push(Variant {
+ doc: Doc::new(),
+ attrs: OtherAttrs::none(),
+ name: Pair {
+ namespace: Namespace::ROOT,
+ cxx: cxx_name,
+ rust: rust_name,
+ },
+ discriminant,
+ expr: None,
+ });
+ }
+ }
+ _ => {}
+ }
+ for inner in &node.inner {
+ traverse(cx, inner, namespace, variants_from_header, idx);
+ }
+ if let Clang::NamespaceDecl(_) = &node.kind {
+ let _ = namespace.pop().unwrap();
+ }
+}
+
+fn translate_qual_type(cx: &mut Errors, enm: &Enum, qual_type: &str) -> Path {
+ let rust_std_name = match qual_type {
+ "char" => "c_char",
+ "int" => "c_int",
+ "long" => "c_long",
+ "long long" => "c_longlong",
+ "signed char" => "c_schar",
+ "short" => "c_short",
+ "unsigned char" => "c_uchar",
+ "unsigned int" => "c_uint",
+ "unsigned long" => "c_ulong",
+ "unsigned long long" => "c_ulonglong",
+ "unsigned short" => "c_ushort",
+ unsupported => {
+ let span = &enm.variants_from_header_attr;
+ let qual_name = CxxName(&enm.name);
+ let msg = format!(
+ "unsupported underlying type for {}: {}",
+ qual_name, unsupported,
+ );
+ cx.error(span, msg);
+ "c_int"
+ }
+ };
+ let span = enm
+ .variants_from_header_attr
+ .as_ref()
+ .unwrap()
+ .path
+ .get_ident()
+ .unwrap()
+ .span();
+ let ident = Ident::new(rust_std_name, span);
+ let path = quote_spanned!(span=> ::std::os::raw::#ident);
+ parse_quote!(#path)
+}
+
+enum ParsedDiscriminant {
+ Constant(Discriminant),
+ Successor,
+ Fail,
+}
+
+fn discriminant_value(mut clang: &[Node]) -> ParsedDiscriminant {
+ if clang.is_empty() {
+ // No discriminant expression provided; use successor of previous
+ // descriminant.
+ return ParsedDiscriminant::Successor;
+ }
+
+ loop {
+ if clang.len() != 1 {
+ return ParsedDiscriminant::Fail;
+ }
+
+ let node = &clang[0];
+ match &node.kind {
+ Clang::ImplicitCastExpr => clang = &node.inner,
+ Clang::ConstantExpr(expr) => match Discriminant::from_str(&expr.value) {
+ Ok(discriminant) => return ParsedDiscriminant::Constant(discriminant),
+ Err(_) => return ParsedDiscriminant::Fail,
+ },
+ _ => return ParsedDiscriminant::Fail,
+ }
+ }
+}
+
+fn span_for_enum_error(enm: &Enum) -> TokenStream {
+ let enum_token = enm.enum_token;
+ let mut brace_token = Group::new(Delimiter::Brace, TokenStream::new());
+ brace_token.set_span(enm.brace_token.span);
+ quote!(#enum_token #brace_token)
+}
+
+struct CxxName<'a>(&'a Pair);
+
+impl<'a> Display for CxxName<'a> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ for namespace in &self.0.namespace {
+ write!(formatter, "{}::", namespace)?;
+ }
+ write!(formatter, "{}", self.0.cxx)
+ }
+}