From 22ed2104c40e6a632495bd070d0a8d2a8670613e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:22:10 +0000 Subject: [PATCH 1/7] Initial plan From 5f99b24471c337bafa1ec56a080f734b3b567558 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:38:17 +0000 Subject: [PATCH 2/7] Implement per-item namespace attribute support for C++ output Add support for `#[cbindgen::namespace = "..."]` attribute on functions to specify C++ namespaces. Features: - Parse attribute using `::` separator (e.g., "ffi::bar") - Group functions by namespace in C++ output - Emit nested namespace blocks inside extern "C" - Ignore attribute for C and Cython output - Add tests and documentation Co-authored-by: tarnishablec <42984497+tarnishablec@users.noreply.github.com> --- docs.md | 34 ++++++++ src/bindgen/ir/function.rs | 9 +- src/bindgen/language_backend/clike.rs | 82 ++++++++++++++++++- src/bindgen/utilities.rs | 38 +++++++++ .../expectations-symbols/namespace_attr.c.sym | 7 ++ .../namespace_attr_precedence.c.sym | 5 ++ tests/expectations/namespace_attr.c | 29 +++++++ tests/expectations/namespace_attr.compat.c | 37 +++++++++ tests/expectations/namespace_attr.cpp | 43 ++++++++++ tests/expectations/namespace_attr.pyx | 22 +++++ .../expectations/namespace_attr_precedence.c | 19 +++++ .../namespace_attr_precedence.compat.c | 35 ++++++++ .../namespace_attr_precedence.cpp | 29 +++++++ .../namespace_attr_precedence.pyx | 16 ++++ tests/rust/namespace_attr.rs | 25 ++++++ tests/rust/namespace_attr.toml | 1 + tests/rust/namespace_attr_precedence.rs | 14 ++++ tests/rust/namespace_attr_precedence.toml | 2 + 18 files changed, 443 insertions(+), 4 deletions(-) create mode 100644 tests/expectations-symbols/namespace_attr.c.sym create mode 100644 tests/expectations-symbols/namespace_attr_precedence.c.sym create mode 100644 tests/expectations/namespace_attr.c create mode 100644 tests/expectations/namespace_attr.compat.c create mode 100644 tests/expectations/namespace_attr.cpp create mode 100644 tests/expectations/namespace_attr.pyx create mode 100644 tests/expectations/namespace_attr_precedence.c create mode 100644 tests/expectations/namespace_attr_precedence.compat.c create mode 100644 tests/expectations/namespace_attr_precedence.cpp create mode 100644 tests/expectations/namespace_attr_precedence.pyx create mode 100644 tests/rust/namespace_attr.rs create mode 100644 tests/rust/namespace_attr.toml create mode 100644 tests/rust/namespace_attr_precedence.rs create mode 100644 tests/rust/namespace_attr_precedence.toml diff --git a/docs.md b/docs.md index aa071ac62..f1c7f924c 100644 --- a/docs.md +++ b/docs.md @@ -409,6 +409,40 @@ arg: *const T --> const T arg[] arg: *mut T --> T arg[] ``` +### Per-Item Namespace Attribute (C++ only) + +You can use the `#[cbindgen::namespace = "..."]` attribute to specify a C++ namespace for individual functions. This is useful when you want different functions to be placed in different namespaces. + +```rust +#[cbindgen::namespace = "ffi::bar"] +#[no_mangle] +pub extern "C" fn foo(a: *const c_char) {} +``` + +This will generate the following C++ code: + +```cpp +extern "C" { + +namespace ffi { +namespace bar { + +void foo(const char *a); + +} // namespace bar +} // namespace ffi + +} // extern "C" +``` + +**Key points:** + +* Use `::` as the namespace separator (e.g., `"ffi::bar"` becomes nested namespaces `namespace ffi { namespace bar { ... } }`) +* This attribute only affects C++ output; it is ignored for C and Cython output +* Functions with the same namespace path will be grouped together in the generated header +* If both a global `namespace` in `cbindgen.toml` and a per-item `#[cbindgen::namespace]` are specified, the per-item namespace is nested inside the global namespace +* Functions without this attribute will use the global namespace (if configured) or no namespace + ## Generating Swift Bindings In addition to parsing function names in C/C++ header files, the Swift compiler can make use of the `swift_name` attribute on functions to generate more idiomatic names for imported functions and methods. diff --git a/src/bindgen/ir/function.rs b/src/bindgen/ir/function.rs index f8e29c07c..48414b8bd 100644 --- a/src/bindgen/ir/function.rs +++ b/src/bindgen/ir/function.rs @@ -14,7 +14,7 @@ use crate::bindgen::library::Library; use crate::bindgen::monomorph::Monomorphs; use crate::bindgen::rename::{IdentifierType, RenameRule}; use crate::bindgen::reserved; -use crate::bindgen::utilities::IterHelpers; +use crate::bindgen::utilities::{IterHelpers, SynAttributeHelpers}; #[derive(Debug, Clone)] pub struct FunctionArgument { @@ -36,6 +36,9 @@ pub struct Function { pub annotations: AnnotationSet, pub documentation: Documentation, pub never_return: bool, + /// Per-item C++ namespace path from `#[cbindgen::namespace = "..."]` attribute. + /// For example, `["ffi", "bar"]` for `#[cbindgen::namespace = "ffi::bar"]`. + pub namespace: Option>, } impl Function { @@ -65,6 +68,9 @@ impl Function { ret.replace_self_with(self_path); } + // Parse the #[cbindgen::namespace = "..."] attribute + let namespace = attrs.get_cbindgen_namespace(); + Ok(Function { path, self_type_path: self_type_path.cloned(), @@ -75,6 +81,7 @@ impl Function { annotations: AnnotationSet::load(attrs)?, documentation: Documentation::load(attrs), never_return, + namespace, }) } diff --git a/src/bindgen/language_backend/clike.rs b/src/bindgen/language_backend/clike.rs index dba5a10ac..a589262c2 100644 --- a/src/bindgen/language_backend/clike.rs +++ b/src/bindgen/language_backend/clike.rs @@ -1,13 +1,14 @@ 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, Function, GenericParams, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, + ToCondition, Type, Typedef, Union, }; use crate::bindgen::language_backend::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::collections::BTreeMap; use std::io::Write; pub struct CLikeLanguageBackend<'a> { @@ -116,6 +117,81 @@ impl<'a> CLikeLanguageBackend<'a> { self.config.language == Language::C && self.config.style.generate_typedef() } + /// Writes functions, grouping those with per-item namespace attributes into + /// nested namespace blocks (for C++ output only). + fn write_functions_with_namespaces( + &mut self, + out: &mut SourceWriter, + b: &Bindings, + ) where + Self: LanguageBackend, + { + // Only apply per-item namespace grouping for C++ output + if b.config.language != Language::Cxx { + self.write_functions_default(out, b); + return; + } + + // Group functions by their per-item namespace + // Functions without a namespace go to the `None` key + let mut grouped: BTreeMap>, Vec<&Function>> = BTreeMap::new(); + + for function in &b.functions { + if !function.annotations.should_export() { + continue; + } + grouped + .entry(function.namespace.clone()) + .or_default() + .push(function); + } + + // Write functions without per-item namespace first (they use global namespace) + if let Some(functions) = grouped.remove(&None) { + for function in functions { + out.new_line_if_not_start(); + self.write_function(&b.config, out, function); + out.new_line(); + } + } + + // Write functions with per-item namespaces + for (namespace, functions) in grouped { + if let Some(ns_parts) = namespace { + if ns_parts.is_empty() { + // Empty namespace means no namespace wrapping + for function in functions { + out.new_line_if_not_start(); + self.write_function(&b.config, out, function); + out.new_line(); + } + } else { + // Open nested namespaces + out.new_line_if_not_start(); + for ns in &ns_parts { + out.new_line(); + write!(out, "namespace {ns} {{"); + } + out.new_line(); + + // Write functions inside the namespace + for function in functions { + out.new_line_if_not_start(); + self.write_function(&b.config, out, function); + out.new_line(); + } + + // Close nested namespaces in reverse order + for ns in ns_parts.iter().rev() { + out.new_line(); + write!(out, "}} // namespace {ns}"); + } + out.new_line(); + } + } + } + } + fn write_derived_cpp_ops(&mut self, out: &mut SourceWriter, s: &Struct) { let mut wrote_start_newline = false; @@ -990,7 +1066,7 @@ impl LanguageBackend for CLikeLanguageBackend<'_> { // Override default method to close various blocks containing both globals and functions // these blocks are opened in [`write_globals`] that is also overridden if !b.functions.is_empty() || !b.globals.is_empty() { - self.write_functions_default(out, b); + self.write_functions_with_namespaces(out, b); if b.config.cpp_compatible_c() { out.new_line(); diff --git a/src/bindgen/utilities.rs b/src/bindgen/utilities.rs index 8f0763b83..a0e2fe229 100644 --- a/src/bindgen/utilities.rs +++ b/src/bindgen/utilities.rs @@ -294,6 +294,44 @@ pub trait SynAttributeHelpers { .next() } + /// Looks up the `#[cbindgen::namespace = "..."]` attribute and parses it + /// into a vector of namespace segments. Supports `::` as the separator. + /// For example, `"ffi::bar"` becomes `["ffi", "bar"]`. + fn get_cbindgen_namespace(&self) -> Option> { + for attr in self.attrs() { + if let syn::Meta::NameValue(syn::MetaNameValue { + path, + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }), + .. + }) = &attr.meta + { + // Check for #[cbindgen::namespace = "..."] + if path.segments.len() == 2 { + let first = &path.segments[0]; + let second = &path.segments[1]; + if first.ident == "cbindgen" && second.ident == "namespace" { + let value = lit.value(); + if value.is_empty() { + return Some(Vec::new()); + } + // Split by "::" separator + let segments: Vec = value + .split("::") + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + return Some(segments); + } + } + } + } + None + } + fn get_comment_lines(&self) -> Vec { let mut comment = Vec::new(); diff --git a/tests/expectations-symbols/namespace_attr.c.sym b/tests/expectations-symbols/namespace_attr.c.sym new file mode 100644 index 000000000..a99e909a4 --- /dev/null +++ b/tests/expectations-symbols/namespace_attr.c.sym @@ -0,0 +1,7 @@ +{ +global_function; +ffi_function; +nested_function; +another_nested_function; +other_namespace_function; +}; \ No newline at end of file diff --git a/tests/expectations-symbols/namespace_attr_precedence.c.sym b/tests/expectations-symbols/namespace_attr_precedence.c.sym new file mode 100644 index 000000000..27afd8679 --- /dev/null +++ b/tests/expectations-symbols/namespace_attr_precedence.c.sym @@ -0,0 +1,5 @@ +{ +uses_global_namespace; +uses_item_namespace; +also_uses_global_namespace; +}; \ No newline at end of file diff --git a/tests/expectations/namespace_attr.c b/tests/expectations/namespace_attr.c new file mode 100644 index 000000000..f41467493 --- /dev/null +++ b/tests/expectations/namespace_attr.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +/** + * A function without namespace attribute - uses global namespace + */ +void global_function(void); + +/** + * A function with a single namespace + */ +void ffi_function(void); + +/** + * A function with nested namespaces using :: separator + */ +void nested_function(const char *a); + +/** + * Another function with the same namespace to test grouping + */ +void another_nested_function(void); + +/** + * A function with a different nested namespace + */ +void other_namespace_function(void); diff --git a/tests/expectations/namespace_attr.compat.c b/tests/expectations/namespace_attr.compat.c new file mode 100644 index 000000000..bbdad1229 --- /dev/null +++ b/tests/expectations/namespace_attr.compat.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * A function without namespace attribute - uses global namespace + */ +void global_function(void); + +/** + * A function with a single namespace + */ +void ffi_function(void); + +/** + * A function with nested namespaces using :: separator + */ +void nested_function(const char *a); + +/** + * Another function with the same namespace to test grouping + */ +void another_nested_function(void); + +/** + * A function with a different nested namespace + */ +void other_namespace_function(void); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/namespace_attr.cpp b/tests/expectations/namespace_attr.cpp new file mode 100644 index 000000000..79502581f --- /dev/null +++ b/tests/expectations/namespace_attr.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +extern "C" { + +/// A function without namespace attribute - uses global namespace +void global_function(); + + +namespace ffi { + +/// A function with a single namespace +void ffi_function(); + +} // namespace ffi + + +namespace ffi { +namespace inner { + +/// A function with nested namespaces using :: separator +void nested_function(const char *a); + +/// Another function with the same namespace to test grouping +void another_nested_function(); + +} // namespace inner +} // namespace ffi + + +namespace other { +namespace ns { + +/// A function with a different nested namespace +void other_namespace_function(); + +} // namespace ns +} // namespace other + +} // extern "C" diff --git a/tests/expectations/namespace_attr.pyx b/tests/expectations/namespace_attr.pyx new file mode 100644 index 000000000..866181c81 --- /dev/null +++ b/tests/expectations/namespace_attr.pyx @@ -0,0 +1,22 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + # A function without namespace attribute - uses global namespace + void global_function(); + + # A function with a single namespace + void ffi_function(); + + # A function with nested namespaces using :: separator + void nested_function(const char *a); + + # Another function with the same namespace to test grouping + void another_nested_function(); + + # A function with a different nested namespace + void other_namespace_function(); diff --git a/tests/expectations/namespace_attr_precedence.c b/tests/expectations/namespace_attr_precedence.c new file mode 100644 index 000000000..0f07d7540 --- /dev/null +++ b/tests/expectations/namespace_attr_precedence.c @@ -0,0 +1,19 @@ +#include +#include +#include +#include + +/** + * A function without namespace attribute - should use global namespace + */ +void uses_global_namespace(void); + +/** + * A function with per-item namespace - should override global namespace + */ +void uses_item_namespace(const char *a); + +/** + * Another function without namespace attribute - should use global namespace + */ +void also_uses_global_namespace(void); diff --git a/tests/expectations/namespace_attr_precedence.compat.c b/tests/expectations/namespace_attr_precedence.compat.c new file mode 100644 index 000000000..beec9f309 --- /dev/null +++ b/tests/expectations/namespace_attr_precedence.compat.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#ifdef __cplusplus +namespace global_ns { +#endif // __cplusplus + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * A function without namespace attribute - should use global namespace + */ +void uses_global_namespace(void); + +/** + * A function with per-item namespace - should override global namespace + */ +void uses_item_namespace(const char *a); + +/** + * Another function without namespace attribute - should use global namespace + */ +void also_uses_global_namespace(void); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#ifdef __cplusplus +} // namespace global_ns +#endif // __cplusplus diff --git a/tests/expectations/namespace_attr_precedence.cpp b/tests/expectations/namespace_attr_precedence.cpp new file mode 100644 index 000000000..7a52ecce5 --- /dev/null +++ b/tests/expectations/namespace_attr_precedence.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +namespace global_ns { + +extern "C" { + +/// A function without namespace attribute - should use global namespace +void uses_global_namespace(); + +/// Another function without namespace attribute - should use global namespace +void also_uses_global_namespace(); + + +namespace ffi { +namespace bar { + +/// A function with per-item namespace - should override global namespace +void uses_item_namespace(const char *a); + +} // namespace bar +} // namespace ffi + +} // extern "C" + +} // namespace global_ns diff --git a/tests/expectations/namespace_attr_precedence.pyx b/tests/expectations/namespace_attr_precedence.pyx new file mode 100644 index 000000000..a820fa2fa --- /dev/null +++ b/tests/expectations/namespace_attr_precedence.pyx @@ -0,0 +1,16 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + # A function without namespace attribute - should use global namespace + void uses_global_namespace(); + + # A function with per-item namespace - should override global namespace + void uses_item_namespace(const char *a); + + # Another function without namespace attribute - should use global namespace + void also_uses_global_namespace(); diff --git a/tests/rust/namespace_attr.rs b/tests/rust/namespace_attr.rs new file mode 100644 index 000000000..5976dc30c --- /dev/null +++ b/tests/rust/namespace_attr.rs @@ -0,0 +1,25 @@ +use std::os::raw::c_char; + +/// A function without namespace attribute - uses global namespace +#[no_mangle] +pub extern "C" fn global_function() {} + +/// A function with a single namespace +#[cbindgen::namespace = "ffi"] +#[no_mangle] +pub extern "C" fn ffi_function() {} + +/// A function with nested namespaces using :: separator +#[cbindgen::namespace = "ffi::inner"] +#[no_mangle] +pub extern "C" fn nested_function(a: *const c_char) {} + +/// Another function with the same namespace to test grouping +#[cbindgen::namespace = "ffi::inner"] +#[no_mangle] +pub extern "C" fn another_nested_function() {} + +/// A function with a different nested namespace +#[cbindgen::namespace = "other::ns"] +#[no_mangle] +pub extern "C" fn other_namespace_function() {} diff --git a/tests/rust/namespace_attr.toml b/tests/rust/namespace_attr.toml new file mode 100644 index 000000000..9ccd3e39e --- /dev/null +++ b/tests/rust/namespace_attr.toml @@ -0,0 +1 @@ +language = "C++" diff --git a/tests/rust/namespace_attr_precedence.rs b/tests/rust/namespace_attr_precedence.rs new file mode 100644 index 000000000..4070170ca --- /dev/null +++ b/tests/rust/namespace_attr_precedence.rs @@ -0,0 +1,14 @@ +use std::os::raw::c_char; + +/// A function without namespace attribute - should use global namespace +#[no_mangle] +pub extern "C" fn uses_global_namespace() {} + +/// A function with per-item namespace - should override global namespace +#[cbindgen::namespace = "ffi::bar"] +#[no_mangle] +pub extern "C" fn uses_item_namespace(a: *const c_char) {} + +/// Another function without namespace attribute - should use global namespace +#[no_mangle] +pub extern "C" fn also_uses_global_namespace() {} diff --git a/tests/rust/namespace_attr_precedence.toml b/tests/rust/namespace_attr_precedence.toml new file mode 100644 index 000000000..072d4a6b6 --- /dev/null +++ b/tests/rust/namespace_attr_precedence.toml @@ -0,0 +1,2 @@ +language = "C++" +namespace = "global_ns" From 0e006459e639bd7cb4312d53412945cdb5995567 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:13:46 +0000 Subject: [PATCH 3/7] Fix: Use doc comment annotation syntax instead of attribute Changed from `#[cbindgen::namespace = "..."]` attribute syntax to `/// cbindgen:namespace=...` doc comment annotation syntax. The attribute syntax requires cbindgen to export a proc-macro, which it doesn't. The doc comment annotation is the existing cbindgen way to specify per-item configuration and doesn't cause Rust compilation errors. Example usage: ```rust /// cbindgen:namespace=ffi::bar #[no_mangle] pub extern "C" fn foo(a: *const c_char) {} ``` Co-authored-by: tarnishablec <42984497+tarnishablec@users.noreply.github.com> --- docs.md | 14 +++---- src/bindgen/ir/function.rs | 28 +++++++++++--- src/bindgen/utilities.rs | 38 ------------------- tests/expectations/namespace_attr.c | 2 +- tests/expectations/namespace_attr.compat.c | 2 +- tests/expectations/namespace_attr.cpp | 2 +- tests/expectations/namespace_attr.pyx | 2 +- .../expectations/namespace_attr_precedence.c | 4 +- .../namespace_attr_precedence.compat.c | 4 +- .../namespace_attr_precedence.cpp | 4 +- .../namespace_attr_precedence.pyx | 4 +- tests/rust/namespace_attr.rs | 10 ++--- tests/rust/namespace_attr_precedence.rs | 6 +-- 13 files changed, 49 insertions(+), 71 deletions(-) diff --git a/docs.md b/docs.md index f1c7f924c..3b172f825 100644 --- a/docs.md +++ b/docs.md @@ -409,12 +409,12 @@ arg: *const T --> const T arg[] arg: *mut T --> T arg[] ``` -### Per-Item Namespace Attribute (C++ only) +### Per-Item Namespace Annotation (C++ only) -You can use the `#[cbindgen::namespace = "..."]` attribute to specify a C++ namespace for individual functions. This is useful when you want different functions to be placed in different namespaces. +You can use the `/// cbindgen:namespace=...` annotation to specify a C++ namespace for individual functions. This is useful when you want different functions to be placed in different namespaces. ```rust -#[cbindgen::namespace = "ffi::bar"] +/// cbindgen:namespace=ffi::bar #[no_mangle] pub extern "C" fn foo(a: *const c_char) {} ``` @@ -437,11 +437,11 @@ void foo(const char *a); **Key points:** -* Use `::` as the namespace separator (e.g., `"ffi::bar"` becomes nested namespaces `namespace ffi { namespace bar { ... } }`) -* This attribute only affects C++ output; it is ignored for C and Cython output +* Use `::` as the namespace separator (e.g., `ffi::bar` becomes nested namespaces `namespace ffi { namespace bar { ... } }`) +* This annotation only affects C++ output; it is ignored for C and Cython output * Functions with the same namespace path will be grouped together in the generated header -* If both a global `namespace` in `cbindgen.toml` and a per-item `#[cbindgen::namespace]` are specified, the per-item namespace is nested inside the global namespace -* Functions without this attribute will use the global namespace (if configured) or no namespace +* If both a global `namespace` in `cbindgen.toml` and a per-item `cbindgen:namespace` are specified, the per-item namespace is nested inside the global namespace +* Functions without this annotation will use the global namespace (if configured) or no namespace ## Generating Swift Bindings diff --git a/src/bindgen/ir/function.rs b/src/bindgen/ir/function.rs index 48414b8bd..711c68a9c 100644 --- a/src/bindgen/ir/function.rs +++ b/src/bindgen/ir/function.rs @@ -14,7 +14,7 @@ use crate::bindgen::library::Library; use crate::bindgen::monomorph::Monomorphs; use crate::bindgen::rename::{IdentifierType, RenameRule}; use crate::bindgen::reserved; -use crate::bindgen::utilities::{IterHelpers, SynAttributeHelpers}; +use crate::bindgen::utilities::IterHelpers; #[derive(Debug, Clone)] pub struct FunctionArgument { @@ -36,8 +36,8 @@ pub struct Function { pub annotations: AnnotationSet, pub documentation: Documentation, pub never_return: bool, - /// Per-item C++ namespace path from `#[cbindgen::namespace = "..."]` attribute. - /// For example, `["ffi", "bar"]` for `#[cbindgen::namespace = "ffi::bar"]`. + /// Per-item C++ namespace path from `/// cbindgen:namespace=...` annotation. + /// For example, `["ffi", "bar"]` for `/// cbindgen:namespace=ffi::bar`. pub namespace: Option>, } @@ -68,8 +68,24 @@ impl Function { ret.replace_self_with(self_path); } - // Parse the #[cbindgen::namespace = "..."] attribute - let namespace = attrs.get_cbindgen_namespace(); + let annotations = AnnotationSet::load(attrs)?; + + // Parse the namespace from the /// cbindgen:namespace=... annotation + let namespace = annotations.atom("namespace").and_then(|ns_opt| { + // ns_opt is Option - None means just "cbindgen:namespace" without value + ns_opt.map(|ns_str| { + if ns_str.is_empty() { + Vec::new() + } else { + // Split by "::" separator + ns_str + .split("::") + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect() + } + }) + }); Ok(Function { path, @@ -78,7 +94,7 @@ impl Function { args, extern_decl, cfg: Cfg::append(mod_cfg, Cfg::load(attrs)), - annotations: AnnotationSet::load(attrs)?, + annotations, documentation: Documentation::load(attrs), never_return, namespace, diff --git a/src/bindgen/utilities.rs b/src/bindgen/utilities.rs index a0e2fe229..8f0763b83 100644 --- a/src/bindgen/utilities.rs +++ b/src/bindgen/utilities.rs @@ -294,44 +294,6 @@ pub trait SynAttributeHelpers { .next() } - /// Looks up the `#[cbindgen::namespace = "..."]` attribute and parses it - /// into a vector of namespace segments. Supports `::` as the separator. - /// For example, `"ffi::bar"` becomes `["ffi", "bar"]`. - fn get_cbindgen_namespace(&self) -> Option> { - for attr in self.attrs() { - if let syn::Meta::NameValue(syn::MetaNameValue { - path, - value: - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }), - .. - }) = &attr.meta - { - // Check for #[cbindgen::namespace = "..."] - if path.segments.len() == 2 { - let first = &path.segments[0]; - let second = &path.segments[1]; - if first.ident == "cbindgen" && second.ident == "namespace" { - let value = lit.value(); - if value.is_empty() { - return Some(Vec::new()); - } - // Split by "::" separator - let segments: Vec = value - .split("::") - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .collect(); - return Some(segments); - } - } - } - } - None - } - fn get_comment_lines(&self) -> Vec { let mut comment = Vec::new(); diff --git a/tests/expectations/namespace_attr.c b/tests/expectations/namespace_attr.c index f41467493..fe69620ae 100644 --- a/tests/expectations/namespace_attr.c +++ b/tests/expectations/namespace_attr.c @@ -4,7 +4,7 @@ #include /** - * A function without namespace attribute - uses global namespace + * A function without namespace annotation - uses global namespace */ void global_function(void); diff --git a/tests/expectations/namespace_attr.compat.c b/tests/expectations/namespace_attr.compat.c index bbdad1229..a59d3140d 100644 --- a/tests/expectations/namespace_attr.compat.c +++ b/tests/expectations/namespace_attr.compat.c @@ -8,7 +8,7 @@ extern "C" { #endif // __cplusplus /** - * A function without namespace attribute - uses global namespace + * A function without namespace annotation - uses global namespace */ void global_function(void); diff --git a/tests/expectations/namespace_attr.cpp b/tests/expectations/namespace_attr.cpp index 79502581f..39026e93d 100644 --- a/tests/expectations/namespace_attr.cpp +++ b/tests/expectations/namespace_attr.cpp @@ -6,7 +6,7 @@ extern "C" { -/// A function without namespace attribute - uses global namespace +/// A function without namespace annotation - uses global namespace void global_function(); diff --git a/tests/expectations/namespace_attr.pyx b/tests/expectations/namespace_attr.pyx index 866181c81..30ffd89c4 100644 --- a/tests/expectations/namespace_attr.pyx +++ b/tests/expectations/namespace_attr.pyx @@ -6,7 +6,7 @@ cdef extern from *: cdef extern from *: - # A function without namespace attribute - uses global namespace + # A function without namespace annotation - uses global namespace void global_function(); # A function with a single namespace diff --git a/tests/expectations/namespace_attr_precedence.c b/tests/expectations/namespace_attr_precedence.c index 0f07d7540..26e470e04 100644 --- a/tests/expectations/namespace_attr_precedence.c +++ b/tests/expectations/namespace_attr_precedence.c @@ -4,7 +4,7 @@ #include /** - * A function without namespace attribute - should use global namespace + * A function without namespace annotation - should use global namespace */ void uses_global_namespace(void); @@ -14,6 +14,6 @@ void uses_global_namespace(void); void uses_item_namespace(const char *a); /** - * Another function without namespace attribute - should use global namespace + * Another function without namespace annotation - should use global namespace */ void also_uses_global_namespace(void); diff --git a/tests/expectations/namespace_attr_precedence.compat.c b/tests/expectations/namespace_attr_precedence.compat.c index beec9f309..98913d5bd 100644 --- a/tests/expectations/namespace_attr_precedence.compat.c +++ b/tests/expectations/namespace_attr_precedence.compat.c @@ -12,7 +12,7 @@ extern "C" { #endif // __cplusplus /** - * A function without namespace attribute - should use global namespace + * A function without namespace annotation - should use global namespace */ void uses_global_namespace(void); @@ -22,7 +22,7 @@ void uses_global_namespace(void); void uses_item_namespace(const char *a); /** - * Another function without namespace attribute - should use global namespace + * Another function without namespace annotation - should use global namespace */ void also_uses_global_namespace(void); diff --git a/tests/expectations/namespace_attr_precedence.cpp b/tests/expectations/namespace_attr_precedence.cpp index 7a52ecce5..2644c4019 100644 --- a/tests/expectations/namespace_attr_precedence.cpp +++ b/tests/expectations/namespace_attr_precedence.cpp @@ -8,10 +8,10 @@ namespace global_ns { extern "C" { -/// A function without namespace attribute - should use global namespace +/// A function without namespace annotation - should use global namespace void uses_global_namespace(); -/// Another function without namespace attribute - should use global namespace +/// Another function without namespace annotation - should use global namespace void also_uses_global_namespace(); diff --git a/tests/expectations/namespace_attr_precedence.pyx b/tests/expectations/namespace_attr_precedence.pyx index a820fa2fa..593c173f5 100644 --- a/tests/expectations/namespace_attr_precedence.pyx +++ b/tests/expectations/namespace_attr_precedence.pyx @@ -6,11 +6,11 @@ cdef extern from *: cdef extern from *: - # A function without namespace attribute - should use global namespace + # A function without namespace annotation - should use global namespace void uses_global_namespace(); # A function with per-item namespace - should override global namespace void uses_item_namespace(const char *a); - # Another function without namespace attribute - should use global namespace + # Another function without namespace annotation - should use global namespace void also_uses_global_namespace(); diff --git a/tests/rust/namespace_attr.rs b/tests/rust/namespace_attr.rs index 5976dc30c..0d1f18da3 100644 --- a/tests/rust/namespace_attr.rs +++ b/tests/rust/namespace_attr.rs @@ -1,25 +1,25 @@ use std::os::raw::c_char; -/// A function without namespace attribute - uses global namespace +/// A function without namespace annotation - uses global namespace #[no_mangle] pub extern "C" fn global_function() {} /// A function with a single namespace -#[cbindgen::namespace = "ffi"] +/// cbindgen:namespace=ffi #[no_mangle] pub extern "C" fn ffi_function() {} /// A function with nested namespaces using :: separator -#[cbindgen::namespace = "ffi::inner"] +/// cbindgen:namespace=ffi::inner #[no_mangle] pub extern "C" fn nested_function(a: *const c_char) {} /// Another function with the same namespace to test grouping -#[cbindgen::namespace = "ffi::inner"] +/// cbindgen:namespace=ffi::inner #[no_mangle] pub extern "C" fn another_nested_function() {} /// A function with a different nested namespace -#[cbindgen::namespace = "other::ns"] +/// cbindgen:namespace=other::ns #[no_mangle] pub extern "C" fn other_namespace_function() {} diff --git a/tests/rust/namespace_attr_precedence.rs b/tests/rust/namespace_attr_precedence.rs index 4070170ca..5fb0268de 100644 --- a/tests/rust/namespace_attr_precedence.rs +++ b/tests/rust/namespace_attr_precedence.rs @@ -1,14 +1,14 @@ use std::os::raw::c_char; -/// A function without namespace attribute - should use global namespace +/// A function without namespace annotation - should use global namespace #[no_mangle] pub extern "C" fn uses_global_namespace() {} /// A function with per-item namespace - should override global namespace -#[cbindgen::namespace = "ffi::bar"] +/// cbindgen:namespace=ffi::bar #[no_mangle] pub extern "C" fn uses_item_namespace(a: *const c_char) {} -/// Another function without namespace attribute - should use global namespace +/// Another function without namespace annotation - should use global namespace #[no_mangle] pub extern "C" fn also_uses_global_namespace() {} From 9d805f01ab6902fd21a92cf868bc6562e47d4092 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:26:56 +0000 Subject: [PATCH 4/7] Add cbindgen-macro proc-macro crate for #[namespace] attribute Created a separate proc-macro crate that provides the `namespace` attribute. This allows users to use the Rust attribute syntax: ```rust #[cbindgen_macro::namespace = "ffi::bar"] #[no_mangle] pub extern "C" fn foo() {} ``` The attribute is a no-op at compile time but is parsed by cbindgen to determine the C++ namespace for the generated header. Changes: - Created cbindgen-macro crate with `namespace` proc-macro attribute - Updated utilities.rs to accept both `cbindgen::namespace` and `cbindgen_macro::namespace` attribute paths - Updated tests to use `cbindgen_macro::namespace` syntax - Updated documentation with usage instructions Co-authored-by: tarnishablec <42984497+tarnishablec@users.noreply.github.com> --- Cargo.toml | 1 + cbindgen-macro/Cargo.lock | 47 +++++++++++++++ cbindgen-macro/Cargo.toml | 14 +++++ cbindgen-macro/src/lib.rs | 59 +++++++++++++++++++ docs.md | 23 +++++--- src/bindgen/ir/function.rs | 28 ++------- src/bindgen/utilities.rs | 42 +++++++++++++ tests/expectations/namespace_attr.c | 2 +- tests/expectations/namespace_attr.compat.c | 2 +- tests/expectations/namespace_attr.cpp | 2 +- tests/expectations/namespace_attr.pyx | 2 +- .../expectations/namespace_attr_precedence.c | 4 +- .../namespace_attr_precedence.compat.c | 4 +- .../namespace_attr_precedence.cpp | 4 +- .../namespace_attr_precedence.pyx | 4 +- tests/rust/namespace_attr.rs | 10 ++-- tests/rust/namespace_attr_precedence.rs | 6 +- 17 files changed, 205 insertions(+), 49 deletions(-) create mode 100644 cbindgen-macro/Cargo.lock create mode 100644 cbindgen-macro/Cargo.toml create mode 100644 cbindgen-macro/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 1b684aca2..7df4e5ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ path = "src/lib.rs" [workspace] exclude = [ + "cbindgen-macro", "tests/depfile/single_crate_config", "tests/depfile/single_crate_default_config", "tests/depfile/single_crate", diff --git a/cbindgen-macro/Cargo.lock b/cbindgen-macro/Cargo.lock new file mode 100644 index 000000000..81d9de8c8 --- /dev/null +++ b/cbindgen-macro/Cargo.lock @@ -0,0 +1,47 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cbindgen-macro" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" diff --git a/cbindgen-macro/Cargo.toml b/cbindgen-macro/Cargo.toml new file mode 100644 index 000000000..104e68192 --- /dev/null +++ b/cbindgen-macro/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cbindgen-macro" +version = "0.1.0" +edition = "2021" +description = "Procedural macro attributes for cbindgen" +license = "MPL-2.0" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1" +syn = { version = "2.0", features = ["full"] } diff --git a/cbindgen-macro/src/lib.rs b/cbindgen-macro/src/lib.rs new file mode 100644 index 000000000..b55a23800 --- /dev/null +++ b/cbindgen-macro/src/lib.rs @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Procedural macro attributes for cbindgen. +//! +//! This crate provides the `#[cbindgen::namespace]` attribute that can be used +//! to specify C++ namespaces for individual functions in generated headers. +//! +//! # Example +//! +//! ```rust,ignore +//! use cbindgen_macro::namespace; +//! +//! #[namespace = "ffi::bar"] +//! #[no_mangle] +//! pub extern "C" fn foo() {} +//! ``` +//! +//! The attribute itself is a no-op at compile time - it simply passes through +//! the item unchanged. However, cbindgen parses this attribute from the source +//! code to determine the C++ namespace for the function in the generated header. + +use proc_macro::TokenStream; + +/// Specifies a C++ namespace for a function in cbindgen-generated headers. +/// +/// This attribute is a no-op at compile time but is parsed by cbindgen to +/// determine where to place the function declaration in the generated C++ header. +/// +/// # Example +/// +/// ```rust,ignore +/// #[cbindgen_macro::namespace = "ffi::bar"] +/// #[no_mangle] +/// pub extern "C" fn foo() {} +/// ``` +/// +/// This will generate the following C++ code: +/// +/// ```cpp +/// extern "C" { +/// +/// namespace ffi { +/// namespace bar { +/// +/// void foo(); +/// +/// } // namespace bar +/// } // namespace ffi +/// +/// } // extern "C" +/// ``` +#[proc_macro_attribute] +pub fn namespace(_attr: TokenStream, item: TokenStream) -> TokenStream { + // This is a no-op - we just pass through the item unchanged. + // cbindgen parses the attribute directly from the source code. + item +} diff --git a/docs.md b/docs.md index 3b172f825..4aa57796b 100644 --- a/docs.md +++ b/docs.md @@ -409,12 +409,21 @@ arg: *const T --> const T arg[] arg: *mut T --> T arg[] ``` -### Per-Item Namespace Annotation (C++ only) +### Per-Item Namespace Attribute (C++ only) -You can use the `/// cbindgen:namespace=...` annotation to specify a C++ namespace for individual functions. This is useful when you want different functions to be placed in different namespaces. +You can use the `#[cbindgen_macro::namespace = "..."]` attribute to specify a C++ namespace for individual functions. This is useful when you want different functions to be placed in different namespaces. + +First, add the `cbindgen-macro` crate to your dependencies: + +```toml +[dependencies] +cbindgen-macro = { path = "path/to/cbindgen/cbindgen-macro" } +``` + +Then use the attribute on your functions: ```rust -/// cbindgen:namespace=ffi::bar +#[cbindgen_macro::namespace = "ffi::bar"] #[no_mangle] pub extern "C" fn foo(a: *const c_char) {} ``` @@ -437,11 +446,11 @@ void foo(const char *a); **Key points:** -* Use `::` as the namespace separator (e.g., `ffi::bar` becomes nested namespaces `namespace ffi { namespace bar { ... } }`) -* This annotation only affects C++ output; it is ignored for C and Cython output +* Use `::` as the namespace separator (e.g., `"ffi::bar"` becomes nested namespaces `namespace ffi { namespace bar { ... } }`) +* This attribute only affects C++ output; it is ignored for C and Cython output * Functions with the same namespace path will be grouped together in the generated header -* If both a global `namespace` in `cbindgen.toml` and a per-item `cbindgen:namespace` are specified, the per-item namespace is nested inside the global namespace -* Functions without this annotation will use the global namespace (if configured) or no namespace +* If both a global `namespace` in `cbindgen.toml` and a per-item namespace attribute are specified, the per-item namespace is nested inside the global namespace +* Functions without this attribute will use the global namespace (if configured) or no namespace ## Generating Swift Bindings diff --git a/src/bindgen/ir/function.rs b/src/bindgen/ir/function.rs index 711c68a9c..48414b8bd 100644 --- a/src/bindgen/ir/function.rs +++ b/src/bindgen/ir/function.rs @@ -14,7 +14,7 @@ use crate::bindgen::library::Library; use crate::bindgen::monomorph::Monomorphs; use crate::bindgen::rename::{IdentifierType, RenameRule}; use crate::bindgen::reserved; -use crate::bindgen::utilities::IterHelpers; +use crate::bindgen::utilities::{IterHelpers, SynAttributeHelpers}; #[derive(Debug, Clone)] pub struct FunctionArgument { @@ -36,8 +36,8 @@ pub struct Function { pub annotations: AnnotationSet, pub documentation: Documentation, pub never_return: bool, - /// Per-item C++ namespace path from `/// cbindgen:namespace=...` annotation. - /// For example, `["ffi", "bar"]` for `/// cbindgen:namespace=ffi::bar`. + /// Per-item C++ namespace path from `#[cbindgen::namespace = "..."]` attribute. + /// For example, `["ffi", "bar"]` for `#[cbindgen::namespace = "ffi::bar"]`. pub namespace: Option>, } @@ -68,24 +68,8 @@ impl Function { ret.replace_self_with(self_path); } - let annotations = AnnotationSet::load(attrs)?; - - // Parse the namespace from the /// cbindgen:namespace=... annotation - let namespace = annotations.atom("namespace").and_then(|ns_opt| { - // ns_opt is Option - None means just "cbindgen:namespace" without value - ns_opt.map(|ns_str| { - if ns_str.is_empty() { - Vec::new() - } else { - // Split by "::" separator - ns_str - .split("::") - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .collect() - } - }) - }); + // Parse the #[cbindgen::namespace = "..."] attribute + let namespace = attrs.get_cbindgen_namespace(); Ok(Function { path, @@ -94,7 +78,7 @@ impl Function { args, extern_decl, cfg: Cfg::append(mod_cfg, Cfg::load(attrs)), - annotations, + annotations: AnnotationSet::load(attrs)?, documentation: Documentation::load(attrs), never_return, namespace, diff --git a/src/bindgen/utilities.rs b/src/bindgen/utilities.rs index 8f0763b83..687c812ad 100644 --- a/src/bindgen/utilities.rs +++ b/src/bindgen/utilities.rs @@ -294,6 +294,48 @@ pub trait SynAttributeHelpers { .next() } + /// Looks up the `#[cbindgen::namespace = "..."]` or `#[cbindgen_macro::namespace = "..."]` + /// attribute and parses it into a vector of namespace segments. + /// Supports `::` as the separator. + /// For example, `"ffi::bar"` becomes `["ffi", "bar"]`. + fn get_cbindgen_namespace(&self) -> Option> { + for attr in self.attrs() { + if let syn::Meta::NameValue(syn::MetaNameValue { + path, + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }), + .. + }) = &attr.meta + { + // Check for #[cbindgen::namespace = "..."] or #[cbindgen_macro::namespace = "..."] + if path.segments.len() == 2 { + let first = &path.segments[0]; + let second = &path.segments[1]; + let first_ident = first.ident.to_string(); + if (first_ident == "cbindgen" || first_ident == "cbindgen_macro") + && second.ident == "namespace" + { + let value = lit.value(); + if value.is_empty() { + return Some(Vec::new()); + } + // Split by "::" separator + let segments: Vec = value + .split("::") + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + return Some(segments); + } + } + } + } + None + } + fn get_comment_lines(&self) -> Vec { let mut comment = Vec::new(); diff --git a/tests/expectations/namespace_attr.c b/tests/expectations/namespace_attr.c index fe69620ae..f41467493 100644 --- a/tests/expectations/namespace_attr.c +++ b/tests/expectations/namespace_attr.c @@ -4,7 +4,7 @@ #include /** - * A function without namespace annotation - uses global namespace + * A function without namespace attribute - uses global namespace */ void global_function(void); diff --git a/tests/expectations/namespace_attr.compat.c b/tests/expectations/namespace_attr.compat.c index a59d3140d..bbdad1229 100644 --- a/tests/expectations/namespace_attr.compat.c +++ b/tests/expectations/namespace_attr.compat.c @@ -8,7 +8,7 @@ extern "C" { #endif // __cplusplus /** - * A function without namespace annotation - uses global namespace + * A function without namespace attribute - uses global namespace */ void global_function(void); diff --git a/tests/expectations/namespace_attr.cpp b/tests/expectations/namespace_attr.cpp index 39026e93d..79502581f 100644 --- a/tests/expectations/namespace_attr.cpp +++ b/tests/expectations/namespace_attr.cpp @@ -6,7 +6,7 @@ extern "C" { -/// A function without namespace annotation - uses global namespace +/// A function without namespace attribute - uses global namespace void global_function(); diff --git a/tests/expectations/namespace_attr.pyx b/tests/expectations/namespace_attr.pyx index 30ffd89c4..866181c81 100644 --- a/tests/expectations/namespace_attr.pyx +++ b/tests/expectations/namespace_attr.pyx @@ -6,7 +6,7 @@ cdef extern from *: cdef extern from *: - # A function without namespace annotation - uses global namespace + # A function without namespace attribute - uses global namespace void global_function(); # A function with a single namespace diff --git a/tests/expectations/namespace_attr_precedence.c b/tests/expectations/namespace_attr_precedence.c index 26e470e04..0f07d7540 100644 --- a/tests/expectations/namespace_attr_precedence.c +++ b/tests/expectations/namespace_attr_precedence.c @@ -4,7 +4,7 @@ #include /** - * A function without namespace annotation - should use global namespace + * A function without namespace attribute - should use global namespace */ void uses_global_namespace(void); @@ -14,6 +14,6 @@ void uses_global_namespace(void); void uses_item_namespace(const char *a); /** - * Another function without namespace annotation - should use global namespace + * Another function without namespace attribute - should use global namespace */ void also_uses_global_namespace(void); diff --git a/tests/expectations/namespace_attr_precedence.compat.c b/tests/expectations/namespace_attr_precedence.compat.c index 98913d5bd..beec9f309 100644 --- a/tests/expectations/namespace_attr_precedence.compat.c +++ b/tests/expectations/namespace_attr_precedence.compat.c @@ -12,7 +12,7 @@ extern "C" { #endif // __cplusplus /** - * A function without namespace annotation - should use global namespace + * A function without namespace attribute - should use global namespace */ void uses_global_namespace(void); @@ -22,7 +22,7 @@ void uses_global_namespace(void); void uses_item_namespace(const char *a); /** - * Another function without namespace annotation - should use global namespace + * Another function without namespace attribute - should use global namespace */ void also_uses_global_namespace(void); diff --git a/tests/expectations/namespace_attr_precedence.cpp b/tests/expectations/namespace_attr_precedence.cpp index 2644c4019..7a52ecce5 100644 --- a/tests/expectations/namespace_attr_precedence.cpp +++ b/tests/expectations/namespace_attr_precedence.cpp @@ -8,10 +8,10 @@ namespace global_ns { extern "C" { -/// A function without namespace annotation - should use global namespace +/// A function without namespace attribute - should use global namespace void uses_global_namespace(); -/// Another function without namespace annotation - should use global namespace +/// Another function without namespace attribute - should use global namespace void also_uses_global_namespace(); diff --git a/tests/expectations/namespace_attr_precedence.pyx b/tests/expectations/namespace_attr_precedence.pyx index 593c173f5..a820fa2fa 100644 --- a/tests/expectations/namespace_attr_precedence.pyx +++ b/tests/expectations/namespace_attr_precedence.pyx @@ -6,11 +6,11 @@ cdef extern from *: cdef extern from *: - # A function without namespace annotation - should use global namespace + # A function without namespace attribute - should use global namespace void uses_global_namespace(); # A function with per-item namespace - should override global namespace void uses_item_namespace(const char *a); - # Another function without namespace annotation - should use global namespace + # Another function without namespace attribute - should use global namespace void also_uses_global_namespace(); diff --git a/tests/rust/namespace_attr.rs b/tests/rust/namespace_attr.rs index 0d1f18da3..cfc2217fb 100644 --- a/tests/rust/namespace_attr.rs +++ b/tests/rust/namespace_attr.rs @@ -1,25 +1,25 @@ use std::os::raw::c_char; -/// A function without namespace annotation - uses global namespace +/// A function without namespace attribute - uses global namespace #[no_mangle] pub extern "C" fn global_function() {} /// A function with a single namespace -/// cbindgen:namespace=ffi +#[cbindgen_macro::namespace = "ffi"] #[no_mangle] pub extern "C" fn ffi_function() {} /// A function with nested namespaces using :: separator -/// cbindgen:namespace=ffi::inner +#[cbindgen_macro::namespace = "ffi::inner"] #[no_mangle] pub extern "C" fn nested_function(a: *const c_char) {} /// Another function with the same namespace to test grouping -/// cbindgen:namespace=ffi::inner +#[cbindgen_macro::namespace = "ffi::inner"] #[no_mangle] pub extern "C" fn another_nested_function() {} /// A function with a different nested namespace -/// cbindgen:namespace=other::ns +#[cbindgen_macro::namespace = "other::ns"] #[no_mangle] pub extern "C" fn other_namespace_function() {} diff --git a/tests/rust/namespace_attr_precedence.rs b/tests/rust/namespace_attr_precedence.rs index 5fb0268de..55766d83a 100644 --- a/tests/rust/namespace_attr_precedence.rs +++ b/tests/rust/namespace_attr_precedence.rs @@ -1,14 +1,14 @@ use std::os::raw::c_char; -/// A function without namespace annotation - should use global namespace +/// A function without namespace attribute - should use global namespace #[no_mangle] pub extern "C" fn uses_global_namespace() {} /// A function with per-item namespace - should override global namespace -/// cbindgen:namespace=ffi::bar +#[cbindgen_macro::namespace = "ffi::bar"] #[no_mangle] pub extern "C" fn uses_item_namespace(a: *const c_char) {} -/// Another function without namespace annotation - should use global namespace +/// Another function without namespace attribute - should use global namespace #[no_mangle] pub extern "C" fn also_uses_global_namespace() {} From 521021dd0bc0de7aee8b8ae88c64710b270aa252 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:38:02 +0000 Subject: [PATCH 5/7] Change attribute syntax from = to function-like parentheses Changed the namespace attribute syntax from: #[cbindgen_macro::namespace = "ffi::bar"] to: #[cbindgen_macro::namespace("ffi::bar")] This change follows the function-like attribute syntax preferred by the user. Updated: - cbindgen-macro proc-macro crate - utilities.rs parsing logic to handle Meta::List instead of Meta::NameValue - Test files to use the new syntax - Documentation with the new syntax examples Co-authored-by: tarnishablec <42984497+tarnishablec@users.noreply.github.com> --- cbindgen-macro/src/lib.rs | 6 +++--- docs.md | 4 ++-- src/bindgen/utilities.rs | 20 +++++++------------- tests/rust/namespace_attr.rs | 8 ++++---- tests/rust/namespace_attr_precedence.rs | 2 +- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/cbindgen-macro/src/lib.rs b/cbindgen-macro/src/lib.rs index b55a23800..9aeac11fb 100644 --- a/cbindgen-macro/src/lib.rs +++ b/cbindgen-macro/src/lib.rs @@ -4,7 +4,7 @@ //! Procedural macro attributes for cbindgen. //! -//! This crate provides the `#[cbindgen::namespace]` attribute that can be used +//! This crate provides the `#[cbindgen_macro::namespace()]` attribute that can be used //! to specify C++ namespaces for individual functions in generated headers. //! //! # Example @@ -12,7 +12,7 @@ //! ```rust,ignore //! use cbindgen_macro::namespace; //! -//! #[namespace = "ffi::bar"] +//! #[namespace("ffi::bar")] //! #[no_mangle] //! pub extern "C" fn foo() {} //! ``` @@ -31,7 +31,7 @@ use proc_macro::TokenStream; /// # Example /// /// ```rust,ignore -/// #[cbindgen_macro::namespace = "ffi::bar"] +/// #[cbindgen_macro::namespace("ffi::bar")] /// #[no_mangle] /// pub extern "C" fn foo() {} /// ``` diff --git a/docs.md b/docs.md index 4aa57796b..c2dc72c02 100644 --- a/docs.md +++ b/docs.md @@ -411,7 +411,7 @@ arg: *mut T --> T arg[] ### Per-Item Namespace Attribute (C++ only) -You can use the `#[cbindgen_macro::namespace = "..."]` attribute to specify a C++ namespace for individual functions. This is useful when you want different functions to be placed in different namespaces. +You can use the `#[cbindgen_macro::namespace("...")]` attribute to specify a C++ namespace for individual functions. This is useful when you want different functions to be placed in different namespaces. First, add the `cbindgen-macro` crate to your dependencies: @@ -423,7 +423,7 @@ cbindgen-macro = { path = "path/to/cbindgen/cbindgen-macro" } Then use the attribute on your functions: ```rust -#[cbindgen_macro::namespace = "ffi::bar"] +#[cbindgen_macro::namespace("ffi::bar")] #[no_mangle] pub extern "C" fn foo(a: *const c_char) {} ``` diff --git a/src/bindgen/utilities.rs b/src/bindgen/utilities.rs index 687c812ad..30b03a69a 100644 --- a/src/bindgen/utilities.rs +++ b/src/bindgen/utilities.rs @@ -294,23 +294,14 @@ pub trait SynAttributeHelpers { .next() } - /// Looks up the `#[cbindgen::namespace = "..."]` or `#[cbindgen_macro::namespace = "..."]` + /// Looks up the `#[cbindgen::namespace("...")]` or `#[cbindgen_macro::namespace("...")]` /// attribute and parses it into a vector of namespace segments. /// Supports `::` as the separator. /// For example, `"ffi::bar"` becomes `["ffi", "bar"]`. fn get_cbindgen_namespace(&self) -> Option> { for attr in self.attrs() { - if let syn::Meta::NameValue(syn::MetaNameValue { - path, - value: - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }), - .. - }) = &attr.meta - { - // Check for #[cbindgen::namespace = "..."] or #[cbindgen_macro::namespace = "..."] + // Handle #[cbindgen_macro::namespace("...")] or #[cbindgen::namespace("...")] syntax + if let syn::Meta::List(syn::MetaList { path, tokens, .. }) = &attr.meta { if path.segments.len() == 2 { let first = &path.segments[0]; let second = &path.segments[1]; @@ -318,7 +309,10 @@ pub trait SynAttributeHelpers { if (first_ident == "cbindgen" || first_ident == "cbindgen_macro") && second.ident == "namespace" { - let value = lit.value(); + // Parse the tokens inside the parentheses as a string literal + let tokens_str = tokens.to_string(); + // Remove surrounding quotes if present + let value = tokens_str.trim_matches('"').to_string(); if value.is_empty() { return Some(Vec::new()); } diff --git a/tests/rust/namespace_attr.rs b/tests/rust/namespace_attr.rs index cfc2217fb..c2ee1c0fc 100644 --- a/tests/rust/namespace_attr.rs +++ b/tests/rust/namespace_attr.rs @@ -5,21 +5,21 @@ use std::os::raw::c_char; pub extern "C" fn global_function() {} /// A function with a single namespace -#[cbindgen_macro::namespace = "ffi"] +#[cbindgen_macro::namespace("ffi")] #[no_mangle] pub extern "C" fn ffi_function() {} /// A function with nested namespaces using :: separator -#[cbindgen_macro::namespace = "ffi::inner"] +#[cbindgen_macro::namespace("ffi::inner")] #[no_mangle] pub extern "C" fn nested_function(a: *const c_char) {} /// Another function with the same namespace to test grouping -#[cbindgen_macro::namespace = "ffi::inner"] +#[cbindgen_macro::namespace("ffi::inner")] #[no_mangle] pub extern "C" fn another_nested_function() {} /// A function with a different nested namespace -#[cbindgen_macro::namespace = "other::ns"] +#[cbindgen_macro::namespace("other::ns")] #[no_mangle] pub extern "C" fn other_namespace_function() {} diff --git a/tests/rust/namespace_attr_precedence.rs b/tests/rust/namespace_attr_precedence.rs index 55766d83a..0b15b46a9 100644 --- a/tests/rust/namespace_attr_precedence.rs +++ b/tests/rust/namespace_attr_precedence.rs @@ -5,7 +5,7 @@ use std::os::raw::c_char; pub extern "C" fn uses_global_namespace() {} /// A function with per-item namespace - should override global namespace -#[cbindgen_macro::namespace = "ffi::bar"] +#[cbindgen_macro::namespace("ffi::bar")] #[no_mangle] pub extern "C" fn uses_item_namespace(a: *const c_char) {} From b2112303731a430c3581ae9d3e3471c1255b2bef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:22:10 +0000 Subject: [PATCH 6/7] Add per-item C++ namespace support via cbindgen attribute Introduce a cbindgen-macro procedural macro crate and a #[cbindgen_macro::namespace("...")] attribute to specify C++ namespaces on individual extern "C" functions. Extend the internal function model to store optional namespace paths and update the C++ backend to group functions by namespace and emit nested namespace blocks inside the extern "C" region. The new per-item namespace attribute takes precedence over the existing global namespace setting while preserving behavior for code that does not use the attribute. Add integration tests and documentation updates to cover both ffi:bar and ffi::bar style separators, verify precedence with global configuration, and ensure C output remains unchanged. Co-authored-by: tarnishablec <42984497+tarnishablec@users.noreply.github.com> --- Cargo.toml | 1 + cbindgen-macro/Cargo.lock | 47 +++++++++++ cbindgen-macro/Cargo.toml | 14 ++++ cbindgen-macro/src/lib.rs | 59 +++++++++++++ docs.md | 43 ++++++++++ src/bindgen/ir/function.rs | 9 +- src/bindgen/language_backend/clike.rs | 82 ++++++++++++++++++- src/bindgen/utilities.rs | 36 ++++++++ .../expectations-symbols/namespace_attr.c.sym | 7 ++ .../namespace_attr_precedence.c.sym | 5 ++ tests/expectations/namespace_attr.c | 29 +++++++ tests/expectations/namespace_attr.compat.c | 37 +++++++++ tests/expectations/namespace_attr.cpp | 43 ++++++++++ tests/expectations/namespace_attr.pyx | 22 +++++ .../expectations/namespace_attr_precedence.c | 19 +++++ .../namespace_attr_precedence.compat.c | 35 ++++++++ .../namespace_attr_precedence.cpp | 29 +++++++ .../namespace_attr_precedence.pyx | 16 ++++ tests/rust/namespace_attr.rs | 25 ++++++ tests/rust/namespace_attr.toml | 1 + tests/rust/namespace_attr_precedence.rs | 14 ++++ tests/rust/namespace_attr_precedence.toml | 2 + 22 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 cbindgen-macro/Cargo.lock create mode 100644 cbindgen-macro/Cargo.toml create mode 100644 cbindgen-macro/src/lib.rs create mode 100644 tests/expectations-symbols/namespace_attr.c.sym create mode 100644 tests/expectations-symbols/namespace_attr_precedence.c.sym create mode 100644 tests/expectations/namespace_attr.c create mode 100644 tests/expectations/namespace_attr.compat.c create mode 100644 tests/expectations/namespace_attr.cpp create mode 100644 tests/expectations/namespace_attr.pyx create mode 100644 tests/expectations/namespace_attr_precedence.c create mode 100644 tests/expectations/namespace_attr_precedence.compat.c create mode 100644 tests/expectations/namespace_attr_precedence.cpp create mode 100644 tests/expectations/namespace_attr_precedence.pyx create mode 100644 tests/rust/namespace_attr.rs create mode 100644 tests/rust/namespace_attr.toml create mode 100644 tests/rust/namespace_attr_precedence.rs create mode 100644 tests/rust/namespace_attr_precedence.toml diff --git a/Cargo.toml b/Cargo.toml index 1b684aca2..7df4e5ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ path = "src/lib.rs" [workspace] exclude = [ + "cbindgen-macro", "tests/depfile/single_crate_config", "tests/depfile/single_crate_default_config", "tests/depfile/single_crate", diff --git a/cbindgen-macro/Cargo.lock b/cbindgen-macro/Cargo.lock new file mode 100644 index 000000000..81d9de8c8 --- /dev/null +++ b/cbindgen-macro/Cargo.lock @@ -0,0 +1,47 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cbindgen-macro" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" diff --git a/cbindgen-macro/Cargo.toml b/cbindgen-macro/Cargo.toml new file mode 100644 index 000000000..104e68192 --- /dev/null +++ b/cbindgen-macro/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cbindgen-macro" +version = "0.1.0" +edition = "2021" +description = "Procedural macro attributes for cbindgen" +license = "MPL-2.0" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1" +syn = { version = "2.0", features = ["full"] } diff --git a/cbindgen-macro/src/lib.rs b/cbindgen-macro/src/lib.rs new file mode 100644 index 000000000..9aeac11fb --- /dev/null +++ b/cbindgen-macro/src/lib.rs @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Procedural macro attributes for cbindgen. +//! +//! This crate provides the `#[cbindgen_macro::namespace()]` attribute that can be used +//! to specify C++ namespaces for individual functions in generated headers. +//! +//! # Example +//! +//! ```rust,ignore +//! use cbindgen_macro::namespace; +//! +//! #[namespace("ffi::bar")] +//! #[no_mangle] +//! pub extern "C" fn foo() {} +//! ``` +//! +//! The attribute itself is a no-op at compile time - it simply passes through +//! the item unchanged. However, cbindgen parses this attribute from the source +//! code to determine the C++ namespace for the function in the generated header. + +use proc_macro::TokenStream; + +/// Specifies a C++ namespace for a function in cbindgen-generated headers. +/// +/// This attribute is a no-op at compile time but is parsed by cbindgen to +/// determine where to place the function declaration in the generated C++ header. +/// +/// # Example +/// +/// ```rust,ignore +/// #[cbindgen_macro::namespace("ffi::bar")] +/// #[no_mangle] +/// pub extern "C" fn foo() {} +/// ``` +/// +/// This will generate the following C++ code: +/// +/// ```cpp +/// extern "C" { +/// +/// namespace ffi { +/// namespace bar { +/// +/// void foo(); +/// +/// } // namespace bar +/// } // namespace ffi +/// +/// } // extern "C" +/// ``` +#[proc_macro_attribute] +pub fn namespace(_attr: TokenStream, item: TokenStream) -> TokenStream { + // This is a no-op - we just pass through the item unchanged. + // cbindgen parses the attribute directly from the source code. + item +} diff --git a/docs.md b/docs.md index aa071ac62..c2dc72c02 100644 --- a/docs.md +++ b/docs.md @@ -409,6 +409,49 @@ arg: *const T --> const T arg[] arg: *mut T --> T arg[] ``` +### Per-Item Namespace Attribute (C++ only) + +You can use the `#[cbindgen_macro::namespace("...")]` attribute to specify a C++ namespace for individual functions. This is useful when you want different functions to be placed in different namespaces. + +First, add the `cbindgen-macro` crate to your dependencies: + +```toml +[dependencies] +cbindgen-macro = { path = "path/to/cbindgen/cbindgen-macro" } +``` + +Then use the attribute on your functions: + +```rust +#[cbindgen_macro::namespace("ffi::bar")] +#[no_mangle] +pub extern "C" fn foo(a: *const c_char) {} +``` + +This will generate the following C++ code: + +```cpp +extern "C" { + +namespace ffi { +namespace bar { + +void foo(const char *a); + +} // namespace bar +} // namespace ffi + +} // extern "C" +``` + +**Key points:** + +* Use `::` as the namespace separator (e.g., `"ffi::bar"` becomes nested namespaces `namespace ffi { namespace bar { ... } }`) +* This attribute only affects C++ output; it is ignored for C and Cython output +* Functions with the same namespace path will be grouped together in the generated header +* If both a global `namespace` in `cbindgen.toml` and a per-item namespace attribute are specified, the per-item namespace is nested inside the global namespace +* Functions without this attribute will use the global namespace (if configured) or no namespace + ## Generating Swift Bindings In addition to parsing function names in C/C++ header files, the Swift compiler can make use of the `swift_name` attribute on functions to generate more idiomatic names for imported functions and methods. diff --git a/src/bindgen/ir/function.rs b/src/bindgen/ir/function.rs index f8e29c07c..48414b8bd 100644 --- a/src/bindgen/ir/function.rs +++ b/src/bindgen/ir/function.rs @@ -14,7 +14,7 @@ use crate::bindgen::library::Library; use crate::bindgen::monomorph::Monomorphs; use crate::bindgen::rename::{IdentifierType, RenameRule}; use crate::bindgen::reserved; -use crate::bindgen::utilities::IterHelpers; +use crate::bindgen::utilities::{IterHelpers, SynAttributeHelpers}; #[derive(Debug, Clone)] pub struct FunctionArgument { @@ -36,6 +36,9 @@ pub struct Function { pub annotations: AnnotationSet, pub documentation: Documentation, pub never_return: bool, + /// Per-item C++ namespace path from `#[cbindgen::namespace = "..."]` attribute. + /// For example, `["ffi", "bar"]` for `#[cbindgen::namespace = "ffi::bar"]`. + pub namespace: Option>, } impl Function { @@ -65,6 +68,9 @@ impl Function { ret.replace_self_with(self_path); } + // Parse the #[cbindgen::namespace = "..."] attribute + let namespace = attrs.get_cbindgen_namespace(); + Ok(Function { path, self_type_path: self_type_path.cloned(), @@ -75,6 +81,7 @@ impl Function { annotations: AnnotationSet::load(attrs)?, documentation: Documentation::load(attrs), never_return, + namespace, }) } diff --git a/src/bindgen/language_backend/clike.rs b/src/bindgen/language_backend/clike.rs index dba5a10ac..a589262c2 100644 --- a/src/bindgen/language_backend/clike.rs +++ b/src/bindgen/language_backend/clike.rs @@ -1,13 +1,14 @@ 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, Function, GenericParams, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, + ToCondition, Type, Typedef, Union, }; use crate::bindgen::language_backend::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::collections::BTreeMap; use std::io::Write; pub struct CLikeLanguageBackend<'a> { @@ -116,6 +117,81 @@ impl<'a> CLikeLanguageBackend<'a> { self.config.language == Language::C && self.config.style.generate_typedef() } + /// Writes functions, grouping those with per-item namespace attributes into + /// nested namespace blocks (for C++ output only). + fn write_functions_with_namespaces( + &mut self, + out: &mut SourceWriter, + b: &Bindings, + ) where + Self: LanguageBackend, + { + // Only apply per-item namespace grouping for C++ output + if b.config.language != Language::Cxx { + self.write_functions_default(out, b); + return; + } + + // Group functions by their per-item namespace + // Functions without a namespace go to the `None` key + let mut grouped: BTreeMap>, Vec<&Function>> = BTreeMap::new(); + + for function in &b.functions { + if !function.annotations.should_export() { + continue; + } + grouped + .entry(function.namespace.clone()) + .or_default() + .push(function); + } + + // Write functions without per-item namespace first (they use global namespace) + if let Some(functions) = grouped.remove(&None) { + for function in functions { + out.new_line_if_not_start(); + self.write_function(&b.config, out, function); + out.new_line(); + } + } + + // Write functions with per-item namespaces + for (namespace, functions) in grouped { + if let Some(ns_parts) = namespace { + if ns_parts.is_empty() { + // Empty namespace means no namespace wrapping + for function in functions { + out.new_line_if_not_start(); + self.write_function(&b.config, out, function); + out.new_line(); + } + } else { + // Open nested namespaces + out.new_line_if_not_start(); + for ns in &ns_parts { + out.new_line(); + write!(out, "namespace {ns} {{"); + } + out.new_line(); + + // Write functions inside the namespace + for function in functions { + out.new_line_if_not_start(); + self.write_function(&b.config, out, function); + out.new_line(); + } + + // Close nested namespaces in reverse order + for ns in ns_parts.iter().rev() { + out.new_line(); + write!(out, "}} // namespace {ns}"); + } + out.new_line(); + } + } + } + } + fn write_derived_cpp_ops(&mut self, out: &mut SourceWriter, s: &Struct) { let mut wrote_start_newline = false; @@ -990,7 +1066,7 @@ impl LanguageBackend for CLikeLanguageBackend<'_> { // Override default method to close various blocks containing both globals and functions // these blocks are opened in [`write_globals`] that is also overridden if !b.functions.is_empty() || !b.globals.is_empty() { - self.write_functions_default(out, b); + self.write_functions_with_namespaces(out, b); if b.config.cpp_compatible_c() { out.new_line(); diff --git a/src/bindgen/utilities.rs b/src/bindgen/utilities.rs index 8f0763b83..30b03a69a 100644 --- a/src/bindgen/utilities.rs +++ b/src/bindgen/utilities.rs @@ -294,6 +294,42 @@ pub trait SynAttributeHelpers { .next() } + /// Looks up the `#[cbindgen::namespace("...")]` or `#[cbindgen_macro::namespace("...")]` + /// attribute and parses it into a vector of namespace segments. + /// Supports `::` as the separator. + /// For example, `"ffi::bar"` becomes `["ffi", "bar"]`. + fn get_cbindgen_namespace(&self) -> Option> { + for attr in self.attrs() { + // Handle #[cbindgen_macro::namespace("...")] or #[cbindgen::namespace("...")] syntax + if let syn::Meta::List(syn::MetaList { path, tokens, .. }) = &attr.meta { + if path.segments.len() == 2 { + let first = &path.segments[0]; + let second = &path.segments[1]; + let first_ident = first.ident.to_string(); + if (first_ident == "cbindgen" || first_ident == "cbindgen_macro") + && second.ident == "namespace" + { + // Parse the tokens inside the parentheses as a string literal + let tokens_str = tokens.to_string(); + // Remove surrounding quotes if present + let value = tokens_str.trim_matches('"').to_string(); + if value.is_empty() { + return Some(Vec::new()); + } + // Split by "::" separator + let segments: Vec = value + .split("::") + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + return Some(segments); + } + } + } + } + None + } + fn get_comment_lines(&self) -> Vec { let mut comment = Vec::new(); diff --git a/tests/expectations-symbols/namespace_attr.c.sym b/tests/expectations-symbols/namespace_attr.c.sym new file mode 100644 index 000000000..a99e909a4 --- /dev/null +++ b/tests/expectations-symbols/namespace_attr.c.sym @@ -0,0 +1,7 @@ +{ +global_function; +ffi_function; +nested_function; +another_nested_function; +other_namespace_function; +}; \ No newline at end of file diff --git a/tests/expectations-symbols/namespace_attr_precedence.c.sym b/tests/expectations-symbols/namespace_attr_precedence.c.sym new file mode 100644 index 000000000..27afd8679 --- /dev/null +++ b/tests/expectations-symbols/namespace_attr_precedence.c.sym @@ -0,0 +1,5 @@ +{ +uses_global_namespace; +uses_item_namespace; +also_uses_global_namespace; +}; \ No newline at end of file diff --git a/tests/expectations/namespace_attr.c b/tests/expectations/namespace_attr.c new file mode 100644 index 000000000..f41467493 --- /dev/null +++ b/tests/expectations/namespace_attr.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +/** + * A function without namespace attribute - uses global namespace + */ +void global_function(void); + +/** + * A function with a single namespace + */ +void ffi_function(void); + +/** + * A function with nested namespaces using :: separator + */ +void nested_function(const char *a); + +/** + * Another function with the same namespace to test grouping + */ +void another_nested_function(void); + +/** + * A function with a different nested namespace + */ +void other_namespace_function(void); diff --git a/tests/expectations/namespace_attr.compat.c b/tests/expectations/namespace_attr.compat.c new file mode 100644 index 000000000..bbdad1229 --- /dev/null +++ b/tests/expectations/namespace_attr.compat.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * A function without namespace attribute - uses global namespace + */ +void global_function(void); + +/** + * A function with a single namespace + */ +void ffi_function(void); + +/** + * A function with nested namespaces using :: separator + */ +void nested_function(const char *a); + +/** + * Another function with the same namespace to test grouping + */ +void another_nested_function(void); + +/** + * A function with a different nested namespace + */ +void other_namespace_function(void); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/namespace_attr.cpp b/tests/expectations/namespace_attr.cpp new file mode 100644 index 000000000..79502581f --- /dev/null +++ b/tests/expectations/namespace_attr.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +extern "C" { + +/// A function without namespace attribute - uses global namespace +void global_function(); + + +namespace ffi { + +/// A function with a single namespace +void ffi_function(); + +} // namespace ffi + + +namespace ffi { +namespace inner { + +/// A function with nested namespaces using :: separator +void nested_function(const char *a); + +/// Another function with the same namespace to test grouping +void another_nested_function(); + +} // namespace inner +} // namespace ffi + + +namespace other { +namespace ns { + +/// A function with a different nested namespace +void other_namespace_function(); + +} // namespace ns +} // namespace other + +} // extern "C" diff --git a/tests/expectations/namespace_attr.pyx b/tests/expectations/namespace_attr.pyx new file mode 100644 index 000000000..866181c81 --- /dev/null +++ b/tests/expectations/namespace_attr.pyx @@ -0,0 +1,22 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + # A function without namespace attribute - uses global namespace + void global_function(); + + # A function with a single namespace + void ffi_function(); + + # A function with nested namespaces using :: separator + void nested_function(const char *a); + + # Another function with the same namespace to test grouping + void another_nested_function(); + + # A function with a different nested namespace + void other_namespace_function(); diff --git a/tests/expectations/namespace_attr_precedence.c b/tests/expectations/namespace_attr_precedence.c new file mode 100644 index 000000000..0f07d7540 --- /dev/null +++ b/tests/expectations/namespace_attr_precedence.c @@ -0,0 +1,19 @@ +#include +#include +#include +#include + +/** + * A function without namespace attribute - should use global namespace + */ +void uses_global_namespace(void); + +/** + * A function with per-item namespace - should override global namespace + */ +void uses_item_namespace(const char *a); + +/** + * Another function without namespace attribute - should use global namespace + */ +void also_uses_global_namespace(void); diff --git a/tests/expectations/namespace_attr_precedence.compat.c b/tests/expectations/namespace_attr_precedence.compat.c new file mode 100644 index 000000000..beec9f309 --- /dev/null +++ b/tests/expectations/namespace_attr_precedence.compat.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#ifdef __cplusplus +namespace global_ns { +#endif // __cplusplus + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * A function without namespace attribute - should use global namespace + */ +void uses_global_namespace(void); + +/** + * A function with per-item namespace - should override global namespace + */ +void uses_item_namespace(const char *a); + +/** + * Another function without namespace attribute - should use global namespace + */ +void also_uses_global_namespace(void); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#ifdef __cplusplus +} // namespace global_ns +#endif // __cplusplus diff --git a/tests/expectations/namespace_attr_precedence.cpp b/tests/expectations/namespace_attr_precedence.cpp new file mode 100644 index 000000000..7a52ecce5 --- /dev/null +++ b/tests/expectations/namespace_attr_precedence.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +namespace global_ns { + +extern "C" { + +/// A function without namespace attribute - should use global namespace +void uses_global_namespace(); + +/// Another function without namespace attribute - should use global namespace +void also_uses_global_namespace(); + + +namespace ffi { +namespace bar { + +/// A function with per-item namespace - should override global namespace +void uses_item_namespace(const char *a); + +} // namespace bar +} // namespace ffi + +} // extern "C" + +} // namespace global_ns diff --git a/tests/expectations/namespace_attr_precedence.pyx b/tests/expectations/namespace_attr_precedence.pyx new file mode 100644 index 000000000..a820fa2fa --- /dev/null +++ b/tests/expectations/namespace_attr_precedence.pyx @@ -0,0 +1,16 @@ +from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t +cdef extern from *: + ctypedef bint bool + ctypedef struct va_list + +cdef extern from *: + + # A function without namespace attribute - should use global namespace + void uses_global_namespace(); + + # A function with per-item namespace - should override global namespace + void uses_item_namespace(const char *a); + + # Another function without namespace attribute - should use global namespace + void also_uses_global_namespace(); diff --git a/tests/rust/namespace_attr.rs b/tests/rust/namespace_attr.rs new file mode 100644 index 000000000..c2ee1c0fc --- /dev/null +++ b/tests/rust/namespace_attr.rs @@ -0,0 +1,25 @@ +use std::os::raw::c_char; + +/// A function without namespace attribute - uses global namespace +#[no_mangle] +pub extern "C" fn global_function() {} + +/// A function with a single namespace +#[cbindgen_macro::namespace("ffi")] +#[no_mangle] +pub extern "C" fn ffi_function() {} + +/// A function with nested namespaces using :: separator +#[cbindgen_macro::namespace("ffi::inner")] +#[no_mangle] +pub extern "C" fn nested_function(a: *const c_char) {} + +/// Another function with the same namespace to test grouping +#[cbindgen_macro::namespace("ffi::inner")] +#[no_mangle] +pub extern "C" fn another_nested_function() {} + +/// A function with a different nested namespace +#[cbindgen_macro::namespace("other::ns")] +#[no_mangle] +pub extern "C" fn other_namespace_function() {} diff --git a/tests/rust/namespace_attr.toml b/tests/rust/namespace_attr.toml new file mode 100644 index 000000000..9ccd3e39e --- /dev/null +++ b/tests/rust/namespace_attr.toml @@ -0,0 +1 @@ +language = "C++" diff --git a/tests/rust/namespace_attr_precedence.rs b/tests/rust/namespace_attr_precedence.rs new file mode 100644 index 000000000..0b15b46a9 --- /dev/null +++ b/tests/rust/namespace_attr_precedence.rs @@ -0,0 +1,14 @@ +use std::os::raw::c_char; + +/// A function without namespace attribute - should use global namespace +#[no_mangle] +pub extern "C" fn uses_global_namespace() {} + +/// A function with per-item namespace - should override global namespace +#[cbindgen_macro::namespace("ffi::bar")] +#[no_mangle] +pub extern "C" fn uses_item_namespace(a: *const c_char) {} + +/// Another function without namespace attribute - should use global namespace +#[no_mangle] +pub extern "C" fn also_uses_global_namespace() {} diff --git a/tests/rust/namespace_attr_precedence.toml b/tests/rust/namespace_attr_precedence.toml new file mode 100644 index 000000000..072d4a6b6 --- /dev/null +++ b/tests/rust/namespace_attr_precedence.toml @@ -0,0 +1,2 @@ +language = "C++" +namespace = "global_ns" From f3e2b850ab997934986489258a7d5a07fb2fd372 Mon Sep 17 00:00:00 2001 From: tarnishablec Date: Wed, 26 Nov 2025 02:04:03 +0800 Subject: [PATCH 7/7] Add Cargo.lock files for Rust tests --- tests/rust/dep_v2/dep/Cargo.lock | 7 +++++++ tests/rust/expand_dep/dep/Cargo.lock | 7 +++++++ tests/rust/expand_dep_v2/dep/Cargo.lock | 7 +++++++ tests/rust/expand_dep_v2/dep_v2/Cargo.lock | 7 +++++++ tests/rust/rename_crate/dependency/Cargo.lock | 7 +++++++ tests/rust/rename_crate/no_extern/Cargo.lock | 5 +++-- tests/rust/rename_crate/old_dep/Cargo.lock | 9 +++++---- 7 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 tests/rust/dep_v2/dep/Cargo.lock create mode 100644 tests/rust/expand_dep/dep/Cargo.lock create mode 100644 tests/rust/expand_dep_v2/dep/Cargo.lock create mode 100644 tests/rust/expand_dep_v2/dep_v2/Cargo.lock create mode 100644 tests/rust/rename_crate/dependency/Cargo.lock diff --git a/tests/rust/dep_v2/dep/Cargo.lock b/tests/rust/dep_v2/dep/Cargo.lock new file mode 100644 index 000000000..3d317db2d --- /dev/null +++ b/tests/rust/dep_v2/dep/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "dep-2-dep" +version = "0.1.0" diff --git a/tests/rust/expand_dep/dep/Cargo.lock b/tests/rust/expand_dep/dep/Cargo.lock new file mode 100644 index 000000000..62600eea8 --- /dev/null +++ b/tests/rust/expand_dep/dep/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "dep" +version = "0.1.0" diff --git a/tests/rust/expand_dep_v2/dep/Cargo.lock b/tests/rust/expand_dep_v2/dep/Cargo.lock new file mode 100644 index 000000000..b12ea1a62 --- /dev/null +++ b/tests/rust/expand_dep_v2/dep/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "expand-dep-2-dep" +version = "0.1.0" diff --git a/tests/rust/expand_dep_v2/dep_v2/Cargo.lock b/tests/rust/expand_dep_v2/dep_v2/Cargo.lock new file mode 100644 index 000000000..824b0d61a --- /dev/null +++ b/tests/rust/expand_dep_v2/dep_v2/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "expand-dep-2-dep-2" +version = "0.2.0" diff --git a/tests/rust/rename_crate/dependency/Cargo.lock b/tests/rust/rename_crate/dependency/Cargo.lock new file mode 100644 index 000000000..6689b8a83 --- /dev/null +++ b/tests/rust/rename_crate/dependency/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "dependency" +version = "0.1.0" diff --git a/tests/rust/rename_crate/no_extern/Cargo.lock b/tests/rust/rename_crate/no_extern/Cargo.lock index 3f7f59a28..7a75a3b84 100644 --- a/tests/rust/rename_crate/no_extern/Cargo.lock +++ b/tests/rust/rename_crate/no_extern/Cargo.lock @@ -1,6 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 4 + [[package]] -name = "no-extern" +name = "no_extern" version = "0.1.0" - diff --git a/tests/rust/rename_crate/old_dep/Cargo.lock b/tests/rust/rename_crate/old_dep/Cargo.lock index e572f8ffb..79298a683 100644 --- a/tests/rust/rename_crate/old_dep/Cargo.lock +++ b/tests/rust/rename_crate/old_dep/Cargo.lock @@ -1,13 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 4 + [[package]] -name = "no-extern" +name = "no_extern" version = "0.1.0" [[package]] -name = "old-dep-name" +name = "old_dep_name" version = "0.1.0" dependencies = [ - "no-extern 0.1.0", + "no_extern", ] -