-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[cxx-interop] Fix exporting cdecl Swift functions to Obj-C++ #74516
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@swift-ci please smoke test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed on the approach chosen, but let's wait for a review from @beccadax to be certain – I'm not sure if there can be any ABI concerns with making this change to Obj-C interop.
We export cdecl function declarations twice: for Objective-C and for C++. When the code is compiled in Objective-C++ both of the declarations are visible to the compiler. The generated header did not compile, because only one of the declarations were noexcept. There are multiple possible ways to fix this issue, one of them would make only C++ declarations visible in Objective-C++ mode. However, for this particular problem I decided to also make the Objective-C functions SWIFT_NOEXCEPT. This approach resolves the inconsistency that broke the code when compiled in Objective-C++ mode. Moreover, Swift guarantees that those cdecl declarations cannot raise errors, so in case we only generate the C declarations and consume them from C++ or Objective-C++, those are the correct declarations. rdar://129550313
4f03fa1
to
5bb9ccf
Compare
Looks like there were a couple of tests that I did not run locally but needed an update. @swift-ci please smoke test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
swift/include/swift/PrintAsClang/ClangMacros.def says:
CLANG_MACRO_CXX("SWIFT_NOEXCEPT", , "noexcept", )
If I understand this correctly, that means SWIFT_NOEXCEPT
exists in both C++ and ObjC modes, evaluating to noexcept
in C++ mode and nothing in ObjC mode.
If that's true, then this change LGTM.
[cxx-interop] Fix exporting cdecl Swift functions to Obj-C++
@@ -1440,6 +1440,7 @@ class DeclAndTypePrinter::Implementation | |||
os << "SWIFT_EXTERN "; | |||
printFunctionDeclAsCFunctionDecl(FD, FD->getCDeclName(), resultTy); | |||
printFunctionClangAttributes(FD, funcTy); | |||
os << " SWIFT_NOEXCEPT"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't noexcept
be the first thing after the function declaration and before any possible attributes? Does the test below try to parse the header or just checks the output?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the RUN: %check-interop-cxx-header-in-clang(%t/functions.h)
runline will try to parse the header. I used this order for consistency, because other parts of the codebase was also emitting those attributes first and noexcept later. I think there is no strict place to put these non-standard attributes on the declaration, Clang seems to be forgiving on that front.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parsing is not as forgiving on my side. In Xcode 16b2 if I add
extern int _Nonnull foo(void) __attribute__((warn_unused_result)) noexcept;
to a C++ file, I get:
error: expected function body after function declarator
10 | extern int _Nonnull foo(void) __attribute__((warn_unused_result)) noexcept;
| ^
Where does the noexcept
is added in other parts of the codebase? I just sent #74780, but if there's more places it might not be bad to look at them too. They might need fixing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just checked and it looks like the order was an oversight on my part. Too bad the -fsyntax-only
wasn't good enough to catch this :/ Thanks a lot for the PR, I just approved it. We also need to cherry-pick this to the release branch.
In swiftlang#74516 the `SWIFT_NOEXCEPT` specifier is added to the imported Swift functions in C++ mode, but it is added after the function attributes. It seems that the tests only do `-fsyntax-only`, which seems not to catch an error like "expected function body after function declarator" when the header is used without that flag. Flip the attributes and the specifier around in the printer, and flip them in all the tests. (cherry picked from commit 1ea7c5a)
In #74516 the `SWIFT_NOEXCEPT` specifier is added to the imported Swift functions in C++ mode, but it is added after the function attributes. It seems that the tests only do `-fsyntax-only`, which seems not to catch an error like "expected function body after function declarator" when the header is used without that flag. Flip the attributes and the specifier around in the printer, and flip them in all the tests. The tests were using `%check-in-clang`, but it only checks importing as an objective-c-header. Add a parallel `%check-in-clang-cxx` to test also in C++. It uses C++17 because of some details in the imported headers and disables a warning about variadic macros that was promoted to an error and was blocking passing the tests. The clang-importer-sdk gets the minimal set of files to compile the two modified tests as C++. The files are mostly empty, except `cstddef` that imports the equivalent C header. Some modifications were needed in `ctypes.h` because the header was using features only available in C and not C++.
Explanation: In #74516 the `SWIFT_NOEXCEPT` specifier is added to the imported Swift functions in C++ mode, but it is added after the function attributes. It seems that the tests only do `-fsyntax-only`, which seems not to catch an error like "expected function body after function declarator" when the header is used without that flag. Flip the attributes and the specifier around in the printer, and flip them in all the tests. The tests were using `%check-in-clang`, but it only checks importing as an objective-c-header. Add a parallel `%check-in-clang-cxx` to test also in C++. It uses C++17 because of some details in the imported headers and disables a warning about variadic macros that was promoted to an error and was blocking passing the tests. The clang-importer-sdk gets the minimal set of files to compile the two modified tests as C++. The files are mostly empty, except `cstddef` that imports the equivalent C header. Some modifications were needed in `ctypes.h` because the header was using features only available in C and not C++. Scope: C++ Interop Risk: Low besides the risks pointed out in #74615. Testing: The tests have been modified. Locally using the header without -fsyntax-only also avoids the error. Reviewer: @Xazax-hun Original PR: #74780
We export cdecl function declarations twice: for Objective-C and for C++. When the code is compiled in Objective-C++ both of the declarations are visible to the compiler. The generated header did not compile, because only one of the declarations were noexcept. There are multiple possible ways to fix this issue, one of them would make only C++ declarations visible in Objective-C++ mode. However, for this particular problem I decided to also make the Objective-C functions SWIFT_NOEXCEPT. This approach resolves the inconsistency that broke the code when compiled in Objective-C++ mode. Moreover, Swift guarantees that those cdecl declarations cannot raise errors, so in case we only generate the C declarations and consume them from C++ or Objective-C++, those are the correct declarations.
rdar://129550313