From a300d263675d3b61262de81d7343ecfcdc6460ca Mon Sep 17 00:00:00 2001 From: dhmztr Date: Tue, 28 Jul 2026 21:15:19 +0200 Subject: [PATCH] fix: use the latest value when a macro is redefined Previously, when a C macro was redefined (e.g. `#define A 1` followed later by `#define A 2`), bindgen kept the value from the *first* definition when emitting the corresponding `pub const`, even though `ctx.parsed_macros` already tracked the latest value internally for evaluating other macro expressions. `Var::parse` now looks up the already-emitted `Var` item for a redefined macro and updates its value/type in place instead of discarding the redefinition, so the generated constant reflects the same value that later code in the header (and any macro expressions referencing it) would actually see. `#undef` is intentionally out of scope: libclang's cursor API has no `CXCursor_MacroUndef` kind, so `#undef` directives cannot currently be observed via cursor traversal. Fixes #2722 --- .../tests/expectations/tests/macro-redef.rs | 2 +- bindgen/ir/context.rs | 9 +++++ bindgen/ir/item_kind.rs | 9 +++++ bindgen/ir/var.rs | 37 +++++++++++++++---- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/bindgen-tests/tests/expectations/tests/macro-redef.rs b/bindgen-tests/tests/expectations/tests/macro-redef.rs index 12e3d3d587..c59c7d37e0 100644 --- a/bindgen-tests/tests/expectations/tests/macro-redef.rs +++ b/bindgen-tests/tests/expectations/tests/macro-redef.rs @@ -1,4 +1,4 @@ #![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] -pub const FOO: u32 = 4; +pub const FOO: u32 = 5; pub const BAR: u32 = 5; pub const BAZ: u32 = 6; diff --git a/bindgen/ir/context.rs b/bindgen/ir/context.rs index b5b6b4a000..a7a048b5f2 100644 --- a/bindgen/ir/context.rs +++ b/bindgen/ir/context.rs @@ -1496,6 +1496,15 @@ If you encounter an error missing from this list, please file an issue or a PR!" } } + /// Resolve the given `ItemId` into a mutable reference to an `Item`, or + /// `None` if no such item exists. + pub(crate) fn resolve_item_mut>( + &mut self, + id: Id, + ) -> Option<&mut Item> { + self.items.get_mut(id.into().0)?.as_mut() + } + /// Get the current module. pub(crate) fn current_module(&self) -> ModuleId { self.current_module diff --git a/bindgen/ir/item_kind.rs b/bindgen/ir/item_kind.rs index 9221b50579..56f8b5eacf 100644 --- a/bindgen/ir/item_kind.rs +++ b/bindgen/ir/item_kind.rs @@ -108,6 +108,15 @@ impl ItemKind { } } + /// Get a mutable reference to this `ItemKind`'s underlying `Var`, or + /// `None` if it is some other kind. + pub(crate) fn as_var_mut(&mut self) -> Option<&mut Var> { + match *self { + ItemKind::Var(ref mut v) => Some(v), + _ => None, + } + } + /// Is this a variable? pub(crate) fn is_var(&self) -> bool { self.as_var().is_some() diff --git a/bindgen/ir/var.rs b/bindgen/ir/var.rs index 9d72dcf06e..ae43d496f4 100644 --- a/bindgen/ir/var.rs +++ b/bindgen/ir/var.rs @@ -97,6 +97,13 @@ impl Var { pub fn link_name(&self) -> Option<&str> { self.link_name.as_deref() } + + /// Overwrite this variable's type and value, e.g. when a macro that + /// backs it has been redefined to a new value. + fn set_val(&mut self, ty: TypeId, val: Option) { + self.ty = ty; + self.val = val; + } } impl DotAttributes for Var { @@ -212,12 +219,6 @@ impl ClangSubItemParser for Var { // derived macros. ctx.note_parsed_macro(id.clone(), value.clone()); - if previously_defined { - let name = String::from_utf8(id).unwrap(); - duplicated_macro_diagnostic(&name, cursor.location(), ctx); - return Err(ParseError::Continue); - } - // NOTE: Unwrapping, here and above, is safe, because the // identifier of a token comes straight from clang, and we // enforce utf8 there, so we should have already panicked at @@ -264,6 +265,28 @@ impl ClangSubItemParser for Var { let ty = Item::builtin_type(type_kind, true, ctx); + if previously_defined { + let existing_id = ctx.items().find_map(|(id, item)| { + let var = item.kind().as_var()?; + (var.name() == name).then_some(id) + }); + + if let Some(existing_id) = existing_id { + duplicated_macro_diagnostic( + &name, + cursor.location(), + ctx, + ); + if let Some(existing_var) = ctx + .resolve_item_mut(existing_id) + .and_then(|item| item.kind_mut().as_var_mut()) + { + existing_var.set_val(ty, Some(val)); + } + return Ok(ParseResult::AlreadyResolved(existing_id)); + } + } + Ok(ParseResult::New( Var::new(name, None, None, ty, Some(val), true), Some(cursor), @@ -481,7 +504,7 @@ fn duplicated_macro_diagnostic( _location: clang::SourceLocation, _ctx: &BindgenContext, ) { - warn!("Duplicated macro definition: {macro_name}"); + warn!("Macro '{macro_name}' redefined; using the latest definition"); #[cfg(feature = "experimental")] // FIXME (pvdrz & amanjeev): This diagnostic message shows way too often to be actually