From 3cdd7e0f73abca0513c64a3d0a1b87dca7be9524 Mon Sep 17 00:00:00 2001 From: j-walther <184943049+j-walther@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:10:35 +0100 Subject: [PATCH] Add function annotations and fix Clang error --- Cargo.lock | 39 ++++ Cargo.toml | 1 + docs.md | 31 ++- src/bindgen/cdecl.rs | 176 ++++++++++++++++-- src/bindgen/config.rs | 14 ++ src/bindgen/ir/annotation.rs | 128 ++++++++++--- src/bindgen/ir/enumeration.rs | 2 +- src/bindgen/language_backend/clike.rs | 4 +- .../function_annotation.c.sym | 3 + tests/expectations/body.compat.c | 12 +- tests/expectations/function_annotation.c | 12 ++ .../expectations/function_annotation.compat.c | 20 ++ tests/expectations/function_annotation.cpp | 17 ++ tests/expectations/function_annotation.pyx | 14 ++ tests/rust/function_annotation.rs | 7 + tests/rust/function_annotation.toml | 6 + 16 files changed, 431 insertions(+), 55 deletions(-) create mode 100644 tests/expectations-symbols/function_annotation.c.sym create mode 100644 tests/expectations/function_annotation.c create mode 100644 tests/expectations/function_annotation.compat.c create mode 100644 tests/expectations/function_annotation.cpp create mode 100644 tests/expectations/function_annotation.pyx create mode 100644 tests/rust/function_annotation.rs create mode 100644 tests/rust/function_annotation.toml diff --git a/Cargo.lock b/Cargo.lock index 43efb9cb3..22582e10f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.6.15" @@ -74,6 +83,7 @@ dependencies = [ "pretty_assertions", "proc-macro2", "quote", + "regex", "serde", "serde_json", "serial_test", @@ -302,6 +312,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rustix" version = "0.38.34" diff --git a/Cargo.toml b/Cargo.toml index a0278d09a..5e92c6063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ toml = "0.8.8" proc-macro2 = "1.0.60" quote = "1" heck = "0.4" +regex = "1.11.1" [dependencies.syn] version = "2.0.85" diff --git a/docs.md b/docs.md index 3d00d814d..0b3fbabd0 100644 --- a/docs.md +++ b/docs.md @@ -272,7 +272,7 @@ While output configuration is primarily done through the cbindgen.toml, in some pub struct Point(pub f32, pub f32); ``` -An annotation may be a bool, string (no quotes), or list of strings. If just the annotation's name is provided, `=true` is assumed. The annotation parser is currently fairly naive and lacks any capacity for escaping, so don't try to make any strings with `=`, `,`, `[` or `]`. +An annotation may be a bool, string (no quotes), or list of strings. If just the annotation's name is provided, `=true` is assumed. The annotation parser is somewhat naive. `,`, `]` and any opening brackets (`[`) after the initial one in lists can be escaped with a backslash. In other annotations values, like string, dict or bool those special character can not currently be escaped. `=` can not currently be escaped at all. Most annotations are just local overrides for identical settings in the cbindgen.toml, but a few are unique because they don't make sense in a global context. The set of supported annotation are as follows: @@ -306,6 +306,35 @@ pub struct Foo { .. }; // This won't be emitted by cbindgen in the header fn bar() -> Foo { .. } // Will be emitted as `struct foo bar();` ``` +### Function annotations + +It's sometimes useful to add attributes or macros to functions in C and C++. This can be done by specifying one of the following annotations: + +* function-arg-prefix\[arg\] +* function-prefix +* function-postfix +* function-ident-prefix + +The following Rust code: + +```rust +#[no_mangle] +/// cbindgen:function-prefix=PREFIX_MACRO +/// cbindgen:function-postfix=POSTFIX_MACRO +/// cbindgen:function-ident-prefix=IDENT_PREFIX_MACRO +/// cbindgen:function-arg-ident-prefix[input]=_Nonnull +/// cbindgen:function-arg-prefix[input]=_In_ +pub extern "C" fn root(input: *const u64, input_size: u64) {} +``` + +will generate the following C code: + +```c +PREFIX_MACRO void IDENT_PREFIX_MACRO root(_In_ const uint64_t *_Nonnull input, uint64_t input_size) POSTFIX_MACRO; +``` + +These annotations are not supported and will be ignored when using Cython. + ### Struct Annotations * field-names=\[field1, field2, ...\] -- sets the names of all the fields in the output struct. These names will be output verbatim, and are not eligible for renaming. diff --git a/src/bindgen/cdecl.rs b/src/bindgen/cdecl.rs index 67d797fc3..68dcdd0e9 100644 --- a/src/bindgen/cdecl.rs +++ b/src/bindgen/cdecl.rs @@ -11,10 +11,77 @@ use crate::bindgen::language_backend::LanguageBackend; use crate::bindgen::writer::{ListType, SourceWriter}; use crate::bindgen::{Config, Language}; +use super::ir::AnnotationValue; + +/// Annotation that allows specifying a prefix for function arguments. +/// It can be used like this: +/// ```rust +/// /// cbindgen:function-arg-prefix[bar]=_In_ +/// fn foo(bar: *const u64) {} +/// ``` +/// +/// This will generate the following code: +/// ```c +/// void root(_In_ uint64_t* input); +/// ``` +pub const ANNOTATION_FUNCTION_ARG_PREFIX: &str = "function-arg-prefix"; + +/// Annotation that allows specifying a prefix for function arguments. +/// It can be used like this: +/// ```rust +/// /// cbindgen:function-arg-ident-prefix[bar]=_NonNull +/// fn foo(bar: *const u64) {} +/// ``` +/// +/// This will generate the following code: +/// ```c +/// void root(uint64_t* _NonNull input); +/// ``` +pub const ANNOTATION_FUNCTION_ARG_IDENT_PREFIX: &str = "function-arg-ident-prefix"; + +/// Annotation that allows specifying a prefix for function declarations. +/// It can be used like this: +/// ```rust +/// /// cbindgen:function-prefix=TEST_MACRO +/// fn root(input: *const u64) {} +/// ``` +/// +/// This will generate the following code: +/// ```c +/// TEST_MACRO void root(uint64_t* input); +/// ``` +pub const ANNOTATION_FUNCTION_PREFIX: &str = "function-prefix"; + +/// Annotation that allows specifying a prefix for function declarations. +/// It can be used like this: +/// ```rust +/// /// cbindgen:function-postfix=TEST_MACRO +/// fn root(input: *const u64) {} +/// ``` +/// +/// This will generate the following code: +/// ```c +/// void root(uint64_t* input) TEST_MACRO; +/// ``` +pub const ANNOTATION_FUNCTION_POSTFIX: &str = "function-postfix"; + +/// Annotation that allows specifying a prefix for function declarations. +/// It can be used like this: +/// ```rust +/// /// cbindgen:function-ident-prefix=TEST_MACRO +/// fn root(input: *const u64) {} +/// ``` +/// +/// This will generate the following code: +/// ```c +/// void TEST_MACRO root(uint64_t* input) ; +/// ``` +pub const ANNOTATION_FUNCTION_IDENT_PREFIX: &str = "function-ident-prefix"; + // This code is for translating Rust types into C declarations. // See Section 6.7, Declarations, in the C standard for background. // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf - +#[derive(Debug)] enum CDeclarator { Ptr { is_const: bool, @@ -23,9 +90,10 @@ enum CDeclarator { }, Array(String), Func { - args: Vec<(Option, CDecl)>, + args: Vec, layout: Layout, never_return: bool, + postfix: Option, }, } @@ -35,6 +103,13 @@ impl CDeclarator { } } +#[derive(Debug)] +struct CFuncArg { + ident: Option, + r#type: CDecl, +} + +#[derive(Debug)] struct CDecl { type_qualifers: String, type_name: String, @@ -42,6 +117,11 @@ struct CDecl { declarators: Vec, type_ctype: Option, deprecated: Option, + // Prefix that should be added before the declaration + prefix: Option, + // Prefix that should be added before the + // identifier but not as a part of the identifier + ident_prefix: Option, } impl CDecl { @@ -53,6 +133,8 @@ impl CDecl { declarators: Vec::new(), type_ctype: None, deprecated: None, + prefix: None, + ident_prefix: None, } } @@ -87,21 +169,60 @@ impl CDecl { } fn build_func(&mut self, f: &Function, layout: Layout, config: &Config) { + let arg_ident_prefixes = f + .annotations + .dict(ANNOTATION_FUNCTION_ARG_IDENT_PREFIX) + .unwrap_or_default(); + let arg_prefixes = f + .annotations + .dict(ANNOTATION_FUNCTION_ARG_PREFIX) + .unwrap_or_default(); + let args = f .args .iter() .map(|arg| { - ( - arg.name.clone(), - CDecl::from_func_arg(&arg.ty, arg.array_length.as_deref(), config), - ) + let ident_prefix = arg + .name + .as_ref() + .and_then(|name| arg_ident_prefixes.get(name).cloned()); + let ident_prefix = match ident_prefix { + Some(AnnotationValue::Atom(prefix)) => prefix, + _ => None, + }; + + let prefix = arg + .name + .as_ref() + .and_then(|name| arg_prefixes.get(name).cloned()); + + let prefix = match prefix { + Some(AnnotationValue::Atom(prefix)) => prefix, + _ => None, + }; + + let mut arg = CFuncArg { + ident: arg.name.clone(), + r#type: CDecl::from_func_arg(&arg.ty, arg.array_length.as_deref(), config), + }; + + arg.r#type.ident_prefix = ident_prefix; + arg.r#type.prefix = prefix; + arg }) .collect(); + self.declarators.push(CDeclarator::Func { args, layout, never_return: f.never_return, + postfix: f.annotations.atom(ANNOTATION_FUNCTION_POSTFIX).flatten(), }); + self.ident_prefix = f + .annotations + .atom(ANNOTATION_FUNCTION_IDENT_PREFIX) + .flatten(); + self.prefix = f.annotations.atom(ANNOTATION_FUNCTION_PREFIX).flatten(); self.deprecated.clone_from(&f.annotations.deprecated); self.build_type(&f.ret, false, config); } @@ -175,7 +296,10 @@ impl CDecl { } => { let args = args .iter() - .map(|(ref name, ref ty)| (name.clone(), CDecl::from_type(ty, config))) + .map(|(ref name, ref ty)| CFuncArg { + ident: name.clone(), + r#type: CDecl::from_type(ty, config), + }) .collect(); self.declarators.push(CDeclarator::Ptr { is_const: false, @@ -186,6 +310,7 @@ impl CDecl { args, layout: config.function.args, never_return: *never_return, + postfix: None, }); self.build_type(ret, false, config); } @@ -199,6 +324,12 @@ impl CDecl { ident: Option<&str>, config: &Config, ) { + if config.language != Language::Cython { + if let Some(prefix) = &self.prefix { + write!(out, "{} ", prefix); + } + } + // Write the type-specifier and type-qualifier first if !self.type_qualifers.is_empty() { write!(out, "{} ", self.type_qualifers); @@ -268,6 +399,12 @@ impl CDecl { // Write the identifier if let Some(ident) = ident { + if config.language != Language::Cython { + if let Some(prefix) = &self.ident_prefix { + write!(out, "{} ", prefix); + } + } + write!(out, "{}", ident); } @@ -293,11 +430,12 @@ impl CDecl { ref args, ref layout, never_return, + ref postfix, + .. } => { if last_was_pointer { out.write(")"); } - out.write("("); if args.is_empty() && config.language == Language::C { out.write("void"); @@ -307,20 +445,20 @@ impl CDecl { language_backend: &mut LB, out: &mut SourceWriter, config: &Config, - args: &[(Option, CDecl)], + args: &[CFuncArg], ) { let align_length = out.line_length_for_align(); out.push_set_spaces(align_length); - for (i, (arg_ident, arg_ty)) in args.iter().enumerate() { + for (i, arg) in args.iter().enumerate() { if i != 0 { out.write(","); out.new_line(); } // Convert &Option to Option<&str> - let arg_ident = arg_ident.as_ref().map(|x| x.as_ref()); + let arg_ident = arg.ident.as_ref().map(|x| x.as_ref()); - arg_ty.write(language_backend, out, arg_ident, config); + arg.r#type.write(language_backend, out, arg_ident, config); } out.pop_tab(); } @@ -329,17 +467,17 @@ impl CDecl { language_backend: &mut LB, out: &mut SourceWriter, config: &Config, - args: &[(Option, CDecl)], + args: &[CFuncArg], ) { - for (i, (arg_ident, arg_ty)) in args.iter().enumerate() { + for (i, arg) in args.iter().enumerate() { if i != 0 { out.write(", "); } // Convert &Option to Option<&str> - let arg_ident = arg_ident.as_ref().map(|x| x.as_ref()); + let arg_ident = arg.ident.as_ref().map(|x| x.as_ref()); - arg_ty.write(language_backend, out, arg_ident, config); + arg.r#type.write(language_backend, out, arg_ident, config); } } @@ -363,6 +501,12 @@ impl CDecl { } } + if config.language != Language::Cython { + if let Some(attr) = postfix { + write!(out, " {}", attr); + } + } + last_was_pointer = true; } } diff --git a/src/bindgen/config.rs b/src/bindgen/config.rs index 31316503d..1f79afe98 100644 --- a/src/bindgen/config.rs +++ b/src/bindgen/config.rs @@ -1104,6 +1104,20 @@ impl Config { } } + pub(crate) fn should_generate_tag(&self, path: &Path) -> bool { + // We're compiling with `-Wnon-c-typedef-for-linkage` so if we're building + // C++ bindings with C compatibility the user might insert code pre- or post-body + // that contains a function declaration. We need to generate a tag in this case or + // the code wouldn't compile. + // This will override the users preferences for this particular path. + let pre_body = self.export.pre_body(path).is_some(); + let post_body = self.export.post_body(path).is_some(); + + let force_tags = self.cpp_compatible_c() && (pre_body || post_body); + + self.style.generate_tag() || force_tags + } + pub fn from_file>(file_name: P) -> Result { let config_text = fs::read_to_string(file_name.as_ref()).map_err(|_| { format!( diff --git a/src/bindgen/ir/annotation.rs b/src/bindgen/ir/annotation.rs index 5e66cc528..3b7bdbcab 100644 --- a/src/bindgen/ir/annotation.rs +++ b/src/bindgen/ir/annotation.rs @@ -9,6 +9,7 @@ use std::str::FromStr; use crate::bindgen::config::{Config, Language}; use crate::bindgen::utilities::SynAttributeHelpers; +use regex::Regex; // A system for specifying properties on items. Annotations are // given through document comments and parsed by this code. @@ -29,6 +30,7 @@ pub enum AnnotationValue { List(Vec), Atom(Option), Bool(bool), + Dict(HashMap), } /// A set of annotations specified by a document comment. @@ -113,6 +115,10 @@ impl AnnotationSet { let deprecated = attrs.find_deprecated_note(); let mut annotations = HashMap::new(); + // Regex to extract the index name from an annotation + let annotation_name_regex = Regex::new(r"(?m)([a-zA-Z0-9_-]+)(\[([a-zA-Z0-9_-]+)\])?") + .expect("Failed to build annotation regex!"); + // Look at each line for an annotation for line in lines { debug_assert!(line.starts_with("cbindgen:")); @@ -127,34 +133,53 @@ impl AnnotationSet { return Err(format!("Couldn't parse {}.", line)); } + let captures = annotation_name_regex.captures(parts[0]).ok_or_else(|| { + format!("Couldn't parse annotation {:?} in line {}", parts[0], line) + })?; + // Grab the name that this annotation is modifying - let name = parts[0]; + let name = captures + .get(1) + .ok_or_else(|| { + format!("Couldn't parse annotation {:?} in line {}", parts[0], line) + })? + .as_str(); - // If the annotation only has a name, assume it's setting a bool flag - if parts.len() == 1 { - annotations.insert(name.to_string(), AnnotationValue::Bool(true)); - continue; - } + // Check if this annotation is a dictionary + let index = captures.get(3).map(|capture| capture.as_str()); // Parse the value we're setting the name to - let value = parts[1]; + let value = if parts.len() == 1 { + // If the annotation only has a name, assume it's setting a bool flag + AnnotationValue::Bool(true) + } else { + parse_value(parts[1]) + }; - if let Some(x) = parse_list(value) { - annotations.insert(name.to_string(), AnnotationValue::List(x)); - continue; - } - if let Ok(x) = value.parse::() { - annotations.insert(name.to_string(), AnnotationValue::Bool(x)); - continue; + match index { + Some(index) => { + // Create a new dictionary entry if it doesn't exist + let entry = annotations + .entry(name.to_string()) + .or_insert(AnnotationValue::Dict(HashMap::new())); + + match entry { + AnnotationValue::Dict(ref mut dict) => { + dict.insert(index.to_string(), value); + } + _ => { + // This is here so a mistyped cbindgen:foo[bar]=baz doesn't silently discard all previous dictionary entries. + return Err(format!( + "Attempted to change type of annotation {} in line {}", + name, line + )); + } + } + } + None => { + annotations.insert(name.to_string(), value); + } } - annotations.insert( - name.to_string(), - if value.is_empty() { - AnnotationValue::Atom(None) - } else { - AnnotationValue::Atom(Some(value.to_string())) - }, - ); } Ok(AnnotationSet { @@ -190,6 +215,13 @@ impl AnnotationSet { } } + pub fn dict(&self, name: &str) -> Option> { + match self.annotations.get(name) { + Some(AnnotationValue::Dict(x)) => Some(x.clone()), + _ => None, + } + } + pub fn parse_atom(&self, name: &str) -> Option where T: Default + FromStr, @@ -204,19 +236,57 @@ impl AnnotationSet { } } +/// Parse a value into an annotation value. +fn parse_value(value: &str) -> AnnotationValue { + if let Some(x) = parse_list(value) { + return AnnotationValue::List(x); + } + if let Ok(x) = value.parse::() { + return AnnotationValue::Bool(x); + } + if value.is_empty() { + return AnnotationValue::Atom(None); + } + AnnotationValue::Atom(Some(value.to_string())) +} + /// Parse lists like "[x, y, z]". This is not implemented efficiently or well. fn parse_list(list: &str) -> Option> { + // Remove leading and trailing whitespace + let list = list.trim(); + + // Ensure that the list is at least 2 characters long if list.len() < 2 { return None; } + // Ensure that the list starts and ends with brackets match (list.chars().next(), list.chars().last()) { - (Some('['), Some(']')) => Some( - list[1..list.len() - 1] - .split(',') - .map(|x| x.trim().to_string()) - .collect(), - ), - _ => None, + (Some('['), Some(']')) => {} + _ => return None, + } + + let mut items = Vec::new(); + let mut current = String::new(); + let mut escape = false; + + for c in list[1..list.len() - 1].chars() { + if escape { + current.push(c); + escape = false; + } else if c == '\\' { + escape = true; + } else if c == ',' { + items.push(current.trim().to_string()); + current.clear(); + } else { + current.push(c); + } } + + if !current.is_empty() { + items.push(current.trim().to_string()); + } + + Some(items) } diff --git a/src/bindgen/ir/enumeration.rs b/src/bindgen/ir/enumeration.rs index 8927b8be5..283d69c49 100644 --- a/src/bindgen/ir/enumeration.rs +++ b/src/bindgen/ir/enumeration.rs @@ -803,7 +803,7 @@ impl Enum { write!(out, " {} ", note); } - if config.language != Language::C || config.style.generate_tag() { + if config.language != Language::C || config.should_generate_tag(&self.path) { write!(out, " {}", self.export_name()); } diff --git a/src/bindgen/language_backend/clike.rs b/src/bindgen/language_backend/clike.rs index b41a3c462..c902ead3d 100644 --- a/src/bindgen/language_backend/clike.rs +++ b/src/bindgen/language_backend/clike.rs @@ -590,7 +590,7 @@ impl LanguageBackend for CLikeLanguageBackend<'_> { write!(out, " {}", note); } - if self.config.language != Language::C || self.config.style.generate_tag() { + if self.config.language != Language::C || self.config.should_generate_tag(&s.path) { write!(out, " {}", s.export_name()); } @@ -675,7 +675,7 @@ impl LanguageBackend for CLikeLanguageBackend<'_> { } } - if self.config.language != Language::C || self.config.style.generate_tag() { + if self.config.language != Language::C || self.config.should_generate_tag(&u.path) { write!(out, " {}", u.export_name); } diff --git a/tests/expectations-symbols/function_annotation.c.sym b/tests/expectations-symbols/function_annotation.c.sym new file mode 100644 index 000000000..f018156fc --- /dev/null +++ b/tests/expectations-symbols/function_annotation.c.sym @@ -0,0 +1,3 @@ +{ +root; +}; \ No newline at end of file diff --git a/tests/expectations/body.compat.c b/tests/expectations/body.compat.c index f61ccd0e5..252535254 100644 --- a/tests/expectations/body.compat.c +++ b/tests/expectations/body.compat.c @@ -15,7 +15,7 @@ typedef enum { Baz1_Prepended, } MyCLikeEnum_Prepended; -typedef struct { +typedef struct MyFancyStruct { int32_t i; #ifdef __cplusplus inline void foo(); @@ -28,7 +28,7 @@ typedef enum { Baz, } MyFancyEnum_Tag; -typedef struct { +typedef struct MyFancyEnum { MyFancyEnum_Tag tag; union { struct { @@ -43,13 +43,13 @@ typedef struct { #endif } MyFancyEnum; -typedef union { +typedef union MyUnion { float f; uint32_t u; int32_t extra_member; } MyUnion; -typedef struct { +typedef struct MyFancyStruct_Prepended { #ifdef __cplusplus inline void prepended_wohoo(); #endif @@ -62,7 +62,7 @@ typedef enum { Baz_Prepended, } MyFancyEnum_Prepended_Tag; -typedef struct { +typedef struct MyFancyEnum_Prepended { #ifdef __cplusplus inline void wohoo(); #endif @@ -77,7 +77,7 @@ typedef struct { }; } MyFancyEnum_Prepended; -typedef union { +typedef union MyUnion_Prepended { int32_t extra_member; float f; uint32_t u; diff --git a/tests/expectations/function_annotation.c b/tests/expectations/function_annotation.c new file mode 100644 index 000000000..1d2573cac --- /dev/null +++ b/tests/expectations/function_annotation.c @@ -0,0 +1,12 @@ +#include +#include +#include +#include +#define TEST_MACRO +#ifndef _In_ +#define _In_ +#endif + + +TEST_MACRO void TEST_MACRO root(_In_ const uint64_t *_Nonnull input, + uint64_t input_size) TEST_MACRO; diff --git a/tests/expectations/function_annotation.compat.c b/tests/expectations/function_annotation.compat.c new file mode 100644 index 000000000..c32ae0d99 --- /dev/null +++ b/tests/expectations/function_annotation.compat.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#define TEST_MACRO +#ifndef _In_ +#define _In_ +#endif + + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +TEST_MACRO void TEST_MACRO root(_In_ const uint64_t *_Nonnull input, + uint64_t input_size) TEST_MACRO; + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/function_annotation.cpp b/tests/expectations/function_annotation.cpp new file mode 100644 index 000000000..4398f2289 --- /dev/null +++ b/tests/expectations/function_annotation.cpp @@ -0,0 +1,17 @@ +#include +#include +#include +#include +#include +#define TEST_MACRO +#ifndef _In_ +#define _In_ +#endif + + +extern "C" { + +TEST_MACRO void TEST_MACRO root(_In_ const uint64_t *_Nonnull input, + uint64_t input_size) TEST_MACRO; + +} // extern "C" diff --git a/tests/expectations/function_annotation.pyx b/tests/expectations/function_annotation.pyx new file mode 100644 index 000000000..9e4a3d947 --- /dev/null +++ b/tests/expectations/function_annotation.pyx @@ -0,0 +1,14 @@ +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 +#define TEST_MACRO +#ifndef _In_ +#define _In_ +#endif + + +cdef extern from *: + + void root(const uint64_t *input, uint64_t input_size); diff --git a/tests/rust/function_annotation.rs b/tests/rust/function_annotation.rs new file mode 100644 index 000000000..51e35b564 --- /dev/null +++ b/tests/rust/function_annotation.rs @@ -0,0 +1,7 @@ +#[no_mangle] +/// cbindgen:function-prefix=TEST_MACRO +/// cbindgen:function-postfix=TEST_MACRO +/// cbindgen:function-ident-prefix=TEST_MACRO +/// cbindgen:function-arg-ident-prefix[input]=_Nonnull +/// cbindgen:function-arg-prefix[input]=_In_ +pub extern "C" fn root(input: *const u64, input_size: u64) {} diff --git a/tests/rust/function_annotation.toml b/tests/rust/function_annotation.toml new file mode 100644 index 000000000..729615d1b --- /dev/null +++ b/tests/rust/function_annotation.toml @@ -0,0 +1,6 @@ +after_includes = """ +#define TEST_MACRO +#ifndef _In_ +#define _In_ +#endif +""" \ No newline at end of file