Squashed 'third_party/autocxx/' content from commit 629e8fa53
git-subtree-dir: third_party/autocxx
git-subtree-split: 629e8fa531a633164c0b52e2a3cab536d4cd0849
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I62a03b0049f49adf029e0204639cdb5468dde1a1
diff --git a/engine/src/parse_file.rs b/engine/src/parse_file.rs
new file mode 100644
index 0000000..3571d19
--- /dev/null
+++ b/engine/src/parse_file.rs
@@ -0,0 +1,414 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::ast_discoverer::{Discoveries, DiscoveryErr};
+use crate::output_generators::RsOutput;
+use crate::{
+ cxxbridge::CxxBridge, Error as EngineError, GeneratedCpp, IncludeCppEngine,
+ RebuildDependencyRecorder,
+};
+use crate::{CppCodegenOptions, LocatedSynError};
+use autocxx_parser::directive_names::SUBCLASS;
+use autocxx_parser::{AllowlistEntry, RustPath, Subclass, SubclassAttrs};
+use indexmap::set::IndexSet as HashSet;
+use miette::Diagnostic;
+use quote::ToTokens;
+use std::{io::Read, path::PathBuf};
+use std::{panic::UnwindSafe, path::Path, rc::Rc};
+use syn::{token::Brace, Item, ItemMod};
+use thiserror::Error;
+
+/// Errors which may occur when parsing a Rust source file to discover
+/// and interpret include_cxx macros.
+#[derive(Error, Diagnostic, Debug)]
+pub enum ParseError {
+ #[error("unable to open the source file: {0}")]
+ FileOpen(std::io::Error),
+ #[error("the .rs file couldn't be read: {0}")]
+ FileRead(std::io::Error),
+ #[error("syntax error interpreting Rust code: {0}")]
+ #[diagnostic(transparent)]
+ Syntax(LocatedSynError),
+ #[error("generate!/generate_ns! was used at the same time as generate_all!")]
+ ConflictingAllowlist,
+ #[error("the subclass attribute couldn't be parsed: {0}")]
+ #[diagnostic(transparent)]
+ SubclassSyntax(LocatedSynError),
+ /// The include CPP macro could not be expanded into
+ /// Rust bindings to C++, because of some problem during the conversion
+ /// process. This could be anything from a C++ parsing error to some
+ /// C++ feature that autocxx can't yet handle and isn't able to skip
+ /// over. It could also cover errors in your syntax of the `include_cpp`
+ /// macro or the directives inside.
+ #[error("the include_cpp! macro couldn't be expanded into Rust bindings to C++: {0}")]
+ #[diagnostic(transparent)]
+ AutocxxCodegenError(EngineError),
+ /// There are two or more `include_cpp` macros with the same
+ /// mod name.
+ #[error("there are two or more include_cpp! mods with the same mod name")]
+ ConflictingModNames,
+ #[error("dynamic discovery was enabled but no mod was found")]
+ ZeroModsForDynamicDiscovery,
+ #[error("dynamic discovery was enabled but multiple mods were found")]
+ MultipleModsForDynamicDiscovery,
+ #[error("a problem occurred while discovering C++ APIs used within the Rust: {0}")]
+ Discovery(DiscoveryErr),
+}
+
+/// Parse a Rust file, and spot any include_cpp macros within it.
+pub fn parse_file<P1: AsRef<Path>>(
+ rs_file: P1,
+ auto_allowlist: bool,
+) -> Result<ParsedFile, ParseError> {
+ let mut source_code = String::new();
+ let mut file = std::fs::File::open(rs_file).map_err(ParseError::FileOpen)?;
+ file.read_to_string(&mut source_code)
+ .map_err(ParseError::FileRead)?;
+ proc_macro2::fallback::force();
+ let source = syn::parse_file(&source_code)
+ .map_err(|e| ParseError::Syntax(LocatedSynError::new(e, &source_code)))?;
+ parse_file_contents(source, auto_allowlist, &source_code)
+}
+
+fn parse_file_contents(
+ source: syn::File,
+ auto_allowlist: bool,
+ file_contents: &str,
+) -> Result<ParsedFile, ParseError> {
+ #[derive(Default)]
+ struct State {
+ auto_allowlist: bool,
+ results: Vec<Segment>,
+ extra_superclasses: Vec<Subclass>,
+ discoveries: Discoveries,
+ }
+ impl State {
+ fn parse_item(
+ &mut self,
+ item: Item,
+ mod_path: Option<RustPath>,
+ file_contents: &str,
+ ) -> Result<(), ParseError> {
+ let result = match item {
+ Item::Macro(mac)
+ if mac
+ .mac
+ .path
+ .segments
+ .last()
+ .map(|s| s.ident == "include_cpp")
+ .unwrap_or(false) =>
+ {
+ Segment::Autocxx(
+ crate::IncludeCppEngine::new_from_syn(mac.mac, file_contents)
+ .map_err(ParseError::AutocxxCodegenError)?,
+ )
+ }
+ Item::Mod(itm)
+ if itm
+ .attrs
+ .iter()
+ .any(|attr| attr.path.to_token_stream().to_string() == "cxx :: bridge") =>
+ {
+ Segment::Cxx(CxxBridge::from(itm))
+ }
+ Item::Mod(itm) => {
+ if let Some((brace, items)) = itm.content {
+ let mut mod_state = State {
+ auto_allowlist: self.auto_allowlist,
+ ..Default::default()
+ };
+ let mod_path = match &mod_path {
+ None => RustPath::new_from_ident(itm.ident.clone()),
+ Some(mod_path) => mod_path.append(itm.ident.clone()),
+ };
+ for item in items {
+ mod_state.parse_item(item, Some(mod_path.clone()), file_contents)?
+ }
+ self.extra_superclasses.extend(mod_state.extra_superclasses);
+ self.discoveries.extend(mod_state.discoveries);
+ Segment::Mod(
+ mod_state.results,
+ (
+ brace,
+ ItemMod {
+ content: None,
+ ..itm
+ },
+ ),
+ )
+ } else {
+ Segment::Other(Item::Mod(itm))
+ }
+ }
+ Item::Struct(ref its) if self.auto_allowlist => {
+ let attrs = &its.attrs;
+ let is_superclass_attr = attrs.iter().find(|attr| {
+ attr.path
+ .segments
+ .last()
+ .map(|seg| seg.ident == "is_subclass" || seg.ident == SUBCLASS)
+ .unwrap_or(false)
+ });
+ if let Some(is_superclass_attr) = is_superclass_attr {
+ if !is_superclass_attr.tokens.is_empty() {
+ let subclass = its.ident.clone();
+ let args: SubclassAttrs =
+ is_superclass_attr.parse_args().map_err(|e| {
+ ParseError::SubclassSyntax(LocatedSynError::new(
+ e,
+ file_contents,
+ ))
+ })?;
+ if let Some(superclass) = args.superclass {
+ self.extra_superclasses.push(Subclass {
+ superclass,
+ subclass,
+ })
+ }
+ }
+ }
+ self.discoveries
+ .search_item(&item, mod_path)
+ .map_err(ParseError::Discovery)?;
+ Segment::Other(item)
+ }
+ _ => {
+ self.discoveries
+ .search_item(&item, mod_path)
+ .map_err(ParseError::Discovery)?;
+ Segment::Other(item)
+ }
+ };
+ self.results.push(result);
+ Ok(())
+ }
+ }
+ let mut state = State {
+ auto_allowlist,
+ ..Default::default()
+ };
+ for item in source.items {
+ state.parse_item(item, None, file_contents)?
+ }
+ let State {
+ auto_allowlist,
+ mut results,
+ mut extra_superclasses,
+ mut discoveries,
+ } = state;
+
+ let must_handle_discovered_things = discoveries.found_rust()
+ || !extra_superclasses.is_empty()
+ || (auto_allowlist && discoveries.found_allowlist());
+
+ // We do not want to enter this 'if' block unless the above conditions are true,
+ // since we may emit errors.
+ if must_handle_discovered_things {
+ let mut autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
+ Segment::Autocxx(engine) => Some(engine),
+ _ => None,
+ });
+ let our_seg = autocxx_seg_iterator.next();
+ match our_seg {
+ None => return Err(ParseError::ZeroModsForDynamicDiscovery),
+ Some(engine) => {
+ engine
+ .config_mut()
+ .subclasses
+ .append(&mut extra_superclasses);
+ if auto_allowlist {
+ for cpp in discoveries.cpp_list {
+ engine
+ .config_mut()
+ .allowlist
+ .push(AllowlistEntry::Item(cpp))
+ .map_err(|_| ParseError::ConflictingAllowlist)?;
+ }
+ }
+ engine
+ .config_mut()
+ .extern_rust_funs
+ .append(&mut discoveries.extern_rust_funs);
+ engine
+ .config_mut()
+ .rust_types
+ .append(&mut discoveries.extern_rust_types);
+ }
+ }
+ if autocxx_seg_iterator.next().is_some() {
+ return Err(ParseError::MultipleModsForDynamicDiscovery);
+ }
+ }
+ let autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
+ Segment::Autocxx(engine) => Some(engine),
+ _ => None,
+ });
+ for seg in autocxx_seg_iterator {
+ seg.config.confirm_complete();
+ }
+ Ok(ParsedFile(results))
+}
+
+/// A Rust file parsed by autocxx. May contain zero or more autocxx 'engines',
+/// i.e. the `IncludeCpp` class, corresponding to zero or more include_cpp
+/// macros within this file. Also contains `syn::Item` structures for all
+/// the rest of the Rust code, such that it can be reconstituted if necessary.
+pub struct ParsedFile(Vec<Segment>);
+
+#[allow(clippy::large_enum_variant)]
+enum Segment {
+ Autocxx(IncludeCppEngine),
+ Cxx(CxxBridge),
+ Mod(Vec<Segment>, (Brace, ItemMod)),
+ Other(Item),
+}
+
+pub trait CppBuildable {
+ fn generate_h_and_cxx(
+ &self,
+ cpp_codegen_options: &CppCodegenOptions,
+ ) -> Result<GeneratedCpp, cxx_gen::Error>;
+}
+
+impl ParsedFile {
+ /// Get all the autocxx `include_cpp` macros found in this file.
+ pub fn get_autocxxes(&self) -> impl Iterator<Item = &IncludeCppEngine> {
+ fn do_get_autocxxes(segments: &[Segment]) -> impl Iterator<Item = &IncludeCppEngine> {
+ segments
+ .iter()
+ .flat_map(|s| -> Box<dyn Iterator<Item = &IncludeCppEngine>> {
+ match s {
+ Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
+ Segment::Mod(segments, _) => Box::new(do_get_autocxxes(segments)),
+ _ => Box::new(std::iter::empty()),
+ }
+ })
+ }
+
+ do_get_autocxxes(&self.0)
+ }
+
+ /// Get all the areas of Rust code which need to be built for these bindings.
+ /// A shortcut for `get_autocxxes()` then calling `get_rs_output` on each.
+ pub fn get_rs_outputs(&self) -> impl Iterator<Item = RsOutput> {
+ self.get_autocxxes().map(|autocxx| autocxx.get_rs_output())
+ }
+
+ /// Get all items which can result in C++ code
+ pub fn get_cpp_buildables(&self) -> impl Iterator<Item = &dyn CppBuildable> {
+ fn do_get_cpp_buildables(segments: &[Segment]) -> impl Iterator<Item = &dyn CppBuildable> {
+ segments
+ .iter()
+ .flat_map(|s| -> Box<dyn Iterator<Item = &dyn CppBuildable>> {
+ match s {
+ Segment::Autocxx(includecpp) => {
+ Box::new(std::iter::once(includecpp as &dyn CppBuildable))
+ }
+ Segment::Cxx(cxxbridge) => {
+ Box::new(std::iter::once(cxxbridge as &dyn CppBuildable))
+ }
+ Segment::Mod(segments, _) => Box::new(do_get_cpp_buildables(segments)),
+ _ => Box::new(std::iter::empty()),
+ }
+ })
+ }
+
+ do_get_cpp_buildables(&self.0)
+ }
+
+ fn get_autocxxes_mut(&mut self) -> impl Iterator<Item = &mut IncludeCppEngine> {
+ fn do_get_autocxxes_mut(
+ segments: &mut [Segment],
+ ) -> impl Iterator<Item = &mut IncludeCppEngine> {
+ segments
+ .iter_mut()
+ .flat_map(|s| -> Box<dyn Iterator<Item = &mut IncludeCppEngine>> {
+ match s {
+ Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
+ Segment::Mod(segments, _) => Box::new(do_get_autocxxes_mut(segments)),
+ _ => Box::new(std::iter::empty()),
+ }
+ })
+ }
+
+ do_get_autocxxes_mut(&mut self.0)
+ }
+
+ /// Determines the include dirs that were set for each include_cpp, so they can be
+ /// used as input to a `cc::Build`.
+ #[cfg(any(test, feature = "build"))]
+ pub(crate) fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
+ fn do_get_include_dirs(segments: &[Segment]) -> impl Iterator<Item = &PathBuf> {
+ segments
+ .iter()
+ .flat_map(|s| -> Box<dyn Iterator<Item = &PathBuf>> {
+ match s {
+ Segment::Autocxx(includecpp) => Box::new(includecpp.include_dirs()),
+ Segment::Mod(segments, _) => Box::new(do_get_include_dirs(segments)),
+ _ => Box::new(std::iter::empty()),
+ }
+ })
+ }
+
+ do_get_include_dirs(&self.0)
+ }
+
+ pub fn resolve_all(
+ &mut self,
+ autocxx_inc: Vec<PathBuf>,
+ extra_clang_args: &[&str],
+ dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
+ cpp_codegen_options: &CppCodegenOptions,
+ ) -> Result<(), ParseError> {
+ let mut mods_found = HashSet::new();
+ let inner_dep_recorder: Option<Rc<dyn RebuildDependencyRecorder>> =
+ dep_recorder.map(Rc::from);
+ for include_cpp in self.get_autocxxes_mut() {
+ #[allow(clippy::manual_map)] // because of dyn shenanigans
+ let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = match &inner_dep_recorder
+ {
+ None => None,
+ Some(inner_dep_recorder) => Some(Box::new(CompositeDepRecorder::new(
+ inner_dep_recorder.clone(),
+ ))),
+ };
+ if !mods_found.insert(include_cpp.get_mod_name()) {
+ return Err(ParseError::ConflictingModNames);
+ }
+ include_cpp
+ .generate(
+ autocxx_inc.clone(),
+ extra_clang_args,
+ dep_recorder,
+ cpp_codegen_options,
+ )
+ .map_err(ParseError::AutocxxCodegenError)?
+ }
+ Ok(())
+ }
+}
+
+/// Shenanigans required to share the same RebuildDependencyRecorder
+/// with all of the include_cpp instances in this one file.
+#[derive(Debug, Clone)]
+struct CompositeDepRecorder(Rc<dyn RebuildDependencyRecorder>);
+
+impl CompositeDepRecorder {
+ fn new(inner: Rc<dyn RebuildDependencyRecorder>) -> Self {
+ CompositeDepRecorder(inner)
+ }
+}
+
+impl UnwindSafe for CompositeDepRecorder {}
+
+impl RebuildDependencyRecorder for CompositeDepRecorder {
+ fn record_header_file_dependency(&self, filename: &str) {
+ self.0.record_header_file_dependency(filename);
+ }
+}