From 5f3b4527117e1ba3f26ecd3e281732e813053933 Mon Sep 17 00:00:00 2001 From: Govind Yadav Date: Fri, 9 Oct 2026 15:46:12 +0530 Subject: [PATCH 1/2] Add opt-in C23 constexpr scalar constants --- docs.md | 28 +++++++ src/bindgen/config.rs | 4 + src/bindgen/ir/constant.rs | 101 ++++++++++++++++++++++ tests/constant_c23.rs | 162 ++++++++++++++++++++++++++++++++++++ tests/constant_c23/input.rs | 55 ++++++++++++ 5 files changed, 350 insertions(+) create mode 100644 tests/constant_c23.rs create mode 100644 tests/constant_c23/input.rs diff --git a/docs.md b/docs.md index aa071ac62..5a9668b6d 100644 --- a/docs.md +++ b/docs.md @@ -1033,6 +1033,34 @@ allow_static_const = true # default: true allow_constexpr = false +# Whether supported primitive constants should be typed constexpr objects in C. +# Requires a C23 compiler. Independent of the C++ allow_constexpr and +# allow_static_const settings; does not affect C++ or Cython output. +# +# Supports integer, floating-point, boolean and character constants whose +# initializers are self-contained literals, supported primitive casts and +# unary/bitwise expressions (including standard integer MAX and signed MIN +# constants). Unsigned MIN and literals outside the generated C integer range +# retain their existing macro output. +# Boolean negation and character literals rendered with Rust Unicode escapes +# also remain macros rather than promoting unsupported C literal forms. +# Explicit conversions to the declared type preserve +# narrowing semantics under C23's exact-representability requirement. +# +# Other constants remain #define macros: pointers, arrays, aggregates, aliases, +# references to user constants, field accesses, and floating-point expressions +# or casts used to initialize integers, binary arithmetic/shifts, and negation +# of compound expressions. +# Integer arithmetic needs type inference to avoid C signed-intermediate overflow. +# These require additional type/dependency +# analysis to satisfy C23's stricter constant-expression rules. +# +# Unlike macros, constexpr objects cannot be used in preprocessor #if/#ifdef +# expressions. Enable this only when consumers use these constants in C code. +# +# default: false +allow_constexpr_in_c = false + # This rule specifies the order in which constants will be sorted. # # "Name": sort by the name of the constant diff --git a/src/bindgen/config.rs b/src/bindgen/config.rs index acffa3b2f..a5fb4e761 100644 --- a/src/bindgen/config.rs +++ b/src/bindgen/config.rs @@ -680,6 +680,9 @@ pub struct ConstantConfig { pub allow_static_const: bool, /// Whether a generated constant should be constexpr in C++ mode. pub allow_constexpr: bool, + /// Whether supported primitive constants should be constexpr in C23 mode. + /// This is independent of `allow_constexpr` and defaults to false. + pub allow_constexpr_in_c: bool, /// Sort key for constants pub sort_by: Option, } @@ -689,6 +692,7 @@ impl Default for ConstantConfig { ConstantConfig { allow_static_const: true, allow_constexpr: true, + allow_constexpr_in_c: false, sort_by: None, } } diff --git a/src/bindgen/ir/constant.rs b/src/bindgen/ir/constant.rs index 3e0416618..1777b3a58 100644 --- a/src/bindgen/ir/constant.rs +++ b/src/bindgen/ir/constant.rs @@ -356,6 +356,84 @@ impl Literal { !self.has_pointer_casts() } + // C23 integer constant expressions are more restrictive than C++ constexpr + // initializers: in particular, casting floating arithmetic to an integer is + // not sufficient. Keep those expressions as macros rather than guessing + // their value or changing the shared C/C++ literal representation. + fn can_be_c_constexpr(&self, allow_float: bool) -> bool { + match self { + Literal::Expr(value) => { + (value.starts_with('\'') && !value.contains("\\u{")) + || value.starts_with("U'") + || matches!(value.as_str(), "true" | "false") + || (value + .bytes() + .all(|c| c.is_ascii_digit() || c == b'u' || c == b'l') + && value + .trim_end_matches(['u', 'l']) + .parse::() + .is_ok_and(|number| value.contains('u') || number <= i64::MAX as u64)) + || (allow_float + && (value.contains('.') || value.contains('e') || value.contains('E')) + && value.parse::().is_ok()) + } + Literal::Path { + associated_to: Some((path, _)), + name, + } => { + // stdint.h has signed MIN bounds, but no UINTn_MIN macros. + !(name == "MIN" + && matches!( + PrimitiveType::maybe(path.name()), + Some(PrimitiveType::Integer { signed: false, .. }) + )) + && to_known_assoc_constant(path, name).is_some() + } + Literal::PostfixUnaryOp { op, value } => { + // Negating an inferred C int intermediate can overflow even + // when the wider Rust destination makes the expression valid. + (*op != "-" || matches!(**value, Literal::Expr(_))) + && (*op != "~" + || value.visit(&mut |literal| { + !matches!(literal, Literal::Expr(expr) if matches!(expr.as_str(), "true" | "false")) + && !matches!(literal, Literal::Cast { ty: Type::Primitive(PrimitiveType::Bool), .. }) + && !matches!(literal, Literal::BinOp { op, .. } if matches!(*op, "==" | "!=" | "<" | "<=" | ">" | ">=" | "&&" | "||")) + })) + && value.can_be_c_constexpr(allow_float) + } + Literal::BinOp { left, op, right } => { + // Unsuffixed Rust literals inherit their integer type, whereas + // C evaluates them as int before our final conversion. Arithmetic + // and shifts can therefore overflow even for valid Rust constants. + matches!( + *op, + "&" | "|" | "^" | "&&" | "||" | "==" | "!=" | "<" | "<=" | ">" | ">=" + ) && left.can_be_c_constexpr(false) + && right.can_be_c_constexpr(false) + } + Literal::Cast { ty, value } => { + let supported_type = match ty { + Type::Primitive(PrimitiveType::Void | PrimitiveType::VaList) => false, + Type::Primitive(PrimitiveType::Float | PrimitiveType::Double) => allow_float, + Type::Primitive(_) => true, + _ => false, + }; + let cast_allows_float = matches!( + ty, + Type::Primitive(PrimitiveType::Float | PrimitiveType::Double) + ); + supported_type && value.can_be_c_constexpr(cast_allows_float) + } + // Constants can be sorted by name, so a reference is not necessarily + // declared yet. Aggregates also need member-wise type checking and + // may contain non-null pointers, which C23 constexpr forbids. + Literal::Path { .. } + | Literal::FieldAccess { .. } + | Literal::Struct { .. } + | Literal::Array { .. } => false, + } + } + fn visit(&self, visitor: &mut impl FnMut(&Self) -> bool) -> bool { if !visitor(self) { return false; @@ -814,6 +892,17 @@ impl Item for Constant { } impl Constant { + fn can_be_c_constexpr(&self) -> bool { + match self.ty { + Type::Primitive(PrimitiveType::Void | PrimitiveType::VaList) => false, + Type::Primitive(PrimitiveType::Float | PrimitiveType::Double) => { + self.value.can_be_c_constexpr(true) + } + Type::Primitive(_) => self.value.can_be_c_constexpr(false), + _ => false, + } + } + pub fn write_declaration( &self, config: &Config, @@ -920,6 +1009,18 @@ impl Constant { let allow_constexpr = config.constant.allow_constexpr && self.value.can_be_constexpr(); match config.language { + Language::C if config.constant.allow_constexpr_in_c && self.can_be_c_constexpr() => { + out.write("constexpr static const "); + crate::bindgen::cdecl::write_field(language_backend, out, &self.ty, &name, config); + out.write(" = ("); + language_backend.write_type(out, &self.ty); + out.write(")("); + // C23 requires exact representability. The explicit conversion + // performs the narrowing normally applied by initialization, + // e.g. for f32 literals or integer-promoted bitwise expressions. + language_backend.write_literal(out, value); + out.write(");"); + } Language::Cxx if config.constant.allow_static_const || allow_constexpr => { if allow_constexpr { out.write("constexpr ") diff --git a/tests/constant_c23.rs b/tests/constant_c23.rs new file mode 100644 index 000000000..1a4e709cd --- /dev/null +++ b/tests/constant_c23.rs @@ -0,0 +1,162 @@ +use std::env; +use std::fs; +use std::path::Path; +use std::process::Command; + +use cbindgen::{Builder, Config, Language, SortKey}; + +fn generate(config: &str, language: Language) -> String { + let mut config: Config = toml::from_str(config).unwrap(); + config.language = language; + config.sort_by = SortKey::Name; + let bindings = Builder::new() + .with_src(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/constant_c23/input.rs")) + .with_config(config) + .generate() + .unwrap(); + let mut output = Vec::new(); + bindings.write(&mut output); + String::from_utf8(output).unwrap() +} + +const ENABLED: &str = "[const]\nallow_constexpr_in_c = true\n"; + +#[test] +fn c_constexpr_is_opt_in() { + let default = generate("", Language::C); + assert!(default.contains("#define SMALL 255")); + assert!(!default.contains("constexpr")); + assert_eq!( + default, + generate("[const]\nallow_constexpr_in_c = false\n", Language::C) + ); +} + +#[test] +fn c_constexpr_emits_typed_scalar_constants() { + let output = generate(ENABLED, Language::C); + for declaration in [ + "constexpr static const uint8_t SMALL = (uint8_t)(255);", + "constexpr static const uint64_t LARGE = (uint64_t)(18446744073709551615ull);", + "constexpr static const int32_t NEGATIVE = (int32_t)(-42);", + "constexpr static const uint32_t MASK = (uint32_t)(~0u);", + "constexpr static const uint32_t BITWISE = (uint32_t)((5 | 2));", + "constexpr static const bool ENABLED = (bool)(true);", + "constexpr static const uint32_t CHARACTER = (uint32_t)('x');", + "constexpr static const float FLOAT = (float)(0.1);", + "constexpr static const double DOUBLE = (double)(0.3333333333333333);", + "constexpr static const double BIG_FLOAT_SIGNED = (double)(9223372036854775808ull);", + "constexpr static const uint32_t BOUND = (uint32_t)(UINT32_MAX);", + "constexpr static const uint32_t Aggregate_ASSOCIATED = (uint32_t)(6);", + ] { + assert!( + output.contains(declaration), + "Missing {declaration}\n{output}" + ); + } +} + +#[test] +fn c_constexpr_preserves_macro_fallbacks() { + let output = generate(ENABLED, Language::C); + for name in [ + "A_REFERENCE", + "ARITHMETIC", + "SHIFT", + "SUM", + "FLOAT_DIVISION", + "NESTED_NEGATION", + "SATURATING_CAST", + "MIN8", + "MIN16", + "MIN32", + "MIN64", + "BIG_FLOAT", + "SIGNED_MIN_LITERAL", + "LATIN", + "OFF", + "OFF_COMPARE", + "FLOAT_TO_INTEGER", + "DOUBLE_CAST", + "POINTER", + "NULL_POINTER", + "ARRAY", + "STRING", + "AGGREGATE", + "FIELD", + "ALIAS", + ] { + assert!( + output.contains(&format!("#define {name} ")), + "{name}\n{output}" + ); + } + assert!(output.find("#define A_REFERENCE").unwrap() < output.find(" Z_VALUE =").unwrap()); +} + +#[test] +fn c_constexpr_does_not_change_other_languages() { + for language in [Language::Cxx, Language::Cython] { + assert_eq!(generate("", language), generate(ENABLED, language)); + assert_eq!( + generate("[const]\nallow_constexpr = false\n", language), + generate(&format!("{ENABLED}allow_constexpr = false\n"), language) + ); + } +} + +#[test] +fn c_constexpr_is_independent_of_cpp_constexpr() { + assert_eq!( + generate(ENABLED, Language::C), + generate(&format!("{ENABLED}allow_constexpr = false\n"), Language::C) + ); +} + +// The ordinary test suite also supports compilers older than C23. Run this +// explicitly with a C23 compiler, e.g. CC=gcc-14 cargo test --test constant_c23 -- --ignored. +#[test] +#[ignore = "requires a C23 compiler (set CC)"] +fn c_constexpr_output_compiles() { + let directory = tempfile::tempdir().unwrap(); + let source = directory.path().join("constants.c"); + let output = directory.path().join("constants"); + let assertions = r#" +static_assert(_Generic(SMALL, uint8_t: 1, default: 0)); +static_assert(_Generic(LARGE, uint64_t: 1, default: 0)); +static_assert(_Generic(FLOAT, float: 1, default: 0)); +static_assert(_Generic(DOUBLE, double: 1, default: 0)); +static_assert(SMALL == 255); +static_assert(LARGE == UINT64_MAX); +static_assert(MASK == UINT32_MAX); +static_assert(BOUND == UINT32_MAX); +static_assert(NEGATIVE == -42); +static_assert(ARITHMETIC == 18); +static_assert(BITWISE == 7); +static_assert(A_REFERENCE == 42); +static_assert(ENABLED); +static_assert(CHARACTER == 'x'); +static_assert(Aggregate_ASSOCIATED == 6); +int sized_array[ARITHMETIC]; +const uint8_t *typed_constant_address = &SMALL; +int main(void) { + return FLOAT != (float)0.1 || DOUBLE != (double)(1.0 / 3.0) + || BIG_FLOAT_SIGNED != 9223372036854775808.0; +} +"#; + fs::write( + &source, + format!("{}\n{assertions}", generate(ENABLED, Language::C)), + ) + .unwrap(); + let result = Command::new(env::var("CC").unwrap_or_else(|_| "cc".into())) + .args(["-std=c23", "-Wall", "-Werror", "-pedantic-errors"]) + .arg(&source) + .arg("-o") + .arg(&output) + .output() + .unwrap(); + assert!(result.status.success(), "{result:?}"); + let result = Command::new(&output).output().unwrap(); + assert!(result.status.success(), "{result:?}"); +} diff --git a/tests/constant_c23/input.rs b/tests/constant_c23/input.rs new file mode 100644 index 000000000..6640925f7 --- /dev/null +++ b/tests/constant_c23/input.rs @@ -0,0 +1,55 @@ +pub const SMALL: u8 = 255; +pub const LARGE: u64 = 18446744073709551615; +pub const NEGATIVE: i32 = -42; +pub const MASK: u32 = !0u32; +pub const ARITHMETIC: usize = (4 + 2) * 3; +pub const SHIFT: u32 = 1 << 31; +pub const SUM: u32 = 2147483647 + 1; +pub const BITWISE: u32 = 5 | 2; +pub const ENABLED: bool = true; +pub const CHARACTER: char = 'x'; +pub const FLOAT: f32 = 0.1; +pub const DOUBLE: f64 = 0.3333333333333333; +pub const FLOAT_DIVISION: f64 = 1.0 / 3.0; +pub const NESTED_NEGATION: i64 = -(!2147483647); +pub const SATURATING_CAST: f64 = (1e100 as i32) as f64; +pub const BOUND: u32 = u32::MAX; +pub const MIN8: u8 = u8::MIN; +pub const MIN16: u16 = u16::MIN; +pub const MIN32: u32 = u32::MIN; +pub const MIN64: u64 = u64::MIN; +pub const BIG_FLOAT: f64 = 18446744073709551616f64; +pub const BIG_FLOAT_SIGNED: f64 = 9223372036854775808f64; +pub const SIGNED_MIN_LITERAL: i64 = -9223372036854775808i64; +pub const LATIN: char = 'é'; +pub const OFF: bool = !true; +pub const OFF_COMPARE: bool = !(1 == 1); + +// These are deliberately left as macros. In particular, alphabetical sorting +// puts A_REFERENCE before its dependency, which is harmless for macros. +pub const A_REFERENCE: u32 = Z_VALUE + 1; +pub const Z_VALUE: u32 = 41; +pub const FLOAT_TO_INTEGER: u32 = (1.5 + 2.5) as u32; +pub const DOUBLE_CAST: u32 = 1 as f32 as u32; +pub const POINTER: *const u8 = 1 as *const u8; +pub const NULL_POINTER: *const u8 = 0 as *const u8; +pub const ARRAY: [u8; 2] = [1, 2]; +pub const STRING: &str = "text"; + +#[repr(C)] +pub struct Aggregate { + pub value: u32, +} + +pub const AGGREGATE: Aggregate = Aggregate { value: 4 }; +pub const FIELD: u32 = AGGREGATE.value; + +pub type Alias = u32; +pub const ALIAS: Alias = 5; + +impl Aggregate { + pub const ASSOCIATED: u32 = 6; +} + +#[no_mangle] +pub extern "C" fn use_types(_: Aggregate, _: Alias) {} From ae45a47bf513c5c0b0df92800466513c39dff77b Mon Sep 17 00:00:00 2001 From: Govind Yadav Date: Fri, 9 Oct 2026 18:33:22 +0530 Subject: [PATCH 2/2] Use fixture harness and shared constexpr checks for C23 constants --- .github/workflows/cbindgen.yml | 5 + docs.md | 24 +-- src/bindgen/ir/constant.rs | 87 +--------- tests/constant_c23.rs | 162 ------------------ tests/constant_c23/input.rs | 55 ------ tests/expectations-symbols/constant_c23.c.sym | 3 + tests/expectations/constant_c23.c | 53 ++++++ tests/expectations/constant_c23.compat.c | 61 +++++++ tests/expectations/constant_c23.cpp | 60 +++++++ tests/expectations/constant_c23.pyx | 55 ++++++ tests/expectations/constant_c23_both.c | 53 ++++++ tests/expectations/constant_c23_both.compat.c | 61 +++++++ tests/expectations/constant_c23_tag.c | 53 ++++++ tests/expectations/constant_c23_tag.compat.c | 61 +++++++ tests/expectations/constant_c23_tag.pyx | 55 ++++++ tests/rust/constant_c23.rs | 34 ++++ tests/rust/constant_c23.toml | 3 + tests/tests.rs | 19 +- 18 files changed, 583 insertions(+), 321 deletions(-) delete mode 100644 tests/constant_c23.rs delete mode 100644 tests/constant_c23/input.rs create mode 100644 tests/expectations-symbols/constant_c23.c.sym create mode 100644 tests/expectations/constant_c23.c create mode 100644 tests/expectations/constant_c23.compat.c create mode 100644 tests/expectations/constant_c23.cpp create mode 100644 tests/expectations/constant_c23.pyx create mode 100644 tests/expectations/constant_c23_both.c create mode 100644 tests/expectations/constant_c23_both.compat.c create mode 100644 tests/expectations/constant_c23_tag.c create mode 100644 tests/expectations/constant_c23_tag.compat.c create mode 100644 tests/expectations/constant_c23_tag.pyx create mode 100644 tests/rust/constant_c23.rs create mode 100644 tests/rust/constant_c23.toml diff --git a/.github/workflows/cbindgen.yml b/.github/workflows/cbindgen.yml index 5fdd81c57..e6c1abc7d 100644 --- a/.github/workflows/cbindgen.yml +++ b/.github/workflows/cbindgen.yml @@ -91,6 +91,9 @@ jobs: python -m pip install --upgrade pip wheel pip install Cython==3.0.2 + - name: Install C23 compiler + run: sudo apt-get update && sudo apt-get install -y gcc-14 + - name: Build run: | cargo +stable build --verbose @@ -102,6 +105,7 @@ jobs: - name: Test package env: CBINDGEN_TEST_VERIFY: 1 + CC: gcc-14 run: | cargo +stable package --verbose (cd target/package/cbindgen-$(cargo +stable run -- --version | cut -d ' ' -f 2) && cargo +stable test --verbose) @@ -114,6 +118,7 @@ jobs: - name: Test env: CBINDGEN_TEST_VERIFY: 1 + CC: gcc-14 run: | cargo +nightly test --verbose diff --git a/docs.md b/docs.md index 5a9668b6d..801820b65 100644 --- a/docs.md +++ b/docs.md @@ -1037,23 +1037,13 @@ allow_constexpr = false # Requires a C23 compiler. Independent of the C++ allow_constexpr and # allow_static_const settings; does not affect C++ or Cython output. # -# Supports integer, floating-point, boolean and character constants whose -# initializers are self-contained literals, supported primitive casts and -# unary/bitwise expressions (including standard integer MAX and signed MIN -# constants). Unsigned MIN and literals outside the generated C integer range -# retain their existing macro output. -# Boolean negation and character literals rendered with Rust Unicode escapes -# also remain macros rather than promoting unsupported C literal forms. -# Explicit conversions to the declared type preserve -# narrowing semantics under C23's exact-representability requirement. -# -# Other constants remain #define macros: pointers, arrays, aggregates, aliases, -# references to user constants, field accesses, and floating-point expressions -# or casts used to initialize integers, binary arithmetic/shifts, and negation -# of compound expressions. -# Integer arithmetic needs type inference to avoid C signed-intermediate overflow. -# These require additional type/dependency -# analysis to satisfy C23's stricter constant-expression rules. +# Supports integer, floating-point, boolean and character constants, using +# the same initializer rendering as C++. Explicit conversions to the declared +# type satisfy C23's exact-representability requirement. The generated +# initializer must be a valid C23 constant expression; compiler diagnostics +# apply to unsupported expressions and overflowing intermediate values. +# +# Pointers, arrays, aggregates and aliases retain their existing macro output. # # Unlike macros, constexpr objects cannot be used in preprocessor #if/#ifdef # expressions. Enable this only when consumers use these constants in C code. diff --git a/src/bindgen/ir/constant.rs b/src/bindgen/ir/constant.rs index 1777b3a58..8e48cf1e4 100644 --- a/src/bindgen/ir/constant.rs +++ b/src/bindgen/ir/constant.rs @@ -356,84 +356,6 @@ impl Literal { !self.has_pointer_casts() } - // C23 integer constant expressions are more restrictive than C++ constexpr - // initializers: in particular, casting floating arithmetic to an integer is - // not sufficient. Keep those expressions as macros rather than guessing - // their value or changing the shared C/C++ literal representation. - fn can_be_c_constexpr(&self, allow_float: bool) -> bool { - match self { - Literal::Expr(value) => { - (value.starts_with('\'') && !value.contains("\\u{")) - || value.starts_with("U'") - || matches!(value.as_str(), "true" | "false") - || (value - .bytes() - .all(|c| c.is_ascii_digit() || c == b'u' || c == b'l') - && value - .trim_end_matches(['u', 'l']) - .parse::() - .is_ok_and(|number| value.contains('u') || number <= i64::MAX as u64)) - || (allow_float - && (value.contains('.') || value.contains('e') || value.contains('E')) - && value.parse::().is_ok()) - } - Literal::Path { - associated_to: Some((path, _)), - name, - } => { - // stdint.h has signed MIN bounds, but no UINTn_MIN macros. - !(name == "MIN" - && matches!( - PrimitiveType::maybe(path.name()), - Some(PrimitiveType::Integer { signed: false, .. }) - )) - && to_known_assoc_constant(path, name).is_some() - } - Literal::PostfixUnaryOp { op, value } => { - // Negating an inferred C int intermediate can overflow even - // when the wider Rust destination makes the expression valid. - (*op != "-" || matches!(**value, Literal::Expr(_))) - && (*op != "~" - || value.visit(&mut |literal| { - !matches!(literal, Literal::Expr(expr) if matches!(expr.as_str(), "true" | "false")) - && !matches!(literal, Literal::Cast { ty: Type::Primitive(PrimitiveType::Bool), .. }) - && !matches!(literal, Literal::BinOp { op, .. } if matches!(*op, "==" | "!=" | "<" | "<=" | ">" | ">=" | "&&" | "||")) - })) - && value.can_be_c_constexpr(allow_float) - } - Literal::BinOp { left, op, right } => { - // Unsuffixed Rust literals inherit their integer type, whereas - // C evaluates them as int before our final conversion. Arithmetic - // and shifts can therefore overflow even for valid Rust constants. - matches!( - *op, - "&" | "|" | "^" | "&&" | "||" | "==" | "!=" | "<" | "<=" | ">" | ">=" - ) && left.can_be_c_constexpr(false) - && right.can_be_c_constexpr(false) - } - Literal::Cast { ty, value } => { - let supported_type = match ty { - Type::Primitive(PrimitiveType::Void | PrimitiveType::VaList) => false, - Type::Primitive(PrimitiveType::Float | PrimitiveType::Double) => allow_float, - Type::Primitive(_) => true, - _ => false, - }; - let cast_allows_float = matches!( - ty, - Type::Primitive(PrimitiveType::Float | PrimitiveType::Double) - ); - supported_type && value.can_be_c_constexpr(cast_allows_float) - } - // Constants can be sorted by name, so a reference is not necessarily - // declared yet. Aggregates also need member-wise type checking and - // may contain non-null pointers, which C23 constexpr forbids. - Literal::Path { .. } - | Literal::FieldAccess { .. } - | Literal::Struct { .. } - | Literal::Array { .. } => false, - } - } - fn visit(&self, visitor: &mut impl FnMut(&Self) -> bool) -> bool { if !visitor(self) { return false; @@ -895,10 +817,7 @@ impl Constant { fn can_be_c_constexpr(&self) -> bool { match self.ty { Type::Primitive(PrimitiveType::Void | PrimitiveType::VaList) => false, - Type::Primitive(PrimitiveType::Float | PrimitiveType::Double) => { - self.value.can_be_c_constexpr(true) - } - Type::Primitive(_) => self.value.can_be_c_constexpr(false), + Type::Primitive(_) => self.value.can_be_constexpr(), _ => false, } } @@ -1012,12 +931,10 @@ impl Constant { Language::C if config.constant.allow_constexpr_in_c && self.can_be_c_constexpr() => { out.write("constexpr static const "); crate::bindgen::cdecl::write_field(language_backend, out, &self.ty, &name, config); + // C23 doesn't do implicit conversions on constexpr constants. out.write(" = ("); language_backend.write_type(out, &self.ty); out.write(")("); - // C23 requires exact representability. The explicit conversion - // performs the narrowing normally applied by initialization, - // e.g. for f32 literals or integer-promoted bitwise expressions. language_backend.write_literal(out, value); out.write(");"); } diff --git a/tests/constant_c23.rs b/tests/constant_c23.rs deleted file mode 100644 index 1a4e709cd..000000000 --- a/tests/constant_c23.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::env; -use std::fs; -use std::path::Path; -use std::process::Command; - -use cbindgen::{Builder, Config, Language, SortKey}; - -fn generate(config: &str, language: Language) -> String { - let mut config: Config = toml::from_str(config).unwrap(); - config.language = language; - config.sort_by = SortKey::Name; - let bindings = Builder::new() - .with_src(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/constant_c23/input.rs")) - .with_config(config) - .generate() - .unwrap(); - let mut output = Vec::new(); - bindings.write(&mut output); - String::from_utf8(output).unwrap() -} - -const ENABLED: &str = "[const]\nallow_constexpr_in_c = true\n"; - -#[test] -fn c_constexpr_is_opt_in() { - let default = generate("", Language::C); - assert!(default.contains("#define SMALL 255")); - assert!(!default.contains("constexpr")); - assert_eq!( - default, - generate("[const]\nallow_constexpr_in_c = false\n", Language::C) - ); -} - -#[test] -fn c_constexpr_emits_typed_scalar_constants() { - let output = generate(ENABLED, Language::C); - for declaration in [ - "constexpr static const uint8_t SMALL = (uint8_t)(255);", - "constexpr static const uint64_t LARGE = (uint64_t)(18446744073709551615ull);", - "constexpr static const int32_t NEGATIVE = (int32_t)(-42);", - "constexpr static const uint32_t MASK = (uint32_t)(~0u);", - "constexpr static const uint32_t BITWISE = (uint32_t)((5 | 2));", - "constexpr static const bool ENABLED = (bool)(true);", - "constexpr static const uint32_t CHARACTER = (uint32_t)('x');", - "constexpr static const float FLOAT = (float)(0.1);", - "constexpr static const double DOUBLE = (double)(0.3333333333333333);", - "constexpr static const double BIG_FLOAT_SIGNED = (double)(9223372036854775808ull);", - "constexpr static const uint32_t BOUND = (uint32_t)(UINT32_MAX);", - "constexpr static const uint32_t Aggregate_ASSOCIATED = (uint32_t)(6);", - ] { - assert!( - output.contains(declaration), - "Missing {declaration}\n{output}" - ); - } -} - -#[test] -fn c_constexpr_preserves_macro_fallbacks() { - let output = generate(ENABLED, Language::C); - for name in [ - "A_REFERENCE", - "ARITHMETIC", - "SHIFT", - "SUM", - "FLOAT_DIVISION", - "NESTED_NEGATION", - "SATURATING_CAST", - "MIN8", - "MIN16", - "MIN32", - "MIN64", - "BIG_FLOAT", - "SIGNED_MIN_LITERAL", - "LATIN", - "OFF", - "OFF_COMPARE", - "FLOAT_TO_INTEGER", - "DOUBLE_CAST", - "POINTER", - "NULL_POINTER", - "ARRAY", - "STRING", - "AGGREGATE", - "FIELD", - "ALIAS", - ] { - assert!( - output.contains(&format!("#define {name} ")), - "{name}\n{output}" - ); - } - assert!(output.find("#define A_REFERENCE").unwrap() < output.find(" Z_VALUE =").unwrap()); -} - -#[test] -fn c_constexpr_does_not_change_other_languages() { - for language in [Language::Cxx, Language::Cython] { - assert_eq!(generate("", language), generate(ENABLED, language)); - assert_eq!( - generate("[const]\nallow_constexpr = false\n", language), - generate(&format!("{ENABLED}allow_constexpr = false\n"), language) - ); - } -} - -#[test] -fn c_constexpr_is_independent_of_cpp_constexpr() { - assert_eq!( - generate(ENABLED, Language::C), - generate(&format!("{ENABLED}allow_constexpr = false\n"), Language::C) - ); -} - -// The ordinary test suite also supports compilers older than C23. Run this -// explicitly with a C23 compiler, e.g. CC=gcc-14 cargo test --test constant_c23 -- --ignored. -#[test] -#[ignore = "requires a C23 compiler (set CC)"] -fn c_constexpr_output_compiles() { - let directory = tempfile::tempdir().unwrap(); - let source = directory.path().join("constants.c"); - let output = directory.path().join("constants"); - let assertions = r#" -static_assert(_Generic(SMALL, uint8_t: 1, default: 0)); -static_assert(_Generic(LARGE, uint64_t: 1, default: 0)); -static_assert(_Generic(FLOAT, float: 1, default: 0)); -static_assert(_Generic(DOUBLE, double: 1, default: 0)); -static_assert(SMALL == 255); -static_assert(LARGE == UINT64_MAX); -static_assert(MASK == UINT32_MAX); -static_assert(BOUND == UINT32_MAX); -static_assert(NEGATIVE == -42); -static_assert(ARITHMETIC == 18); -static_assert(BITWISE == 7); -static_assert(A_REFERENCE == 42); -static_assert(ENABLED); -static_assert(CHARACTER == 'x'); -static_assert(Aggregate_ASSOCIATED == 6); -int sized_array[ARITHMETIC]; -const uint8_t *typed_constant_address = &SMALL; -int main(void) { - return FLOAT != (float)0.1 || DOUBLE != (double)(1.0 / 3.0) - || BIG_FLOAT_SIGNED != 9223372036854775808.0; -} -"#; - fs::write( - &source, - format!("{}\n{assertions}", generate(ENABLED, Language::C)), - ) - .unwrap(); - let result = Command::new(env::var("CC").unwrap_or_else(|_| "cc".into())) - .args(["-std=c23", "-Wall", "-Werror", "-pedantic-errors"]) - .arg(&source) - .arg("-o") - .arg(&output) - .output() - .unwrap(); - assert!(result.status.success(), "{result:?}"); - let result = Command::new(&output).output().unwrap(); - assert!(result.status.success(), "{result:?}"); -} diff --git a/tests/constant_c23/input.rs b/tests/constant_c23/input.rs deleted file mode 100644 index 6640925f7..000000000 --- a/tests/constant_c23/input.rs +++ /dev/null @@ -1,55 +0,0 @@ -pub const SMALL: u8 = 255; -pub const LARGE: u64 = 18446744073709551615; -pub const NEGATIVE: i32 = -42; -pub const MASK: u32 = !0u32; -pub const ARITHMETIC: usize = (4 + 2) * 3; -pub const SHIFT: u32 = 1 << 31; -pub const SUM: u32 = 2147483647 + 1; -pub const BITWISE: u32 = 5 | 2; -pub const ENABLED: bool = true; -pub const CHARACTER: char = 'x'; -pub const FLOAT: f32 = 0.1; -pub const DOUBLE: f64 = 0.3333333333333333; -pub const FLOAT_DIVISION: f64 = 1.0 / 3.0; -pub const NESTED_NEGATION: i64 = -(!2147483647); -pub const SATURATING_CAST: f64 = (1e100 as i32) as f64; -pub const BOUND: u32 = u32::MAX; -pub const MIN8: u8 = u8::MIN; -pub const MIN16: u16 = u16::MIN; -pub const MIN32: u32 = u32::MIN; -pub const MIN64: u64 = u64::MIN; -pub const BIG_FLOAT: f64 = 18446744073709551616f64; -pub const BIG_FLOAT_SIGNED: f64 = 9223372036854775808f64; -pub const SIGNED_MIN_LITERAL: i64 = -9223372036854775808i64; -pub const LATIN: char = 'é'; -pub const OFF: bool = !true; -pub const OFF_COMPARE: bool = !(1 == 1); - -// These are deliberately left as macros. In particular, alphabetical sorting -// puts A_REFERENCE before its dependency, which is harmless for macros. -pub const A_REFERENCE: u32 = Z_VALUE + 1; -pub const Z_VALUE: u32 = 41; -pub const FLOAT_TO_INTEGER: u32 = (1.5 + 2.5) as u32; -pub const DOUBLE_CAST: u32 = 1 as f32 as u32; -pub const POINTER: *const u8 = 1 as *const u8; -pub const NULL_POINTER: *const u8 = 0 as *const u8; -pub const ARRAY: [u8; 2] = [1, 2]; -pub const STRING: &str = "text"; - -#[repr(C)] -pub struct Aggregate { - pub value: u32, -} - -pub const AGGREGATE: Aggregate = Aggregate { value: 4 }; -pub const FIELD: u32 = AGGREGATE.value; - -pub type Alias = u32; -pub const ALIAS: Alias = 5; - -impl Aggregate { - pub const ASSOCIATED: u32 = 6; -} - -#[no_mangle] -pub extern "C" fn use_types(_: Aggregate, _: Alias) {} diff --git a/tests/expectations-symbols/constant_c23.c.sym b/tests/expectations-symbols/constant_c23.c.sym new file mode 100644 index 000000000..0cbbc45c0 --- /dev/null +++ b/tests/expectations-symbols/constant_c23.c.sym @@ -0,0 +1,3 @@ +{ +use_types; +}; \ No newline at end of file diff --git a/tests/expectations/constant_c23.c b/tests/expectations/constant_c23.c new file mode 100644 index 000000000..4de019c6f --- /dev/null +++ b/tests/expectations/constant_c23.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +constexpr static const uint8_t SMALL = (uint8_t)(255); + +constexpr static const uint64_t LARGE = (uint64_t)(18446744073709551615ull); + +constexpr static const int32_t NEGATIVE = (int32_t)(-42); + +constexpr static const uint32_t MASK = (uint32_t)(~0u); + +constexpr static const uintptr_t ARITHMETIC = (uintptr_t)(((4 + 2) * 3)); + +constexpr static const uint32_t SHIFT = (uint32_t)((1u << 31)); + +constexpr static const uint32_t BITWISE = (uint32_t)((5 | 2)); + +constexpr static const bool ENABLED = (bool)(true); + +constexpr static const uint32_t CHARACTER = (uint32_t)('x'); + +constexpr static const float FLOAT = (float)(0.1); + +constexpr static const double DOUBLE = (double)(0.3333333333333333); + +constexpr static const double FLOAT_DIVISION = (double)((1.0 / 3.0)); + +constexpr static const int64_t NESTED_NEGATION = (int64_t)(-~42); + +constexpr static const uint32_t BOUND = (uint32_t)(UINT32_MAX); + +constexpr static const int32_t SIGNED_MIN = (int32_t)(INT32_MIN); + +#define POINTER (const uint8_t*)1 + +typedef struct { + uint32_t value; +} Aggregate; +constexpr static const uint32_t Aggregate_ASSOCIATED = (uint32_t)(6); + +typedef uint32_t Alias; + +#define ARRAY { 1, 2, } + +#define STRING "text" + +#define AGGREGATE (Aggregate){ .value = 4 } + +#define ALIAS 5 + +void use_types(Aggregate, Alias); diff --git a/tests/expectations/constant_c23.compat.c b/tests/expectations/constant_c23.compat.c new file mode 100644 index 000000000..9269a9e64 --- /dev/null +++ b/tests/expectations/constant_c23.compat.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +constexpr static const uint8_t SMALL = (uint8_t)(255); + +constexpr static const uint64_t LARGE = (uint64_t)(18446744073709551615ull); + +constexpr static const int32_t NEGATIVE = (int32_t)(-42); + +constexpr static const uint32_t MASK = (uint32_t)(~0u); + +constexpr static const uintptr_t ARITHMETIC = (uintptr_t)(((4 + 2) * 3)); + +constexpr static const uint32_t SHIFT = (uint32_t)((1u << 31)); + +constexpr static const uint32_t BITWISE = (uint32_t)((5 | 2)); + +constexpr static const bool ENABLED = (bool)(true); + +constexpr static const uint32_t CHARACTER = (uint32_t)('x'); + +constexpr static const float FLOAT = (float)(0.1); + +constexpr static const double DOUBLE = (double)(0.3333333333333333); + +constexpr static const double FLOAT_DIVISION = (double)((1.0 / 3.0)); + +constexpr static const int64_t NESTED_NEGATION = (int64_t)(-~42); + +constexpr static const uint32_t BOUND = (uint32_t)(UINT32_MAX); + +constexpr static const int32_t SIGNED_MIN = (int32_t)(INT32_MIN); + +#define POINTER (const uint8_t*)1 + +typedef struct { + uint32_t value; +} Aggregate; +constexpr static const uint32_t Aggregate_ASSOCIATED = (uint32_t)(6); + +typedef uint32_t Alias; + +#define ARRAY { 1, 2, } + +#define STRING "text" + +#define AGGREGATE (Aggregate){ .value = 4 } + +#define ALIAS 5 + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void use_types(Aggregate, Alias); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/constant_c23.cpp b/tests/expectations/constant_c23.cpp new file mode 100644 index 000000000..4478a98bf --- /dev/null +++ b/tests/expectations/constant_c23.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +constexpr const uint8_t SMALL = 255; + +constexpr const uint64_t LARGE = 18446744073709551615ull; + +constexpr const int32_t NEGATIVE = -42; + +constexpr const uint32_t MASK = ~0u; + +constexpr const uintptr_t ARITHMETIC = ((4 + 2) * 3); + +constexpr const uint32_t SHIFT = (1u << 31); + +constexpr const uint32_t BITWISE = (5 | 2); + +constexpr const bool ENABLED = true; + +constexpr const uint32_t CHARACTER = 'x'; + +constexpr const float FLOAT = 0.1; + +constexpr const double DOUBLE = 0.3333333333333333; + +constexpr const double FLOAT_DIVISION = (1.0 / 3.0); + +constexpr const int64_t NESTED_NEGATION = -~42; + +constexpr const uint32_t BOUND = UINT32_MAX; + +constexpr const int32_t SIGNED_MIN = INT32_MIN; + +#define POINTER (const uint8_t*)1 + +struct Aggregate { + uint32_t value; +}; +constexpr const uint32_t Aggregate_ASSOCIATED = 6; + +using Alias = uint32_t; + +constexpr const uint8_t ARRAY[2] = { 1, 2, }; + +constexpr const char STRING[] = "text"; + +constexpr const Aggregate AGGREGATE = Aggregate{ + /* .value = */ 4 +}; + +constexpr const Alias ALIAS = 5; + +extern "C" { + +void use_types(Aggregate, Alias); + +} // extern "C" diff --git a/tests/expectations/constant_c23.pyx b/tests/expectations/constant_c23.pyx new file mode 100644 index 000000000..a049e03f9 --- /dev/null +++ b/tests/expectations/constant_c23.pyx @@ -0,0 +1,55 @@ +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 *: + + const uint8_t SMALL # = 255 + + const uint64_t LARGE # = 18446744073709551615ull + + const int32_t NEGATIVE # = -42 + + const uint32_t MASK # = ~0u + + const uintptr_t ARITHMETIC # = ((4 + 2) * 3) + + const uint32_t SHIFT # = (1u << 31) + + const uint32_t BITWISE # = (5 | 2) + + const bool ENABLED # = True + + const uint32_t CHARACTER # = 'x' + + const float FLOAT # = 0.1 + + const double DOUBLE # = 0.3333333333333333 + + const double FLOAT_DIVISION # = (1.0 / 3.0) + + const int64_t NESTED_NEGATION # = -~42 + + const uint32_t BOUND # = UINT32_MAX + + const int32_t SIGNED_MIN # = INT32_MIN + + const uint8_t *POINTER # = 1 + + ctypedef struct Aggregate: + uint32_t value; + const uint32_t Aggregate_ASSOCIATED # = 6 + + ctypedef uint32_t Alias; + + const uint8_t ARRAY[2] # = [ 1, 2, ] + + const char STRING[] # = "text" + + const Aggregate AGGREGATE # = { 4 } + + const Alias ALIAS # = 5 + + void use_types(Aggregate, Alias); diff --git a/tests/expectations/constant_c23_both.c b/tests/expectations/constant_c23_both.c new file mode 100644 index 000000000..84cfef3ca --- /dev/null +++ b/tests/expectations/constant_c23_both.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +constexpr static const uint8_t SMALL = (uint8_t)(255); + +constexpr static const uint64_t LARGE = (uint64_t)(18446744073709551615ull); + +constexpr static const int32_t NEGATIVE = (int32_t)(-42); + +constexpr static const uint32_t MASK = (uint32_t)(~0u); + +constexpr static const uintptr_t ARITHMETIC = (uintptr_t)(((4 + 2) * 3)); + +constexpr static const uint32_t SHIFT = (uint32_t)((1u << 31)); + +constexpr static const uint32_t BITWISE = (uint32_t)((5 | 2)); + +constexpr static const bool ENABLED = (bool)(true); + +constexpr static const uint32_t CHARACTER = (uint32_t)('x'); + +constexpr static const float FLOAT = (float)(0.1); + +constexpr static const double DOUBLE = (double)(0.3333333333333333); + +constexpr static const double FLOAT_DIVISION = (double)((1.0 / 3.0)); + +constexpr static const int64_t NESTED_NEGATION = (int64_t)(-~42); + +constexpr static const uint32_t BOUND = (uint32_t)(UINT32_MAX); + +constexpr static const int32_t SIGNED_MIN = (int32_t)(INT32_MIN); + +#define POINTER (const uint8_t*)1 + +typedef struct Aggregate { + uint32_t value; +} Aggregate; +constexpr static const uint32_t Aggregate_ASSOCIATED = (uint32_t)(6); + +typedef uint32_t Alias; + +#define ARRAY { 1, 2, } + +#define STRING "text" + +#define AGGREGATE (Aggregate){ .value = 4 } + +#define ALIAS 5 + +void use_types(struct Aggregate, Alias); diff --git a/tests/expectations/constant_c23_both.compat.c b/tests/expectations/constant_c23_both.compat.c new file mode 100644 index 000000000..287bf24ed --- /dev/null +++ b/tests/expectations/constant_c23_both.compat.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +constexpr static const uint8_t SMALL = (uint8_t)(255); + +constexpr static const uint64_t LARGE = (uint64_t)(18446744073709551615ull); + +constexpr static const int32_t NEGATIVE = (int32_t)(-42); + +constexpr static const uint32_t MASK = (uint32_t)(~0u); + +constexpr static const uintptr_t ARITHMETIC = (uintptr_t)(((4 + 2) * 3)); + +constexpr static const uint32_t SHIFT = (uint32_t)((1u << 31)); + +constexpr static const uint32_t BITWISE = (uint32_t)((5 | 2)); + +constexpr static const bool ENABLED = (bool)(true); + +constexpr static const uint32_t CHARACTER = (uint32_t)('x'); + +constexpr static const float FLOAT = (float)(0.1); + +constexpr static const double DOUBLE = (double)(0.3333333333333333); + +constexpr static const double FLOAT_DIVISION = (double)((1.0 / 3.0)); + +constexpr static const int64_t NESTED_NEGATION = (int64_t)(-~42); + +constexpr static const uint32_t BOUND = (uint32_t)(UINT32_MAX); + +constexpr static const int32_t SIGNED_MIN = (int32_t)(INT32_MIN); + +#define POINTER (const uint8_t*)1 + +typedef struct Aggregate { + uint32_t value; +} Aggregate; +constexpr static const uint32_t Aggregate_ASSOCIATED = (uint32_t)(6); + +typedef uint32_t Alias; + +#define ARRAY { 1, 2, } + +#define STRING "text" + +#define AGGREGATE (Aggregate){ .value = 4 } + +#define ALIAS 5 + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void use_types(struct Aggregate, Alias); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/constant_c23_tag.c b/tests/expectations/constant_c23_tag.c new file mode 100644 index 000000000..3543c7c18 --- /dev/null +++ b/tests/expectations/constant_c23_tag.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +constexpr static const uint8_t SMALL = (uint8_t)(255); + +constexpr static const uint64_t LARGE = (uint64_t)(18446744073709551615ull); + +constexpr static const int32_t NEGATIVE = (int32_t)(-42); + +constexpr static const uint32_t MASK = (uint32_t)(~0u); + +constexpr static const uintptr_t ARITHMETIC = (uintptr_t)(((4 + 2) * 3)); + +constexpr static const uint32_t SHIFT = (uint32_t)((1u << 31)); + +constexpr static const uint32_t BITWISE = (uint32_t)((5 | 2)); + +constexpr static const bool ENABLED = (bool)(true); + +constexpr static const uint32_t CHARACTER = (uint32_t)('x'); + +constexpr static const float FLOAT = (float)(0.1); + +constexpr static const double DOUBLE = (double)(0.3333333333333333); + +constexpr static const double FLOAT_DIVISION = (double)((1.0 / 3.0)); + +constexpr static const int64_t NESTED_NEGATION = (int64_t)(-~42); + +constexpr static const uint32_t BOUND = (uint32_t)(UINT32_MAX); + +constexpr static const int32_t SIGNED_MIN = (int32_t)(INT32_MIN); + +#define POINTER (const uint8_t*)1 + +struct Aggregate { + uint32_t value; +}; +constexpr static const uint32_t Aggregate_ASSOCIATED = (uint32_t)(6); + +typedef uint32_t Alias; + +#define ARRAY { 1, 2, } + +#define STRING "text" + +#define AGGREGATE (Aggregate){ .value = 4 } + +#define ALIAS 5 + +void use_types(struct Aggregate, Alias); diff --git a/tests/expectations/constant_c23_tag.compat.c b/tests/expectations/constant_c23_tag.compat.c new file mode 100644 index 000000000..e76a7d055 --- /dev/null +++ b/tests/expectations/constant_c23_tag.compat.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +constexpr static const uint8_t SMALL = (uint8_t)(255); + +constexpr static const uint64_t LARGE = (uint64_t)(18446744073709551615ull); + +constexpr static const int32_t NEGATIVE = (int32_t)(-42); + +constexpr static const uint32_t MASK = (uint32_t)(~0u); + +constexpr static const uintptr_t ARITHMETIC = (uintptr_t)(((4 + 2) * 3)); + +constexpr static const uint32_t SHIFT = (uint32_t)((1u << 31)); + +constexpr static const uint32_t BITWISE = (uint32_t)((5 | 2)); + +constexpr static const bool ENABLED = (bool)(true); + +constexpr static const uint32_t CHARACTER = (uint32_t)('x'); + +constexpr static const float FLOAT = (float)(0.1); + +constexpr static const double DOUBLE = (double)(0.3333333333333333); + +constexpr static const double FLOAT_DIVISION = (double)((1.0 / 3.0)); + +constexpr static const int64_t NESTED_NEGATION = (int64_t)(-~42); + +constexpr static const uint32_t BOUND = (uint32_t)(UINT32_MAX); + +constexpr static const int32_t SIGNED_MIN = (int32_t)(INT32_MIN); + +#define POINTER (const uint8_t*)1 + +struct Aggregate { + uint32_t value; +}; +constexpr static const uint32_t Aggregate_ASSOCIATED = (uint32_t)(6); + +typedef uint32_t Alias; + +#define ARRAY { 1, 2, } + +#define STRING "text" + +#define AGGREGATE (Aggregate){ .value = 4 } + +#define ALIAS 5 + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void use_types(struct Aggregate, Alias); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tests/expectations/constant_c23_tag.pyx b/tests/expectations/constant_c23_tag.pyx new file mode 100644 index 000000000..626ff0307 --- /dev/null +++ b/tests/expectations/constant_c23_tag.pyx @@ -0,0 +1,55 @@ +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 *: + + const uint8_t SMALL # = 255 + + const uint64_t LARGE # = 18446744073709551615ull + + const int32_t NEGATIVE # = -42 + + const uint32_t MASK # = ~0u + + const uintptr_t ARITHMETIC # = ((4 + 2) * 3) + + const uint32_t SHIFT # = (1u << 31) + + const uint32_t BITWISE # = (5 | 2) + + const bool ENABLED # = True + + const uint32_t CHARACTER # = 'x' + + const float FLOAT # = 0.1 + + const double DOUBLE # = 0.3333333333333333 + + const double FLOAT_DIVISION # = (1.0 / 3.0) + + const int64_t NESTED_NEGATION # = -~42 + + const uint32_t BOUND # = UINT32_MAX + + const int32_t SIGNED_MIN # = INT32_MIN + + const uint8_t *POINTER # = 1 + + cdef struct Aggregate: + uint32_t value; + const uint32_t Aggregate_ASSOCIATED # = 6 + + ctypedef uint32_t Alias; + + const uint8_t ARRAY[2] # = [ 1, 2, ] + + const char STRING[] # = "text" + + const Aggregate AGGREGATE # = { 4 } + + const Alias ALIAS # = 5 + + void use_types(Aggregate, Alias); diff --git a/tests/rust/constant_c23.rs b/tests/rust/constant_c23.rs new file mode 100644 index 000000000..8dca32d55 --- /dev/null +++ b/tests/rust/constant_c23.rs @@ -0,0 +1,34 @@ +pub const SMALL: u8 = 255; +pub const LARGE: u64 = 18446744073709551615; +pub const NEGATIVE: i32 = -42; +pub const MASK: u32 = !0u32; +pub const ARITHMETIC: usize = (4 + 2) * 3; +pub const SHIFT: u32 = 1u32 << 31; +pub const BITWISE: u32 = 5 | 2; +pub const ENABLED: bool = true; +pub const CHARACTER: char = 'x'; +pub const FLOAT: f32 = 0.1; +pub const DOUBLE: f64 = 0.3333333333333333; +pub const FLOAT_DIVISION: f64 = 1.0 / 3.0; +pub const NESTED_NEGATION: i64 = -(!42); +pub const BOUND: u32 = u32::MAX; +pub const SIGNED_MIN: i32 = i32::MIN; +pub const POINTER: *const u8 = 1 as *const u8; +pub const ARRAY: [u8; 2] = [1, 2]; +pub const STRING: &str = "text"; + +#[repr(C)] +pub struct Aggregate { + pub value: u32, +} + +pub const AGGREGATE: Aggregate = Aggregate { value: 4 }; +pub type Alias = u32; +pub const ALIAS: Alias = 5; + +impl Aggregate { + pub const ASSOCIATED: u32 = 6; +} + +#[no_mangle] +pub extern "C" fn use_types(_: Aggregate, _: Alias) {} diff --git a/tests/rust/constant_c23.toml b/tests/rust/constant_c23.toml new file mode 100644 index 000000000..15cabcde0 --- /dev/null +++ b/tests/rust/constant_c23.toml @@ -0,0 +1,3 @@ +[const] +allow_constexpr_in_c = true +allow_static_const = false diff --git a/tests/tests.rs b/tests/tests.rs index 69bf798ff..dc7d05f71 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -128,6 +128,7 @@ fn run_cbindgen( } } +#[allow(clippy::too_many_arguments)] fn compile( cbindgen_output: &Path, tests_path: &Path, @@ -136,6 +137,7 @@ fn compile( style: Option