diff --git a/bindgen-tests/tests/expectations/tests/issue-3441.rs b/bindgen-tests/tests/expectations/tests/issue-3441.rs new file mode 100644 index 0000000000..747ec3d2ef --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/issue-3441.rs @@ -0,0 +1,40 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct S { + pub _address: u8, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of S"][::std::mem::size_of::() - 1usize]; + ["Alignment of S"][::std::mem::align_of::() - 1usize]; +}; +unsafe extern "C" { + #[link_name = "\u{1}_ZNH1S5valueES_i"] + pub fn S_value(self_: S, y: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} +unsafe extern "C" { + #[link_name = "\u{1}_ZNH1S9referenceERS_i"] + pub fn S_reference(self_: *mut S, y: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} +unsafe extern "C" { + #[link_name = "\u{1}_ZN1S7regularEi"] + pub fn S_regular(this: *mut S, y: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} +impl S { + #[inline] + pub unsafe fn value(self_: S, y: ::std::os::raw::c_int) -> ::std::os::raw::c_int { + S_value(self_, y) + } + #[inline] + pub unsafe fn reference( + self_: *mut S, + y: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int { + S_reference(self_, y) + } + #[inline] + pub unsafe fn regular(&mut self, y: ::std::os::raw::c_int) -> ::std::os::raw::c_int { + S_regular(self, y) + } +} diff --git a/bindgen-tests/tests/headers/issue-3441.hpp b/bindgen-tests/tests/headers/issue-3441.hpp new file mode 100644 index 0000000000..4938bb5c0b --- /dev/null +++ b/bindgen-tests/tests/headers/issue-3441.hpp @@ -0,0 +1,8 @@ +// bindgen-flags: -- -std=c++23 +// bindgen-min-clang-version: 18 + +struct S { + int value(this S self, int y); + int reference([[maybe_unused]] this S& self, int y); + int regular(int y); +}; diff --git a/bindgen-tests/tests/tests.rs b/bindgen-tests/tests/tests.rs index 77d86bd45a..2cde8c0441 100644 --- a/bindgen-tests/tests/tests.rs +++ b/bindgen-tests/tests/tests.rs @@ -124,6 +124,17 @@ fn compare_generated_header( builder: BuilderState, check_roundtrip: bool, ) -> Result<(), Error> { + let skip_for_clang_version = if let Some(minimum) = + builder.minimum_clang_version + { + matches!(clang_version().parsed, Some((major, _)) if major < minimum) + } else { + false + }; + if skip_for_clang_version { + return Ok(()); + } + let file_name = header.file_name().ok_or_else(|| { Error::new(ErrorKind::Other, "compare_generated_header expects a file") })?; @@ -241,6 +252,7 @@ fn builder() -> Builder { struct BuilderState { builder: Builder, parse_callbacks: Option, + minimum_clang_version: Option, } impl BuilderState { @@ -259,6 +271,7 @@ impl BuilderState { Some(BuilderState { builder, parse_callbacks: self.parse_callbacks, + minimum_clang_version: self.minimum_clang_version, }) } else { None @@ -277,6 +290,7 @@ fn create_bindgen_builder(header: &Path) -> Result { // Scoop up bindgen-flags from test header let mut flags = Vec::with_capacity(2); let mut parse_callbacks = None; + let mut minimum_clang_version = None; for line in reader.lines() { let line = line?; @@ -302,6 +316,17 @@ fn create_bindgen_builder(header: &Path) -> Result { let parse_cb = line.split("bindgen-parse-callbacks: ").last().unwrap(); parse_callbacks = Some(parse_cb.to_owned()); + } else if line.contains("bindgen-min-clang-version: ") { + let version = + line.split("bindgen-min-clang-version: ").last().unwrap(); + minimum_clang_version = Some(version.parse().map_err(|err| { + Error::new( + ErrorKind::InvalidData, + format!( + "Invalid minimum libclang version '{version}': {err}" + ), + ) + })?); } } @@ -349,6 +374,7 @@ fn create_bindgen_builder(header: &Path) -> Result { Ok(BuilderState { builder, parse_callbacks, + minimum_clang_version, }) } diff --git a/bindgen/clang.rs b/bindgen/clang.rs index 9e614da9f8..3960d2d416 100644 --- a/bindgen/clang.rs +++ b/bindgen/clang.rs @@ -907,6 +907,20 @@ impl Cursor { unsafe { clang_CXXMethod_isStatic(self.x) != 0 } } + /// Is this cursor's referent a C++23 explicit-object member function? + pub(crate) fn method_is_explicit_object(&self) -> bool { + debug_assert_eq!(self.kind(), CXCursor_CXXMethod); + + // libclang has no direct API for this. The first parameter's extent + // starts at `this`, even when attributes precede the parameter. + self.args() + .and_then(|args| args.into_iter().next()) + .and_then(|arg| arg.tokens().iter().next()) + .is_some_and(|token| { + token.kind == CXToken_Keyword && token.spelling() == b"this" + }) + } + /// Is this cursor's referent a member function that is declared `const`? pub(crate) fn method_is_const(&self) -> bool { unsafe { clang_CXXMethod_isConst(self.x) != 0 } diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index 11bb26a021..277b45e5fc 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -3164,6 +3164,7 @@ impl Method { MethodKind::Destructor | MethodKind::VirtualDestructor { .. } => cc.destructors(), MethodKind::Static | + MethodKind::ExplicitObject | MethodKind::Normal | MethodKind::Virtual { .. } => cc.methods(), } @@ -3237,7 +3238,10 @@ impl Method { let mut args = utils::fnsig_arguments(ctx, signature); let mut ret = utils::fnsig_return_ty(ctx, signature); - if !self.is_static() && !self.is_constructor() { + if !self.is_static() && + !self.is_explicit_object() && + !self.is_constructor() + { args[0] = if self.is_const() { quote! { &self } } else { @@ -3271,7 +3275,7 @@ impl Method { let mut __bindgen_tmp = ::#prefix::mem::MaybeUninit::uninit() }; stmts.push(tmp_variable_decl); - } else if !self.is_static() { + } else if !self.is_static() && !self.is_explicit_object() { assert!(!exprs.is_empty()); exprs[0] = quote! { self diff --git a/bindgen/ir/comp.rs b/bindgen/ir/comp.rs index 5aae745131..17750098f3 100644 --- a/bindgen/ir/comp.rs +++ b/bindgen/ir/comp.rs @@ -44,6 +44,8 @@ pub(crate) enum MethodKind { }, /// A static method. Static, + /// A method with an explicit object parameter. + ExplicitObject, /// A normal method. Normal, /// A virtual method. @@ -72,7 +74,7 @@ impl MethodKind { } } -/// A struct representing a C++ method, either static, normal, or virtual. +/// A struct representing a C++ method. #[derive(Debug)] pub(crate) struct Method { kind: MethodKind, @@ -121,6 +123,11 @@ impl Method { self.kind == MethodKind::Static } + /// Does this method have an explicit object parameter? + pub(crate) fn is_explicit_object(&self) -> bool { + self.kind == MethodKind::ExplicitObject + } + /// Get the ID for the `Function` signature for this method. pub(crate) fn signature(&self) -> FunctionId { self.signature @@ -1581,6 +1588,8 @@ impl CompInfo { MethodKind::Virtual { pure_virtual: cur.method_is_pure_virtual(), } + } else if cur.method_is_explicit_object() { + MethodKind::ExplicitObject } else { MethodKind::Normal }; diff --git a/bindgen/ir/function.rs b/bindgen/ir/function.rs index f329c07d0d..7cc274563c 100644 --- a/bindgen/ir/function.rs +++ b/bindgen/ir/function.rs @@ -52,6 +52,8 @@ impl FunctionKind { }) } else if cursor.method_is_static() { FunctionKind::Method(MethodKind::Static) + } else if cursor.method_is_explicit_object() { + FunctionKind::Method(MethodKind::ExplicitObject) } else { FunctionKind::Method(MethodKind::Normal) } @@ -528,8 +530,10 @@ impl FunctionSig { if is_method || is_constructor || is_destructor { let is_const = is_method && cursor.method_is_const(); let is_virtual = is_method && cursor.method_is_virtual(); - let is_static = is_method && cursor.method_is_static(); - if !is_static && + let has_implicit_object = is_method && + !cursor.method_is_static() && + !cursor.method_is_explicit_object(); + if (!is_method || has_implicit_object) && (!is_virtual || ctx.options().use_specific_virtual_function_receiver) { diff --git a/bindgen/ir/item.rs b/bindgen/ir/item.rs index 4ce8c1b7ca..6e26c937db 100644 --- a/bindgen/ir/item.rs +++ b/bindgen/ir/item.rs @@ -1039,6 +1039,7 @@ impl Item { ) => cc.destructors(), FunctionKind::Method( MethodKind::Static | + MethodKind::ExplicitObject | MethodKind::Normal | MethodKind::Virtual { .. }, ) => cc.methods(),