Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions bindgen-tests/tests/headers/objc_builtin_typedef_cycle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// bindgen-flags: -- -x objective-c -isystem tests/headers/support/objc-builtin-typedef-cycle

#include <system-objc.h>

@protocol Foo
- (id)foo;
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* Minimal stand-in for a platform SDK's objc/objc.h: a typedef that
* shadows a compiler builtin ObjC type by aliasing its own underlying
* representation. Included via `-isystem` so clang treats it as a system
* header, matching how real SDKs provide it. */
typedef struct objc_class *Class;
struct objc_object {
Class isa;
};
typedef struct objc_object *id;
33 changes: 33 additions & 0 deletions bindgen/ir/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,39 @@ impl Type {
}
CXType_Typedef => {
let inner = cursor.typedef_type().expect("Not valid Type?");

// Some compiler builtins (Objective-C's `id`, `SEL`,
// `Class`) can also be redeclared via a same-named
// typedef aliasing their underlying representation (see
// e.g. objc/objc.h's `#if !OBJC_TYPES_DEFINED` block,
// meant to avoid clashing when the builtins aren't
// already provided by the compiler). Depending on
// libclang's version, asking such a typedef for its
// underlying type can report the *same* declaration
// (identical USR) back instead of desugaring to the
// written-out type. Treating that as an ordinary
// `Alias` would make this type indirectly reference
// itself once bindgen's deferred `resolve_typerefs`
// finishes resolving the "already resolved" builtin
// lookup, looping forever when e.g. computing its
// canonical type. Detect the same-USR case up front and
// resolve directly as the inner kind instead of
// aliasing to it, exactly as we would if `ty` itself had
// reported that kind.
let same_underlying_decl = matches!(

@emilio emilio Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check the USR tho? Can we check cursor == inner.declaration() or so?

View changes since the review

@glandium glandium Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, cursor == inner.declaration() is false for id/Class even in the exact case we want to catch, and cursor.canonical() == inner.declaration().canonical() is also false. Only the USR matches. Looks like the "builtin definitions" cursor libclang hands back for the inner type isn't the same node as the source-level typedef decl, just the same USR.

(cursor.usr(), inner.declaration().usr()),
(Some(outer), Some(inner)) if outer == inner
);
if same_underlying_decl {
return Self::from_clang_ty(
potential_id,
&inner,
location,
parent_id,
ctx,
);
}

let inner_id =
Item::from_ty_or_ref(inner, location, None, ctx);
if inner_id == potential_id {
Expand Down
Loading