diff options
author | Emilio Cobos Álvarez <emilio@crisal.io> | 2017-01-07 02:30:51 +0100 |
---|---|---|
committer | Emilio Cobos Álvarez <emilio@crisal.io> | 2017-01-11 14:34:24 +0100 |
commit | 8c54a566457a1c4aacabf72977380c25c7fa10a1 (patch) | |
tree | abd6b1a5349937bdafebf35c2cf90f54c8fae7c6 | |
parent | 66447ff277181073d14bb04c7947eec805cd0623 (diff) |
ir: Handle inline namespaces.
Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
-rw-r--r-- | bindgen/src/options.rs | 8 | ||||
-rw-r--r-- | libbindgen/src/codegen/mod.rs | 3 | ||||
-rw-r--r-- | libbindgen/src/ir/context.rs | 67 | ||||
-rw-r--r-- | libbindgen/src/ir/item.rs | 17 | ||||
-rw-r--r-- | libbindgen/src/ir/module.rs | 19 | ||||
-rw-r--r-- | libbindgen/src/lib.rs | 39 | ||||
-rw-r--r-- | libbindgen/tests/expectations/tests/inline_namespace.rs | 28 | ||||
-rw-r--r-- | libbindgen/tests/expectations/tests/inline_namespace_conservative.rs | 33 | ||||
-rw-r--r-- | libbindgen/tests/expectations/tests/inline_namespace_whitelist.rs | 15 | ||||
-rw-r--r-- | libbindgen/tests/headers/inline_namespace.hpp | 11 | ||||
-rw-r--r-- | libbindgen/tests/headers/inline_namespace_conservative.hpp | 12 | ||||
-rw-r--r-- | libbindgen/tests/headers/inline_namespace_whitelist.hpp | 7 |
12 files changed, 239 insertions, 20 deletions
diff --git a/bindgen/src/options.rs b/bindgen/src/options.rs index 41a8a905..e6226350 100644 --- a/bindgen/src/options.rs +++ b/bindgen/src/options.rs @@ -121,6 +121,10 @@ pub fn builder_from_flags<I>(args: I) Arg::with_name("use-core") .long("use-core") .help("Use types from Rust core instead of std."), + Arg::with_name("conservative-inline-namespaces") + .long("conservative-inline-namespaces") + .help("Conservatively generate inline namespaces to avoid name \ + conflicts."), Arg::with_name("use-msvc-mangling") .long("use-msvc-mangling") .help("MSVC C++ ABI mangling. DEPRECATED: Has no effect."), @@ -268,6 +272,10 @@ pub fn builder_from_flags<I>(args: I) builder = builder.use_core(); } + if matches.is_present("conservative-inline-namespaces") { + builder = builder.conservative_inline_namespaces(); + } + if let Some(whitelist) = matches.values_of("whitelist-function") { for regex in whitelist { builder = builder.whitelisted_function(regex); diff --git a/libbindgen/src/codegen/mod.rs b/libbindgen/src/codegen/mod.rs index d12c3d2a..f8352cc8 100644 --- a/libbindgen/src/codegen/mod.rs +++ b/libbindgen/src/codegen/mod.rs @@ -329,7 +329,8 @@ impl CodeGenerator for Module { } }; - if !ctx.options().enable_cxx_namespaces { + if !ctx.options().enable_cxx_namespaces || + (self.is_inline() && !ctx.options().conservative_inline_namespaces) { codegen_self(result, &mut false); return; } diff --git a/libbindgen/src/ir/context.rs b/libbindgen/src/ir/context.rs index 2d877920..c3de2ca8 100644 --- a/libbindgen/src/ir/context.rs +++ b/libbindgen/src/ir/context.rs @@ -14,7 +14,7 @@ use super::derive::{CanDeriveCopy, CanDeriveDebug}; use super::int::IntKind; use super::item::{Item, ItemCanonicalPath}; use super::item_kind::ItemKind; -use super::module::Module; +use super::module::{Module, ModuleKind}; use super::ty::{FloatKind, Type, TypeKind}; use super::type_collector::{ItemSet, TypeCollector}; use syntax::ast::Ident; @@ -545,7 +545,7 @@ impl<'ctx> BindgenContext<'ctx> { } fn build_root_module(id: ItemId) -> Item { - let module = Module::new(Some("root".into())); + let module = Module::new(Some("root".into()), ModuleKind::Normal); Item::new(id, None, None, id, ItemKind::Module(module)) } @@ -955,31 +955,64 @@ impl<'ctx> BindgenContext<'ctx> { &self.options } + /// Tokenizes a namespace cursor in order to get the name and kind of the + /// namespace, + fn tokenize_namespace(&self, + cursor: &clang::Cursor) + -> (Option<String>, ModuleKind) { + assert_eq!(cursor.kind(), ::clang_sys::CXCursor_Namespace, + "Be a nice person"); + let tokens = match self.translation_unit.tokens(&cursor) { + Some(tokens) => tokens, + None => return (None, ModuleKind::Normal), + }; + + let mut iter = tokens.iter(); + let mut kind = ModuleKind::Normal; + let mut found_namespace_keyword = false; + let mut module_name = None; + while let Some(token) = iter.next() { + match &*token.spelling { + "inline" => { + assert!(!found_namespace_keyword); + assert!(kind != ModuleKind::Inline); + kind = ModuleKind::Inline; + } + "namespace" => { + found_namespace_keyword = true; + } + "{" => { + assert!(found_namespace_keyword); + break; + } + name if found_namespace_keyword => { + module_name = Some(name.to_owned()); + break; + } + _ => { + panic!("Unknown token while processing namespace: {:?}", + token); + } + } + }; + + (module_name, kind) + } + /// Given a CXCursor_Namespace cursor, return the item id of the /// corresponding module, or create one on the fly. pub fn module(&mut self, cursor: clang::Cursor) -> ItemId { use clang_sys::*; - assert!(cursor.kind() == CXCursor_Namespace, "Be a nice person"); + assert_eq!(cursor.kind(), CXCursor_Namespace, "Be a nice person"); let cursor = cursor.canonical(); if let Some(id) = self.modules.get(&cursor) { return *id; } - let module_id = self.next_item_id(); - let module_name = self.translation_unit - .tokens(&cursor) - .and_then(|tokens| { - if tokens.len() <= 1 { - None - } else { - match &*tokens[1].spelling { - "{" => None, - s => Some(s.to_owned()), - } - } - }); + let (module_name, kind) = self.tokenize_namespace(&cursor); - let module = Module::new(module_name); + let module_id = self.next_item_id(); + let module = Module::new(module_name, kind); let module = Item::new(module_id, None, None, diff --git a/libbindgen/src/ir/item.rs b/libbindgen/src/ir/item.rs index e9960166..3810bc2f 100644 --- a/libbindgen/src/ir/item.rs +++ b/libbindgen/src/ir/item.rs @@ -853,6 +853,15 @@ impl Item { format!("id_{}", self.id().as_usize()) } + /// Get a reference to this item's `Module`, or `None` if this is not a + /// `Module` item. + pub fn as_module(&self) -> Option<&Module> { + match self.kind { + ItemKind::Module(ref module) => Some(module), + _ => None, + } + } + /// Get a mutable reference to this item's `Module`, or `None` if this is /// not a `Module` item. pub fn as_module_mut(&mut self) -> Option<&mut Module> { @@ -1304,7 +1313,13 @@ impl ItemCanonicalPath for Item { let mut path: Vec<_> = target.ancestors(ctx) .chain(iter::once(ctx.root_module())) .map(|id| ctx.resolve_item(id)) - .filter(|item| item.is_module() || item.id() == target.id()) + .filter(|item| { + item.id() == target.id() || + item.as_module().map_or(false, |module| { + !module.is_inline() || + ctx.options().conservative_inline_namespaces + }) + }) .map(|item| { ctx.resolve_item(item.name_target(ctx)) .name(ctx) diff --git a/libbindgen/src/ir/module.rs b/libbindgen/src/ir/module.rs index 5fae3932..002fe36e 100644 --- a/libbindgen/src/ir/module.rs +++ b/libbindgen/src/ir/module.rs @@ -5,20 +5,32 @@ use parse::{ClangSubItemParser, ParseError, ParseResult}; use parse_one; use super::context::{BindgenContext, ItemId}; +/// Whether this module is inline or not. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ModuleKind { + /// This module is not inline. + Normal, + /// This module is inline, as in `inline namespace foo {}`. + Inline, +} + /// A module, as in, a C++ namespace. #[derive(Clone, Debug)] pub struct Module { /// The name of the module, or none if it's anonymous. name: Option<String>, + /// The kind of module this is. + kind: ModuleKind, /// The children of this module, just here for convenience. children_ids: Vec<ItemId>, } impl Module { /// Construct a new `Module`. - pub fn new(name: Option<String>) -> Self { + pub fn new(name: Option<String>, kind: ModuleKind) -> Self { Module { name: name, + kind: kind, children_ids: vec![], } } @@ -37,6 +49,11 @@ impl Module { pub fn children(&self) -> &[ItemId] { &self.children_ids } + + /// Whether this namespace is inline. + pub fn is_inline(&self) -> bool { + self.kind == ModuleKind::Inline + } } impl ClangSubItemParser for Module { diff --git a/libbindgen/src/lib.rs b/libbindgen/src/lib.rs index 1834b3f2..60b750de 100644 --- a/libbindgen/src/lib.rs +++ b/libbindgen/src/lib.rs @@ -305,6 +305,39 @@ impl Builder { self } + /// Treat inline namespaces conservatively. + /// + /// This is tricky, because in C++ is technically legal to override an item + /// defined in an inline namespace: + /// + /// ```cpp + /// inline namespace foo { + /// using Bar = int; + /// } + /// using Bar = long; + /// ``` + /// + /// Even though referencing `Bar` is a compiler error. + /// + /// We want to support this (arguably esoteric) use case, but we don't want + /// to make the rest of bindgen users pay an usability penalty for that. + /// + /// To support this, we need to keep all the inline namespaces around, but + /// then bindgen usage is a bit more difficult, because you cannot + /// reference, e.g., `std::string` (you'd need to use the proper inline + /// namespace). + /// + /// We could complicate a lot of the logic to detect name collisions, and if + /// not detected generate a `pub use inline_ns::*` or something like that. + /// + /// That's probably something we can do if we see this option is needed in a + /// lot of cases, to improve it's usability, but my guess is that this is + /// not going to be too useful. + pub fn conservative_inline_namespaces(mut self) -> Builder { + self.options.conservative_inline_namespaces = true; + self + } + /// Ignore functions. pub fn ignore_functions(mut self) -> Builder { self.options.codegen_config.functions = false; @@ -448,6 +481,11 @@ pub struct BindgenOptions { /// Which kind of items should we generate? By default, we'll generate all /// of them. pub codegen_config: CodegenConfig, + + /// Whether to treat inline namespaces conservatively. + /// + /// See the builder method description for more details. + pub conservative_inline_namespaces: bool, } impl BindgenOptions { @@ -489,6 +527,7 @@ impl Default for BindgenOptions { dummy_uses: None, type_chooser: None, codegen_config: CodegenConfig::all(), + conservative_inline_namespaces: false, } } } diff --git a/libbindgen/tests/expectations/tests/inline_namespace.rs b/libbindgen/tests/expectations/tests/inline_namespace.rs new file mode 100644 index 00000000..5f6776b7 --- /dev/null +++ b/libbindgen/tests/expectations/tests/inline_namespace.rs @@ -0,0 +1,28 @@ +/* automatically generated by rust-bindgen */ + + +#![allow(non_snake_case)] + + +pub mod root { + #[allow(unused_imports)] + use self::super::root; + pub mod foo { + #[allow(unused_imports)] + use self::super::super::root; + pub type Ty = ::std::os::raw::c_int; + } + #[repr(C)] + #[derive(Debug, Copy)] + pub struct Bar { + pub baz: root::foo::Ty, + } + #[test] + fn bindgen_test_layout_Bar() { + assert_eq!(::std::mem::size_of::<Bar>() , 4usize); + assert_eq!(::std::mem::align_of::<Bar>() , 4usize); + } + impl Clone for Bar { + fn clone(&self) -> Self { *self } + } +} diff --git a/libbindgen/tests/expectations/tests/inline_namespace_conservative.rs b/libbindgen/tests/expectations/tests/inline_namespace_conservative.rs new file mode 100644 index 00000000..d759a882 --- /dev/null +++ b/libbindgen/tests/expectations/tests/inline_namespace_conservative.rs @@ -0,0 +1,33 @@ +/* automatically generated by rust-bindgen */ + + +#![allow(non_snake_case)] + + +pub mod root { + #[allow(unused_imports)] + use self::super::root; + pub mod foo { + #[allow(unused_imports)] + use self::super::super::root; + pub mod bar { + #[allow(unused_imports)] + use self::super::super::super::root; + pub type Ty = ::std::os::raw::c_int; + } + pub type Ty = ::std::os::raw::c_longlong; + } + #[repr(C)] + #[derive(Debug, Copy)] + pub struct Bar { + pub baz: root::foo::bar::Ty, + } + #[test] + fn bindgen_test_layout_Bar() { + assert_eq!(::std::mem::size_of::<Bar>() , 4usize); + assert_eq!(::std::mem::align_of::<Bar>() , 4usize); + } + impl Clone for Bar { + fn clone(&self) -> Self { *self } + } +} diff --git a/libbindgen/tests/expectations/tests/inline_namespace_whitelist.rs b/libbindgen/tests/expectations/tests/inline_namespace_whitelist.rs new file mode 100644 index 00000000..9b8f87a5 --- /dev/null +++ b/libbindgen/tests/expectations/tests/inline_namespace_whitelist.rs @@ -0,0 +1,15 @@ +/* automatically generated by rust-bindgen */ + + +#![allow(non_snake_case)] + + +pub mod root { + #[allow(unused_imports)] + use self::super::root; + pub mod std { + #[allow(unused_imports)] + use self::super::super::root; + pub type string = *const ::std::os::raw::c_char; + } +} diff --git a/libbindgen/tests/headers/inline_namespace.hpp b/libbindgen/tests/headers/inline_namespace.hpp new file mode 100644 index 00000000..2ccf8ab0 --- /dev/null +++ b/libbindgen/tests/headers/inline_namespace.hpp @@ -0,0 +1,11 @@ +// bindgen-flags: --enable-cxx-namespaces -- -std=c++11 + +namespace foo { + inline namespace bar { + using Ty = int; + }; +}; + +class Bar { + foo::Ty baz; +}; diff --git a/libbindgen/tests/headers/inline_namespace_conservative.hpp b/libbindgen/tests/headers/inline_namespace_conservative.hpp new file mode 100644 index 00000000..50068a2e --- /dev/null +++ b/libbindgen/tests/headers/inline_namespace_conservative.hpp @@ -0,0 +1,12 @@ +// bindgen-flags: --enable-cxx-namespaces --conservative-inline-namespaces -- -std=c++11 + +namespace foo { + inline namespace bar { + using Ty = int; + }; + using Ty = long long; +}; + +class Bar { + foo::bar::Ty baz; +}; diff --git a/libbindgen/tests/headers/inline_namespace_whitelist.hpp b/libbindgen/tests/headers/inline_namespace_whitelist.hpp new file mode 100644 index 00000000..30047bbe --- /dev/null +++ b/libbindgen/tests/headers/inline_namespace_whitelist.hpp @@ -0,0 +1,7 @@ +// bindgen-flags: --enable-cxx-namespaces --whitelist-type=std::string -- -std=c++11 + +namespace std { + inline namespace bar { + using string = const char*; + }; +}; |