From f265f9e55f4363f7cce7057868d7ad8376ee9de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20J=2E=20Saraiva?= Date: Sat, 25 Jan 2025 18:13:04 +0000 Subject: [PATCH] Resolve the order of dependencies in the clike language backend. --- .clippy.toml | 2 +- Cargo.lock | 10 + Cargo.toml | 1 + src/bindgen/ir/item.rs | 3 + src/bindgen/ir/ty.rs | 42 +- src/bindgen/language_backend/clike.rs | 642 +++++++++++++++++- src/bindgen/language_backend/cython.rs | 6 +- src/bindgen/language_backend/mod.rs | 2 + src/bindgen/library.rs | 7 + tests/clike_language_backend.rs | 197 ++++++ tests/expectations/forward_declaration.c | 10 +- .../expectations/forward_declaration.compat.c | 10 +- tests/expectations/forward_declaration.cpp | 2 + tests/expectations/forward_declaration_both.c | 2 + .../forward_declaration_both.compat.c | 2 + tests/expectations/forward_declaration_tag.c | 2 + .../forward_declaration_tag.compat.c | 2 + 17 files changed, 923 insertions(+), 19 deletions(-) create mode 100644 tests/clike_language_backend.rs diff --git a/.clippy.toml b/.clippy.toml index 4785aed47..08df7d895 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,2 +1,2 @@ # Specify the minimum supported Rust version -msrv = "1.70" +msrv = "1.74" diff --git a/Cargo.lock b/Cargo.lock index 43efb9cb3..02fde0383 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,7 @@ dependencies = [ "heck", "indexmap", "log", + "multimap", "pretty_assertions", "proc-macro2", "quote", @@ -236,6 +237,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +dependencies = [ + "serde", +] + [[package]] name = "once_cell" version = "1.19.0" diff --git a/Cargo.toml b/Cargo.toml index a0278d09a..e70aac5fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ exclude = [ clap = { version = "4.3", optional = true } indexmap = "2.1.0" log = "0.4" +multimap = "0.10" serde = { version = "1.0.103", default-features = false, features = ["derive"] } serde_json = "1.0" tempfile = "3" diff --git a/src/bindgen/ir/item.rs b/src/bindgen/ir/item.rs index 03e1c153a..4c01b9302 100644 --- a/src/bindgen/ir/item.rs +++ b/src/bindgen/ir/item.rs @@ -64,6 +64,8 @@ pub enum ItemContainer { Union(Union), Enum(Enum), Typedef(Typedef), + /// Generated by the language backend to resolve the order of dependencies. + Declaration(Box), } impl ItemContainer { @@ -76,6 +78,7 @@ impl ItemContainer { ItemContainer::Union(ref x) => x, ItemContainer::Enum(ref x) => x, ItemContainer::Typedef(ref x) => x, + ItemContainer::Declaration(ref x) => x.deref(), } } } diff --git a/src/bindgen/ir/ty.rs b/src/bindgen/ir/ty.rs index 2d0d692ae..21862eeec 100644 --- a/src/bindgen/ir/ty.rs +++ b/src/bindgen/ir/ty.rs @@ -568,7 +568,7 @@ impl Type { } pub fn simplify_standard_types(&mut self, config: &Config) { - self.visit_types(|ty| ty.simplify_standard_types(config)); + self.visit_types_mut(|ty| ty.simplify_standard_types(config)); if let Some(ty) = self.simplified_type(config) { *self = ty; } @@ -578,10 +578,33 @@ impl Type { if let Type::Path(ref mut generic_path) = *self { generic_path.replace_self_with(self_ty); } - self.visit_types(|ty| ty.replace_self_with(self_ty)) + self.visit_types_mut(|ty| ty.replace_self_with(self_ty)) } - fn visit_types(&mut self, mut visitor: impl FnMut(&mut Type)) { + fn visit_types(&self, mut visitor: impl FnMut(&Type)) { + match *self { + Type::Array(ref ty, ..) | Type::Ptr { ref ty, .. } => visitor(ty), + Type::Path(ref path) => { + for generic in path.generics() { + match *generic { + GenericArgument::Type(ref ty) => visitor(ty), + GenericArgument::Const(_) => {} + } + } + } + Type::Primitive(..) => {} + Type::FuncPtr { + ref ret, ref args, .. + } => { + visitor(ret); + for arg in args { + visitor(&arg.1) + } + } + } + } + + fn visit_types_mut(&mut self, mut visitor: impl FnMut(&mut Type)) { match *self { Type::Array(ref mut ty, ..) | Type::Ptr { ref mut ty, .. } => visitor(ty), Type::Path(ref mut path) => { @@ -627,6 +650,19 @@ impl Type { } } + /// Visit root paths. + /// Includes self and inner types. + pub fn visit_root_paths(&self, mut visitor: impl FnMut(Path)) { + if let Some(path) = self.get_root_path() { + visitor(path); + } + self.visit_types(|ty| { + if let Some(path) = ty.get_root_path() { + visitor(path); + } + }); + } + pub fn specialize(&self, mappings: &[(&Path, &GenericArgument)]) -> Type { match *self { Type::Ptr { diff --git a/src/bindgen/language_backend/clike.rs b/src/bindgen/language_backend/clike.rs index b41a3c462..2d639e8f2 100644 --- a/src/bindgen/language_backend/clike.rs +++ b/src/bindgen/language_backend/clike.rs @@ -1,14 +1,19 @@ +use multimap::MultiMap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::io::Write; + +use crate::bindgen::dependencies::Dependencies; use crate::bindgen::ir::{ to_known_assoc_constant, ConditionWrite, DeprecatedNoteKind, Documentation, Enum, EnumVariant, - Field, GenericParams, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, ToCondition, Type, - Typedef, Union, + Field, GenericParams, Item, Literal, OpaqueItem, Path, ReprAlign, Static, Struct, ToCondition, + Type, Typedef, Union, }; -use crate::bindgen::language_backend::LanguageBackend; +use crate::bindgen::language_backend::{ItemContainer, LanguageBackend}; use crate::bindgen::rename::IdentifierType; use crate::bindgen::writer::{ListType, SourceWriter}; use crate::bindgen::{cdecl, Bindings, Config, Language}; use crate::bindgen::{DocumentationLength, DocumentationStyle}; -use std::io::Write; pub struct CLikeLanguageBackend<'a> { config: &'a Config, @@ -19,6 +24,28 @@ impl<'a> CLikeLanguageBackend<'a> { Self { config } } + /// Resolve the item order by checking inter dependencies, reordering items and adding forward declarations. + pub fn resolve_order(&self, dependencies: &mut Dependencies) { + let mut resolver = ResolveOrder::new_preserve_order(self.config, dependencies); + if let Err(n) = resolver.resolve() { + warn!("resolve_order: failed with {n} pending items, continuing anyway..."); + } + for (index, &item_index) in resolver.order.iter().enumerate() { + let path = resolver.dependencies.order[item_index].deref().path(); + let state = &resolver.states[item_index]; + trace!("DONE {} <- {} {} is {:?}", index, item_index, path, state); + } + for &item_index in resolver.pending.iter() { + let path = resolver.dependencies.order[item_index].deref().path(); + let state = &resolver.states[item_index]; + warn!("PENDING {} {} is {:?}", item_index, path, state); + } + resolver.apply(); + for (index, item) in dependencies.order.iter().enumerate() { + trace!("APPLY {} {}", index, item.deref().path()); + } + } + fn write_enum_variant(&mut self, out: &mut SourceWriter, u: &EnumVariant) { let condition = u.cfg.to_condition(self.config); @@ -1009,4 +1036,611 @@ impl LanguageBackend for CLikeLanguageBackend<'_> { } } } + + fn write_declaration(&mut self, out: &mut SourceWriter, d: &ItemContainer) { + let some_condition = d.deref().cfg().map(|cfg| cfg.to_condition(self.config)); + if let Some(condition) = &some_condition { + condition.write_before(self.config, out); + } + + match d { + ItemContainer::Struct(ref s) => write!(out, "struct {};", s.export_name), + ItemContainer::Union(ref u) => write!(out, "union {};", u.export_name), + ItemContainer::Enum(ref e) => { + if self.config.language == Language::C { + write!(out, "enum {};", e.export_name); + } else { + unreachable!(); + } + } + _ => unreachable!(), + } + + if let Some(condition) = &some_condition { + condition.write_after(self.config, out); + } + } +} + +/// State of an item. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum ResolveOrderState { + Pending, // cannot be used + Declaration(usize), // declared, can be used in pointers and references + Alias(Path), // declared, defined when path is defined + Defined, // defined (and declared), can be used +} + +/// What the resolver wants. +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub enum ResolveOrderWants { + Declaration(Path), + Definition(Path), +} +impl ResolveOrderWants { + pub fn path(&self) -> &Path { + match self { + Self::Declaration(ref path) => path, + Self::Definition(ref path) => path, + } + } +} + +/// Resolve the order of dependencies. +/// +/// Can try to preserve the original order. +/// Assumes all unknown paths are defined. +/// Multiple items can have the same path. +/// +/// Algorithm: +/// - if no pending items: done +/// - if countdown is over or no item: give up +/// - if no unresolved dependencies: alias or define item, reset countdown or retry pending +/// - if can declare dependencies: declare dependencies, alias or define item, reset countdown or retry pending +/// - next item, decrease countdown +pub struct ResolveOrder<'a> { + /// Configuration. + pub config: &'a Config, + /// Contains items that need to be resolved. + pub dependencies: &'a mut Dependencies, + /// Location of each path. + pub paths2indexes: MultiMap, + /// Contains the state of each item. + pub states: Vec, + /// The pending order of items. + pub pending: Vec, + /// Current pending item being processed. + pub current: usize, + /// When to give up. + pub countdown: Option, + /// The resolved order of items. + pub order: Vec, +} + +impl<'a> ResolveOrder<'a> { + /// Create a new resolver capable of changing the order of the dependencies. + /// + /// Does not try to preserve the original order. + pub fn new(config: &'a Config, dependencies: &'a mut Dependencies) -> Self { + let num_items = dependencies.order.len(); + let mut paths2indexes: MultiMap = MultiMap::new(); + for (item_index, item) in dependencies.order.iter().enumerate() { + let path = item.deref().path().clone(); + paths2indexes.insert(path, item_index); + } + let states = vec![ResolveOrderState::Pending; num_items]; + let pending: Vec = (0..num_items).collect(); + Self { + config, + dependencies, + paths2indexes, + states, + pending, + current: 0, + countdown: Some(num_items), + order: Vec::new(), + } + } + + /// Create a new resolver capable of changing the order of the dependencies. + /// + /// Tries to preserve the original order. + pub fn new_preserve_order(config: &'a Config, dependencies: &'a mut Dependencies) -> Self { + let mut resolver = Self::new(config, dependencies); + resolver.countdown = None; + resolver + } + + /// Apply the computed order to the dependencies, consuming the resolver. + /// + /// Pending dependencies are assume resolved. + pub fn apply(mut self) { + self.order.extend(self.pending); + sort_by_order(&mut self.dependencies.order, self.order); + } + + /// Resolve dependencies by computing a new order of items. + /// + /// Can add item declarations. + /// + /// Returns `Ok(())` when done. + /// Returns `Err(n)` when it gives up with `n` pending items. + pub fn resolve(&mut self) -> Result<(), usize> { + loop { + if self.pending.is_empty() { + trace!("resolve: done"); + return Ok(()); + } + let give_up = if let Some(n) = self.countdown { + if self.current == self.pending.len() { + self.current = 0; + } + n == 0 + } else { + self.current == self.pending.len() + }; + if give_up { + let n = self.pending.len(); + trace!("resolve: give up with {n} pending items"); + return Err(n); + } + trace!( + "resolve: trying index {} of {}...", + self.current, + self.pending.len() + ); + let item_index = self.pending[self.current]; + let unresolved = self.unresolved_of(item_index); + if unresolved.is_empty() { + self.alias_or_define(item_index); + if self.countdown.is_some() { + self.countdown = Some(self.pending.len()); + } else { + self.current = 0; + } + continue; + } + if let Some(declarations) = self.declarations(&unresolved) { + for declaration in declarations.into_iter() { + self.declare(declaration); + } + self.alias_or_define(item_index); + if self.countdown.is_some() { + self.countdown = Some(self.pending.len()); + } else { + self.current = 0; + } + continue; + } + // try next item + trace!( + "resolve: {} depends on {}", + self.dependencies.order[item_index].deref().path(), + self.unresolved_string(&unresolved), + ); + self.current += 1; + if let Some(n) = self.countdown { + self.countdown = Some(n - 1); + } + } + } + + /// Alias an item or define an item. + pub fn alias_or_define(&mut self, item_index: usize) { + let path = self.dependencies.order[item_index].deref().path(); + if let Some(target) = self.alias_of(item_index) { + let is_defined = self + .paths2indexes + .get_vec(&target) + .expect("vec") + .iter() + .all(|&index| self.states[index] == ResolveOrderState::Defined); + if !is_defined { + trace!("alias_or_define: {path} alias to {target}"); + self.states[item_index] = ResolveOrderState::Alias(target); + self.done(item_index); + return; + } + trace!("alias_or_define: {target} is defined"); + } + self.define(item_index); + } + + /// Define an item. + pub fn define(&mut self, item_index: usize) { + let path = self.dependencies.order[item_index].deref().path().clone(); + trace!("define: {path} defined"); + self.states[item_index] = ResolveOrderState::Defined; + self.done(item_index); + + // also define alias to item + let mut define_alias_to = vec![path]; + while let Some(defined) = define_alias_to.pop() { + for (path, indexes) in self.paths2indexes.iter_all() { + for &index in indexes.iter() { + if matches!(self.states[index], ResolveOrderState::Alias(ref target) if target == &defined) + { + // also define alias to alias + define_alias_to.push(path.clone()); + trace!("define: {path} alias to {defined} defined"); + self.states[index] = ResolveOrderState::Defined; + } + } + } + } + } + + /// Add item declaration. + pub fn declare(&mut self, state: ResolveOrderState) { + if let ResolveOrderState::Declaration(target) = state { + let pending_item = self.dependencies.order[target].clone(); + let item = ItemContainer::Declaration(Box::new(pending_item)); + let path = item.deref().path().clone(); + trace!("declare: {path} declared"); + let item_index = self.dependencies.order.len(); + self.dependencies.order.push(item); + self.paths2indexes.insert(path, item_index); + self.states.push(state); + self.order.push(item_index); + assert_eq!( + self.dependencies.order.len(), + self.pending.len() + self.order.len() + ); + } else { + unreachable!(); + } + } + + /// Move item from the pending list to the order list. + pub fn done(&mut self, item_index: usize) { + self.pending.remove( + self.pending + .iter() + .position(|&index| index == item_index) + .expect("pending index"), + ); + self.order.push(item_index); + assert_eq!( + self.dependencies.order.len(), + self.pending.len() + self.order.len() + ); + } + + /// Get the string of unresolved dependencies. + pub fn unresolved_string(&self, unresolved: &HashSet) -> String { + let unresolved: Vec<_> = unresolved.iter().map(|want| want.path().name()).collect(); + unresolved.join(",") + } + + /// Get the unresolved dependencies of an item. + pub fn unresolved_of(&self, item_index: usize) -> HashSet { + // gather wants + struct GatherWants { + what: HashMap, + } + impl GatherWants { + fn new() -> Self { + Self { + what: HashMap::new(), + } + } + fn want_declaration_of_path(&mut self, path: &Path) { + if self.what.contains_key(path) { + // already want declaration or definition + return; + } + trace!("unresolved_of: want declaration of {path}"); + let want = ResolveOrderWants::Declaration(path.clone()); + self.what.insert(path.clone(), want); + } + fn want_definition_of_path(&mut self, path: &Path) { + if matches!(self.what.get(path), Some(ResolveOrderWants::Definition(_))) { + // already want definition + return; + } + let path = path.clone(); + trace!("unresolved_of: want definition of {path}"); + let want = ResolveOrderWants::Definition(path.clone()); + self.what.insert(path, want); + } + fn want_declaration_of_type(&mut self, x: &Type) { + match x { + Type::Ptr { ref ty, .. } => { + ty.visit_root_paths(|path| { + self.want_declaration_of_path(&path); + }); + } + Type::Path(ref generic_path) => { + self.want_declaration_of_path(generic_path.path()); + } + Type::Primitive(_) => {} + Type::Array(ref ty, ..) => { + self.want_declaration_of_type(ty); + } + Type::FuncPtr { + ref ret, ref args, .. + } => { + self.want_declaration_of_type(ret); + for (_, ty) in args { + self.want_declaration_of_type(ty) + } + } + } + } + fn want_definition_of_type(&mut self, x: &Type) { + match x { + Type::Ptr { ref ty, .. } => { + ty.visit_root_paths(|path| { + self.want_declaration_of_path(&path); + }); + } + Type::Path(ref generic_path) => { + self.want_definition_of_path(&generic_path.path().clone()); + } + Type::Primitive(_) => {} + Type::Array(ref ty, ..) => { + self.want_definition_of_type(ty); + } + Type::FuncPtr { + ref ret, ref args, .. + } => { + self.want_declaration_of_type(ret); + for (_, ty) in args { + self.want_declaration_of_type(ty) + } + } + } + } + } + let mut wants = GatherWants::new(); + match &self.dependencies.order[item_index] { + ItemContainer::Typedef(ref x) => { + wants.want_declaration_of_type(&x.aliased); + } + ItemContainer::Struct(ref x) => { + for field in x.fields.iter() { + wants.want_definition_of_type(&field.ty); + } + } + ItemContainer::Union(ref x) => { + for field in x.fields.iter() { + wants.want_definition_of_type(&field.ty); + } + } + _ => {} + } + + // resolve wants + let item_path = self.dependencies.order[item_index].deref().path(); + let mut unresolved: HashSet = HashSet::new(); + for (_, want) in wants.what.into_iter() { + match want { + ResolveOrderWants::Declaration(ref dependency) => { + if !self.is_declared(dependency) { + trace!("unresolved_of: {dependency} not declared"); + unresolved.insert(want); + } + } + ResolveOrderWants::Definition(ref dependency) => { + if !self.is_defined(dependency) { + trace!("unresolved_of: {dependency} not defined"); + unresolved.insert(want); + } + } + } + } + unresolved.retain(|want| { + if let ResolveOrderWants::Declaration(ref dependency) = want { + if dependency == item_path { + trace!("unresolved_of: {dependency} assume self declared"); + return false; + } + } + true + }); + unresolved + } + + /// Try to get declarations of all unresolved dependencies. + pub fn declarations( + &self, + unresolved: &HashSet, + ) -> Option> { + let mut declarations = Vec::new(); + for want in unresolved.iter() { + match want { + ResolveOrderWants::Declaration(ref path) => { + for &item_index in self.paths2indexes.get_vec(path).expect("vec").iter() { + if let Some(declaration) = self.declaration_of(item_index) { + declarations.push(declaration); + continue; + } + trace!("declarations: {path} cannot be declared"); + return None; + } + } + ResolveOrderWants::Definition(ref path) => { + trace!("declarations: {path} cannot be defined"); + return None; + } + } + } + Some(declarations) + } + + /// Try to get the declaration of an item. + pub fn declaration_of(&self, item_index: usize) -> Option { + let item = &self.dependencies.order[item_index]; + let path = item.deref().path(); + match item { + ItemContainer::Struct(_) | ItemContainer::Union(_) => { + if self.will_be_named(item_index) { + trace!("declaration_of: {path} can be declared"); + return Some(ResolveOrderState::Declaration(item_index)); + } + } + ItemContainer::Enum(_) => { + if self.will_be_named(item_index) && self.config.language == Language::C { + // C allows, Cxx errors + trace!("declaration_of: {path} can be declared"); + return Some(ResolveOrderState::Declaration(item_index)); + } + } + _ => {} + } + trace!("declaration_of: {path} cannot be declared"); + None + } + + /// Try to get the target of an alias item. + pub fn alias_of(&self, item_index: usize) -> Option { + let item = &self.dependencies.order[item_index]; + if let ItemContainer::Typedef(ref x) = item { + if let Type::Path(ref generic_path) = x.aliased { + let path = item.deref().path(); + let target = generic_path.path(); + trace!("alias_of: {path} is alias of {target}"); + return Some(target.clone()); + } + } + None + } + + /// Check if a path is declared. + pub fn is_declared(&self, path: &Path) -> bool { + if let Some(indexes) = self.paths2indexes.get_vec(path) { + let is_declared = indexes.iter().all(|&index| { + match self.states[index] { + ResolveOrderState::Pending => { + indexes.iter().any(|&declaration_index| { + matches!(self.states[declaration_index], ResolveOrderState::Declaration(target) if target == index) + }) + } + _ => true, + } + }); + if is_declared { + trace!("is_declared: {path} is declared"); + } else { + trace!("is_declared: {path} is not declared"); + } + is_declared + } else { + trace!("is_declared: {path} is assume declared"); + true + } + } + + /// Check if a path is defined. + pub fn is_defined(&self, path: &Path) -> bool { + if let Some(indexes) = self.paths2indexes.get_vec(path) { + let mut is_defined = false; + for &index in indexes.iter() { + match self.states[index] { + ResolveOrderState::Pending => { + is_defined = false; + break; + } + ResolveOrderState::Declaration(_) => {} + ResolveOrderState::Defined => is_defined = true, + ResolveOrderState::Alias(ref target) => { + // assume path is defined if it is an alias to path + is_defined = target == path; + if !is_defined { + break; + } + } + } + } + if is_defined { + trace!("is_defined: {path} is defined"); + } else { + trace!("is_defined: {path} is not defined"); + } + is_defined + } else { + trace!("is_defined: {path} is assume defined"); + true + } + } + + /// Check if the language backend will write names. + pub fn will_be_named(&self, item_index: usize) -> bool { + let item = &self.dependencies.order[item_index]; + match item { + ItemContainer::Struct(_) | ItemContainer::Union(_) => { + // see docs for codegen option `style="type"` + self.config.language != Language::C || self.config.style.generate_tag() + } + _ => true, + } + } +} + +/// Put the data values in the target order. +/// +/// Panics if the data and the order have different sizes. +pub fn sort_by_order(data: &mut [T], mut order: Vec) { + assert_eq!(data.len(), order.len()); + for mut to in 0..order.len() { + let mut from = order[to]; + if from != to { + // swap the whole chain of data into place + order[to] = to; + while order[from] != from { + data.swap(from, to); + to = from; + from = order[to]; + order[to] = to; + } + } + } +} + +#[test] +fn test_sort_by_order() { + macro_rules! test { + ($a:literal, $b:literal, $c:literal, $d:literal) => { + let order = [$a, $b, $c, $d]; + let expect = [ + stringify!($a), + stringify!($b), + stringify!($c), + stringify!($d), + ]; + let mut data = ["0", "1", "2", "3"]; + sort_by_order(&mut data, order.to_vec()); + assert!( + expect == data, + "order {:?} produced {:?}, but {:?} was expected", + order, + data, + expect + ); + }; + } + test!(0, 1, 2, 3); + test!(0, 1, 3, 2); + test!(0, 2, 1, 3); + test!(0, 2, 3, 1); + test!(0, 3, 1, 2); + test!(0, 3, 2, 1); + test!(1, 0, 2, 3); + test!(1, 0, 3, 2); + test!(1, 2, 0, 3); + test!(1, 2, 3, 0); + test!(1, 3, 0, 2); + test!(1, 3, 2, 0); + test!(2, 0, 1, 3); + test!(2, 0, 3, 1); + test!(2, 1, 0, 3); + test!(2, 1, 3, 0); + test!(2, 3, 0, 1); + test!(2, 3, 1, 0); + test!(3, 0, 1, 2); + test!(3, 0, 2, 1); + test!(3, 1, 0, 2); + test!(3, 1, 2, 0); + test!(3, 2, 0, 1); + test!(3, 2, 1, 0); } diff --git a/src/bindgen/language_backend/cython.rs b/src/bindgen/language_backend/cython.rs index 8497f3cd0..2176651ee 100644 --- a/src/bindgen/language_backend/cython.rs +++ b/src/bindgen/language_backend/cython.rs @@ -2,7 +2,7 @@ use crate::bindgen::ir::{ to_known_assoc_constant, ConditionWrite, DeprecatedNoteKind, Documentation, Enum, EnumVariant, Field, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, ToCondition, Type, Typedef, Union, }; -use crate::bindgen::language_backend::LanguageBackend; +use crate::bindgen::language_backend::{ItemContainer, LanguageBackend}; use crate::bindgen::writer::{ListType, SourceWriter}; use crate::bindgen::DocumentationLength; use crate::bindgen::{cdecl, Bindings, Config}; @@ -418,4 +418,8 @@ impl LanguageBackend for CythonLanguageBackend<'_> { out.write("pass"); } } + + fn write_declaration(&mut self, _out: &mut SourceWriter, _item: &ItemContainer) { + unreachable!(); + } } diff --git a/src/bindgen/language_backend/mod.rs b/src/bindgen/language_backend/mod.rs index 065bade20..969146958 100644 --- a/src/bindgen/language_backend/mod.rs +++ b/src/bindgen/language_backend/mod.rs @@ -25,6 +25,7 @@ pub trait LanguageBackend: Sized { fn write_opaque_item(&mut self, out: &mut SourceWriter, o: &OpaqueItem); fn write_type_def(&mut self, out: &mut SourceWriter, t: &Typedef); fn write_static(&mut self, out: &mut SourceWriter, s: &Static); + fn write_declaration(&mut self, out: &mut SourceWriter, item: &ItemContainer); fn write_function( &mut self, @@ -177,6 +178,7 @@ pub trait LanguageBackend: Sized { ItemContainer::Union(ref x) => self.write_union(out, x), ItemContainer::OpaqueItem(ref x) => self.write_opaque_item(out, x), ItemContainer::Typedef(ref x) => self.write_type_def(out, x), + ItemContainer::Declaration(ref x) => self.write_declaration(out, x), } out.new_line(); } diff --git a/src/bindgen/library.rs b/src/bindgen/library.rs index 9d61257f9..e9401a9b0 100644 --- a/src/bindgen/library.rs +++ b/src/bindgen/library.rs @@ -12,6 +12,7 @@ use crate::bindgen::dependencies::Dependencies; use crate::bindgen::error::Error; use crate::bindgen::ir::{Constant, Enum, Function, Item, ItemContainer, ItemMap}; use crate::bindgen::ir::{OpaqueItem, Path, Static, Struct, Typedef, Union}; +use crate::bindgen::language_backend::CLikeLanguageBackend; use crate::bindgen::monomorph::Monomorphs; use crate::bindgen::ItemType; @@ -105,6 +106,12 @@ impl Library { } dependencies.sort(); + match self.config.language { + Language::C | Language::Cxx => { + CLikeLanguageBackend::new(&self.config).resolve_order(&mut dependencies); + } + Language::Cython => {} + } let items = dependencies.order; let constants = if self.config.export.should_generate(ItemType::Constants) { diff --git a/tests/clike_language_backend.rs b/tests/clike_language_backend.rs new file mode 100644 index 000000000..fa0d8f175 --- /dev/null +++ b/tests/clike_language_backend.rs @@ -0,0 +1,197 @@ +//! This file tests assumptions that the clike language backend can make. +//! +//! FIXME ideally compile tests with `#[should_panic]` should give a warning result instead of a failure result + +use cbindgen::Language; +use std::env; +use std::fs; +use std::process::Command; + +fn compile(language: Language, code: &str) { + // respect the desire to not compile (from tests.rs) + // FIXME ideally it should skip the test + let no_compile = env::var_os("CBINDGEN_TEST_NO_COMPILE").is_some(); + if no_compile { + return; + } + + let (compiler, filename) = match language { + Language::C => ( + env::var("CC").unwrap_or_else(|_| "gcc".to_owned()), + "test.c", + ), + Language::Cxx => ( + env::var("CXX").unwrap_or_else(|_| "g++".to_owned()), + "test.cpp", + ), + _ => unreachable!(), + }; + + let tmp_dir = tempfile::Builder::new() + .prefix("cbindgen-test-clike") + .tempdir() + .expect("Creating tmp dir failed"); + let tmp_dir = tmp_dir.path(); + + let mut command = Command::new(compiler); + command.arg("-Wall"); + command.arg("-Werror"); + command.arg("-c"); + command.arg(filename); + command.current_dir(tmp_dir); + + fs::write(tmp_dir.join(filename), code).expect("Failed to write test code."); + + println!("Running: {:?}", command); + let out = command.output().expect("failed to compile"); + + // propagate command output to the test output for inspection + println!("{}", String::from_utf8_lossy(&out.stdout)); + eprintln!("{}", String::from_utf8_lossy(&out.stderr)); + + assert!(out.status.success(), "Output failed to compile: {:#?}", out); +} + +/// TEST: the forward declaration "struct s;" is not needed? +mod auto_declared_struct { + // ok in C, ok in Cxx + const CODE: &'static str = r#" + typedef struct s s_t; + struct s { + int f; + }; + "#; + + use super::*; + + #[test] + fn test_c() { + compile(Language::C, CODE); + } + + #[test] + fn test_cxx() { + compile(Language::Cxx, CODE); + } +} + +/// TEST: the forward declaration "union u;" is not needed? +mod auto_declared_union { + // ok in C, ok in Cxx + const CODE: &'static str = r#" + typedef union u u_t; + union u { + int f; + }; + "#; + + use super::*; + + #[test] + fn test_c() { + compile(Language::C, CODE); + } + + #[test] + fn test_cxx() { + compile(Language::Cxx, CODE); + } +} + +/// TEST: the forward declaration "enum e;" is not needed? +mod auto_declared_enum { + // ok in C, error in Cxx + const CODE: &'static str = r#" + typedef enum e e_t; + enum e { + V + }; + "#; + + use super::*; + + #[test] + fn test_c() { + compile(Language::C, CODE); + } + + #[test] + #[ignore] + #[should_panic] + fn test_cxx() { + compile(Language::Cxx, CODE); + } +} + +/// TEST: "struct" does not need to be used? +mod struct_not_needed { + // error in C, ok in Cxx + const CODE: &'static str = r#" + struct s { + s* f; + }; + "#; + + use super::*; + + #[test] + #[ignore] + #[should_panic] + fn test_c() { + compile(Language::C, CODE); + } + + #[test] + fn test_cxx() { + compile(Language::Cxx, CODE); + } +} + +/// TEST: "union" does not need to be used? +mod union_not_needed { + // error in C, ok in Cxx + const CODE: &'static str = r#" + union u { + u* f; + }; + "#; + + use super::*; + + #[test] + #[ignore] + #[should_panic] + fn test_c() { + compile(Language::C, CODE); + } + + #[test] + fn test_cxx() { + compile(Language::Cxx, CODE); + } +} + +/// TEST: avoid warning `‘struct s’ declared inside parameter list will not be visible outside of this definition or declaration` +mod func_arg_warning { + // warning in C, ok in Cxx + const CODE: &'static str = r#" + typedef int (*f_t)(struct s*); + struct s { + int f; + }; + "#; + + use super::*; + + #[test] + #[ignore] + #[should_panic] + fn test_c() { + compile(Language::C, CODE); + } + + #[test] + fn test_cxx() { + compile(Language::Cxx, CODE); + } +} diff --git a/tests/expectations/forward_declaration.c b/tests/expectations/forward_declaration.c index f77076dd9..4057cfb1d 100644 --- a/tests/expectations/forward_declaration.c +++ b/tests/expectations/forward_declaration.c @@ -14,11 +14,6 @@ #include #include -typedef struct { - const TypeInfo *const *fields; - uintptr_t num_fields; -} StructInfo; - typedef enum { Primitive, Struct, @@ -37,6 +32,11 @@ typedef struct { TypeData data; } TypeInfo; +typedef struct { + const TypeInfo *const *fields; + uintptr_t num_fields; +} StructInfo; + void root(TypeInfo x); #if 0 diff --git a/tests/expectations/forward_declaration.compat.c b/tests/expectations/forward_declaration.compat.c index 375fc8dce..41c68ead7 100644 --- a/tests/expectations/forward_declaration.compat.c +++ b/tests/expectations/forward_declaration.compat.c @@ -14,11 +14,6 @@ #include #include -typedef struct { - const TypeInfo *const *fields; - uintptr_t num_fields; -} StructInfo; - typedef enum { Primitive, Struct, @@ -37,6 +32,11 @@ typedef struct { TypeData data; } TypeInfo; +typedef struct { + const TypeInfo *const *fields; + uintptr_t num_fields; +} StructInfo; + #ifdef __cplusplus extern "C" { #endif // __cplusplus diff --git a/tests/expectations/forward_declaration.cpp b/tests/expectations/forward_declaration.cpp index 766b79946..5284b12eb 100644 --- a/tests/expectations/forward_declaration.cpp +++ b/tests/expectations/forward_declaration.cpp @@ -15,6 +15,8 @@ #include #include +struct TypeInfo; + struct StructInfo { const TypeInfo *const *fields; uintptr_t num_fields; diff --git a/tests/expectations/forward_declaration_both.c b/tests/expectations/forward_declaration_both.c index 9e88bab21..7c784ee0e 100644 --- a/tests/expectations/forward_declaration_both.c +++ b/tests/expectations/forward_declaration_both.c @@ -14,6 +14,8 @@ #include #include +struct TypeInfo; + typedef struct StructInfo { const struct TypeInfo *const *fields; uintptr_t num_fields; diff --git a/tests/expectations/forward_declaration_both.compat.c b/tests/expectations/forward_declaration_both.compat.c index 031e40c0a..8467c34ba 100644 --- a/tests/expectations/forward_declaration_both.compat.c +++ b/tests/expectations/forward_declaration_both.compat.c @@ -14,6 +14,8 @@ #include #include +struct TypeInfo; + typedef struct StructInfo { const struct TypeInfo *const *fields; uintptr_t num_fields; diff --git a/tests/expectations/forward_declaration_tag.c b/tests/expectations/forward_declaration_tag.c index acd820428..4dcab23b7 100644 --- a/tests/expectations/forward_declaration_tag.c +++ b/tests/expectations/forward_declaration_tag.c @@ -14,6 +14,8 @@ #include #include +struct TypeInfo; + struct StructInfo { const struct TypeInfo *const *fields; uintptr_t num_fields; diff --git a/tests/expectations/forward_declaration_tag.compat.c b/tests/expectations/forward_declaration_tag.compat.c index 1a61c5e2d..46ad37a4c 100644 --- a/tests/expectations/forward_declaration_tag.compat.c +++ b/tests/expectations/forward_declaration_tag.compat.c @@ -14,6 +14,8 @@ #include #include +struct TypeInfo; + struct StructInfo { const struct TypeInfo *const *fields; uintptr_t num_fields;