From aa32669b3e5ff093e3bcbfe5a9364395a97f7048 Mon Sep 17 00:00:00 2001 From: Jonathan Schwender Date: Sat, 8 Feb 2025 13:16:09 +0100 Subject: [PATCH 1/3] Generate doc comment when type alias is hidden If a type is not documented, but a type alias with the same canonical path is, then generate the documentation of the typealias onto the type, since otherwise it would be lost. --- .../tests/3119_overlapping_alias_comment.rs | 8 +++ .../headers/3119_overlapping_alias_comment.h | 7 +++ bindgen/codegen/mod.rs | 49 ++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 bindgen-tests/tests/expectations/tests/3119_overlapping_alias_comment.rs create mode 100644 bindgen-tests/tests/headers/3119_overlapping_alias_comment.h diff --git a/bindgen-tests/tests/expectations/tests/3119_overlapping_alias_comment.rs b/bindgen-tests/tests/expectations/tests/3119_overlapping_alias_comment.rs new file mode 100644 index 0000000000..70008e72d1 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/3119_overlapping_alias_comment.rs @@ -0,0 +1,8 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +/** This is a forward declared struct alias with overlapping names + and documentation.*/ +#[repr(C)] +#[derive(Debug)] +pub struct Struct { + _unused: [u8; 0], +} diff --git a/bindgen-tests/tests/headers/3119_overlapping_alias_comment.h b/bindgen-tests/tests/headers/3119_overlapping_alias_comment.h new file mode 100644 index 0000000000..daa6c83df3 --- /dev/null +++ b/bindgen-tests/tests/headers/3119_overlapping_alias_comment.h @@ -0,0 +1,7 @@ +// bindgen-flags: --no-layout-tests + +/** + * This is a forward declared struct alias with overlapping names + * and documentation. + */ +typedef struct Struct Struct; diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index 7a998c8fac..f173e1dcea 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -2478,7 +2478,12 @@ impl CodeGenerator for CompInfo { let mut needs_debug_impl = false; let mut needs_partialeq_impl = false; let needs_flexarray_impl = flex_array_generic.is_some(); - if let Some(comment) = item.comment(ctx) { + let type_id = item.id().expect_type_id(ctx); + + if let Some(comment) = item + .comment(ctx) + .or_else(|| Self::get_typedef_fallback_comment(ctx, &type_id)) + { attributes.push(attributes::doc(&comment)); } @@ -3065,6 +3070,48 @@ impl CompInfo { } } } + + /// Use a fallback comment from a type alias to this type if necessary + /// + /// The documentation for a type could get lost in the following circumstances: + /// + /// - We have a type and a type alias with the same canonical path + /// - The Documentation is only associated with the type alias + /// + /// In this case bindgen will not generate the type alias and the documentation would be lost. + /// To avoid this, we check here if there is any type alias to this type, which has + /// the same canonical path and return the comment as a fallback, if our type does + /// not have documentation. + fn get_typedef_fallback_comment( + ctx: &BindgenContext, + type_id: &crate::ir::context::TypeId, + ) -> Option { + if !ctx.options().generate_comments { + return None; + } + let type_alias_comment = ctx + .items() + .filter(|(_id, alias)| { + let Some(this_ty) = alias.as_type() else { + return false; + }; + let TypeKind::Alias(alias_to) = this_ty.kind() else { + return false; + }; + // + match ctx.resolve_type(*alias_to).kind() { + TypeKind::ResolvedTypeRef(resolved_typeid) => { + resolved_typeid == type_id && + alias.canonical_path(ctx) == + type_id.canonical_path(ctx) + } + _ => false, + } + }) + .filter_map(|(_id, item)| item.comment(ctx)); + let alias_comment: Vec = type_alias_comment.collect(); + alias_comment.get(0).cloned() + } } impl Method { From 9a64be4904380898a776153c310e0d55f5095aa3 Mon Sep 17 00:00:00 2001 From: Jonathan Schwender Date: Wed, 25 Feb 2026 04:10:07 +0100 Subject: [PATCH 2/3] revert impl change --- bindgen/codegen/mod.rs | 49 +----------------------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index f173e1dcea..7a998c8fac 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -2478,12 +2478,7 @@ impl CodeGenerator for CompInfo { let mut needs_debug_impl = false; let mut needs_partialeq_impl = false; let needs_flexarray_impl = flex_array_generic.is_some(); - let type_id = item.id().expect_type_id(ctx); - - if let Some(comment) = item - .comment(ctx) - .or_else(|| Self::get_typedef_fallback_comment(ctx, &type_id)) - { + if let Some(comment) = item.comment(ctx) { attributes.push(attributes::doc(&comment)); } @@ -3070,48 +3065,6 @@ impl CompInfo { } } } - - /// Use a fallback comment from a type alias to this type if necessary - /// - /// The documentation for a type could get lost in the following circumstances: - /// - /// - We have a type and a type alias with the same canonical path - /// - The Documentation is only associated with the type alias - /// - /// In this case bindgen will not generate the type alias and the documentation would be lost. - /// To avoid this, we check here if there is any type alias to this type, which has - /// the same canonical path and return the comment as a fallback, if our type does - /// not have documentation. - fn get_typedef_fallback_comment( - ctx: &BindgenContext, - type_id: &crate::ir::context::TypeId, - ) -> Option { - if !ctx.options().generate_comments { - return None; - } - let type_alias_comment = ctx - .items() - .filter(|(_id, alias)| { - let Some(this_ty) = alias.as_type() else { - return false; - }; - let TypeKind::Alias(alias_to) = this_ty.kind() else { - return false; - }; - // - match ctx.resolve_type(*alias_to).kind() { - TypeKind::ResolvedTypeRef(resolved_typeid) => { - resolved_typeid == type_id && - alias.canonical_path(ctx) == - type_id.canonical_path(ctx) - } - _ => false, - } - }) - .filter_map(|(_id, item)| item.comment(ctx)); - let alias_comment: Vec = type_alias_comment.collect(); - alias_comment.get(0).cloned() - } } impl Method { From 20ceeebdbec4e5af3e17539e455f8950c7403a69 Mon Sep 17 00:00:00 2001 From: Jonathan Schwender Date: Wed, 25 Feb 2026 04:37:13 +0100 Subject: [PATCH 3/3] update implementation --- bindgen/ir/context.rs | 39 ++++++++++++++++++++++++++++++++++++++- bindgen/ir/item.rs | 7 +++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/bindgen/ir/context.rs b/bindgen/ir/context.rs index 8e4163df5e..1fb0ef4b3c 100644 --- a/bindgen/ir/context.rs +++ b/bindgen/ir/context.rs @@ -1848,7 +1848,10 @@ If you encounter an error missing from this list, please file an issue or a PR!" ty: &clang::Type, location: Option, ) -> Option { - use clang_sys::{CXCursor_TypeAliasTemplateDecl, CXCursor_TypeRef}; + use clang_sys::{ + CXCursor_TypeAliasTemplateDecl, CXCursor_TypeRef, + CXCursor_TypedefDecl, + }; debug!("builtin_or_resolved_ty: {ty:?}, {location:?}, {with_id:?}, {parent_id:?}"); if let Some(decl) = ty.canonical_declaration(location.as_ref()) { @@ -1864,6 +1867,18 @@ If you encounter an error missing from this list, please file an issue or a PR!" // * we have already parsed and resolved this type, and // there's nothing left to do. if let Some(location) = location { + // When a hidden `typedef struct Foo Foo;` carries docs, the + // alias is not emitted in codegen and those docs would + // otherwise be lost. Attach the docs to the already-resolved + // target type here. + if location.kind() == CXCursor_TypedefDecl { + self.inherit_typedef_comment( + id, + *decl.cursor(), + location, + ); + } + if decl.cursor().is_template_like() && *ty != decl.cursor().cur_type() { @@ -1899,6 +1914,28 @@ If you encounter an error missing from this list, please file an issue or a PR!" self.build_builtin_ty(ty) } + fn inherit_typedef_comment( + &mut self, + resolved_type: TypeId, + decl: Cursor, + typedef_cursor: Cursor, + ) { + let Some(comment) = typedef_cursor.raw_comment() else { + return; + }; + + // Only inherit docs for hidden overlapping aliases, e.g. + // `typedef struct Foo Foo;`. + if typedef_cursor.spelling() != decl.spelling() { + return; + } + + let item_id: ItemId = resolved_type.into(); + if let Some(item) = self.items[item_id.0].as_mut() { + item.set_comment_if_none(comment); + } + } + /// Make a new item that is a resolved type reference to the `wrapped_id`. /// /// This is unfortunately a lot of bloat, but is needed to properly track diff --git a/bindgen/ir/item.rs b/bindgen/ir/item.rs index eea02cce6c..77b4d1bbb1 100644 --- a/bindgen/ir/item.rs +++ b/bindgen/ir/item.rs @@ -516,6 +516,13 @@ impl Item { .map(|comment| ctx.options().process_comment(comment)) } + /// Set this item's raw comment if it does not already have one. + pub(crate) fn set_comment_if_none(&mut self, comment: String) { + if self.comment.is_none() { + self.comment = Some(comment); + } + } + /// What kind of item is this? pub(crate) fn kind(&self) -> &ItemKind { &self.kind