Skip to content

ir: Don't mark functions returning a noreturn function pointer as divergent - #3537

Closed
HawkForge wants to merge 2 commits into
rust-lang:mainfrom
HawkForge:fix/noreturn-returned-fn-ptr
Closed

HawkForge wants to merge 2 commits into
rust-lang:mainfrom
HawkForge:fix/noreturn-returned-fn-ptr

Conversation

@HawkForge

@HawkForge HawkForge commented Oct 28, 2026 •

Copy link
Copy Markdown

Fixes #3489. Thanks to @lwz23 for the report.

For void (__attribute__((noreturn)) *take(int t))(int); libclang spells the function type as void (*(int))(int) __attribute__((noreturn)). FunctionSig::from_ty looks for __attribute__((noreturn)) at parenthesis depth 0 of that spelling, but here the attribute belongs to the pointed-to function type, not to take, so take was emitted as -> !.

When the return type is spelled as a raw function pointer, the function's own attributes follow its parameters inside the (*...) group, e.g. void (*(int) __attribute__((noreturn)))(int) __attribute__((noreturn)) for a function that is noreturn itself. So in that case the check now looks at depth 1 instead of 0. A return type spelled through a typedef (fp (int) __attribute__((noreturn))) and plain functions keep the depth-0 check, and argument attributes (the #2715/#2716 case) stay at depth 2+.

The header test covers take, a noreturn function returning a noreturn function pointer, the typedef'd return with and without noreturn, and a plain noreturn function. Only take changes compared to main; the test fails without the change.

Comment thread bindgen/ir/function.rs Outdated
.and_then(|ret| ret.canonical_type().pointee_type())
.is_some_and(|pointee| {
matches!(
pointee.canonical_type().kind(),

@emilio emilio Oct 28, 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.

Is this the right check tho? What if I return an alias or so? What if I have both? E.g:

void (__attribute__((noreturn)) *take(int t))(int) __attribute__((noreturn));
void plain(int t) __attribute__((noreturn));

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

good catch, both of those broke. with the first version take_noreturn-style decls and a noreturn function returning a typedef'd fn pointer both lost -> !.

looked at what libclang actually spells:

  • void (__attribute__((noreturn)) *take(int))(int) → void (*(int))(int) __attribute__((noreturn))
  • the same plus a trailing __attribute__((noreturn)) → void (*(int) __attribute__((noreturn)))(int) __attribute__((noreturn))
  • fp f(int) __attribute__((noreturn)) with fp a typedef → fp (int) __attribute__((noreturn))

so when the return type is spelled as a raw function pointer, the function's own attributes end up right after its params inside the (*...) group (depth 1), and depth 0 is the pointee's. for an alias (or anything else) it's still depth 0. pushed that: it now checks ret.kind() == CXType_Pointer on the non-canonical type (so aliases keep the old path) and looks at depth 1 in that case. args still sit at depth 2+, so the #2715 case is unchanged.

the test now covers plain, both, alias and alias + noreturn. all match main except take, which is the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

__attribute__((noreturn)) on a returned function pointer makes take itself -> !

2 participants