Skip to content

[cxx-interop] Import default public ctor of C++ foreign ref types as Swift Initializer #79986

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

Merged
merged 1 commit into from
Apr 1, 2025

Conversation

fahadnayyar
Copy link
Contributor

@fahadnayyar fahadnayyar commented Mar 13, 2025

Building on top of PR #79288, this update synthesizes a static factory method using the default new operator, with a call to the default constructor expression for C++ foreign reference types, and imports them as Swift initializers.

rdar://147529406

@fahadnayyar fahadnayyar force-pushed the cxx-frt-constructors branch from c225a0a to bc612bf Compare March 19, 2025 06:35
Xazax-hun

This comment was marked as resolved.

@Xazax-hun

This comment was marked as resolved.

@fahadnayyar fahadnayyar force-pushed the cxx-frt-constructors branch 2 times, most recently from 52235c1 to 6c9b82a Compare March 21, 2025 07:04
egorzhdan

This comment was marked as resolved.

@fahadnayyar fahadnayyar force-pushed the cxx-frt-constructors branch from 6c9b82a to 2fcb83e Compare March 23, 2025 23:17
@fahadnayyar fahadnayyar force-pushed the cxx-frt-constructors branch from 2fcb83e to 2dcf154 Compare March 25, 2025 00:45
@fahadnayyar fahadnayyar changed the title [WIP] [cxx-interop] Import default/trivial ctors of C++ foreign ref types as Swift Initializer [cxx-interop] Import default ctor of C++ foreign ref types as Swift Initializer Mar 25, 2025
@fahadnayyar fahadnayyar self-assigned this Mar 25, 2025
@fahadnayyar fahadnayyar added the c++ interop Feature: Interoperability with C++ label Mar 25, 2025
@fahadnayyar fahadnayyar force-pushed the cxx-frt-constructors branch 3 times, most recently from 695d2b2 to 3f507ef Compare March 25, 2025 03:35
@fahadnayyar fahadnayyar marked this pull request as ready for review March 25, 2025 03:46
if (auto *cxxRecordDecl = const_cast<clang::CXXRecordDecl *>(
dyn_cast<clang::CXXRecordDecl>(decl))) {
bool hasUserProvidedStaticFactory = false;
for (const auto *method : cxxRecordDecl->methods()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have a non-empty ctors array for foreign reference types? Actually, I wonder if it would be better to not even try to import the constructors above for foreign reference types. If we skip the constructors in the for (auto m : decl->decls()) { loop for foreign reference types the ctors array will only have an element if one of the static members is annotated with init. This way we can both skip some redundant work (not importing constructors we would not use) and simplify the code (do not need another pass through all the methods).

This comment was marked as resolved.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we skip the constructors in the for (auto m : decl->decls()) { loop for foreign reference types the ctors array will only have an element if one of the static members is annotated with init

I am not sure if that is the best way to handle this. I checked that the ctors array is same whether or not the FRT has user provided swift_name("init()")) method. Annotated user provided factories are not added to ctors anyway so we may have to think of some other way to factor out whether user provided factory exist or not for cxxRecordDecl

This way we can both skip some redundant work (not importing constructors we would not use) and simplify the code (do not need another pass through all the methods).

If this is not a big concern. can we optimise for this in a follow up patch?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am OK doing a different strategy for checking if there is a user annotated factory and importing ctors for foreign references more lazily in a follow-up patch.

@@ -464,6 +464,10 @@ EXPERIMENTAL_FEATURE(AssumeResilientCxxTypes, true)
/// Import inherited non-public members when importing C++ classes.
EXPERIMENTAL_FEATURE(ImportNonPublicCxxMembers, true)

/// Synthesize static factory methods for C++ foreign reference types and import
/// them as Swift initializers.
EXPERIMENTAL_FEATURE(CXXForeignReferenceTypeInitializers, true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that all the major concerns are addressed in this PR, I would even be OK to not have the feature flag and have this feature on by default for default constructors.

Copy link
Contributor

Choose a reason for hiding this comment

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

As per some discussions offline, we might want to invert this flag so users can opt out of this feature if it causes any trouble.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we might want to invert this flag so users can opt out of this feature if it causes any trouble.

I seem like we can use the same feature flag to either enable or disable a feature.
Disable: -disable-experimental-feature CXXForeignReferenceTypeInitializers
Enable: -enable-experimental-feature CXXForeignReferenceTypeInitializers

I tested this at least for the features which are marked as EXPERIMENTAL_FEATURE(<feature-name>, true)

I am not sure what would be the semantics of EXPERIMENTAL_FEATURE(<feature-name>, false)

I am not sure what is the best thing to do to call this feature as ready. My inclination is towards completely removing the feature flag if it feels stable enough and we are happy with the test coverage.

Copy link
Contributor

@Xazax-hun Xazax-hun Mar 29, 2025

Choose a reason for hiding this comment

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

My inclination is towards completely removing the feature flag if it feels stable enough and we are happy with the test coverage.

I think the compilation error with operator new and immortal references is a blocker to turn this on by default. Until those are resolved there will be user code that is broken by this patch. I.e., it was compiling fine before but stopped compiling after.

Comment on lines 2558 to 2559
ctor->getAccess() != clang::AS_private &&
ctor->getAccess() != clang::AS_protected) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be ctor->getAccess() == clang::AS_public?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm actually wondering, should we even need to check this? I think there's utility to importing non-public static factories as Swift-private initializers. We just need to make sure we set the synthesized access specifiers appropriately (rather than simply assuming and hard-coding clang::AS_public/AccessLevel::Public).

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point. Initially, I was thinking of declaring a ctor private as a way to opt out of this feature if the users run into some trouble. But in case we invert the experimental flag to be an opt out, we might not need to check the access.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could this be ctor->getAccess() == clang::AS_public?

I am not sure what to do for clang::AS_none. @j-hui told me that AS_none is for global functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We just need to make sure we set the synthesized access specifiers appropriately (rather than simply assuming and hard-coding clang::AS_public/AccessLevel::Public).

Any thoughts about what to do when operator new is private/protected (for both overloaded and global operator new) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think there's utility to importing non-public static factories as Swift-private initializers.

I am not sure if we import private or public static factories which call into private ctor in case of foreign reference types. Maybe we should keep this patch about public contractors, public new operator and come back to the other access specifier problem later in a follow up patch. (also address the clang::AS_none aka global operator new peculiarities).

@fahadnayyar fahadnayyar requested a review from hnrklssn as a code owner March 28, 2025 19:41
@fahadnayyar fahadnayyar force-pushed the cxx-frt-constructors branch 3 times, most recently from 5c3ddc9 to 06bf551 Compare March 30, 2025 01:33
@fahadnayyar fahadnayyar changed the title [cxx-interop] Import default ctor of C++ foreign ref types as Swift Initializer [cxx-interop] Import default public ctor of C++ foreign ref types as Swift Initializer Mar 31, 2025
@fahadnayyar fahadnayyar force-pushed the cxx-frt-constructors branch 2 times, most recently from 3b33855 to 38987fb Compare March 31, 2025 05:56
@fahadnayyar
Copy link
Contributor Author

@swift-ci please smoke test

@fahadnayyar fahadnayyar enabled auto-merge March 31, 2025 06:04
llvm::any_of(method->template specific_attrs<
clang::SwiftNameAttr>(),
[](const auto *attr) {
return attr->getName().str().find(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should thighten this condition here. This would also trigger for something like "myfunc(init:foo:)", so something that is not imported as an initializer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changing the check to use .startswith("init("). But we may re-visit this comment in a follow up patch if we can come up with an ever better check 🤔

@fahadnayyar
Copy link
Contributor Author

@swift-ci please smoke test macos

@fahadnayyar fahadnayyar disabled auto-merge March 31, 2025 16:45
@ravikandhadai ravikandhadai self-requested a review March 31, 2025 21:46
@fahadnayyar fahadnayyar force-pushed the cxx-frt-constructors branch from 38987fb to 7191d19 Compare March 31, 2025 22:43
@fahadnayyar
Copy link
Contributor Author

@swift-ci please smoke test

@fahadnayyar
Copy link
Contributor Author

@swift-ci please test macOS

@fahadnayyar
Copy link
Contributor Author

fahadnayyar commented Mar 31, 2025

Enabling auto-merge on this patch with these points in mind:

  • Currently the feature is hidden under -enable-experimental-feature CXXForeignReferenceTypeInitializers. I have added sufficient tests to demonstrate that the feature is stable for most common cases of public default ctor. Before removing this feature flag we need to have these 3 follow up patches (probably in that order):
    • 1.) Extend the support of this patch to allow calling non-default or parameterized ctors of c++ frts as swift initializers. rdar://148285251
    • 2.) Handle cases of private and protected operator new. That patch will also add test cases for scenarios where C++ frt (in various scenarios) are referred by Swift but its initializer is not called explicitly. We should assert that no diagnostics are triggered for cases of only referring to a frt. rdar://148284888
    • 3.) Add support and test for private and protected ctor and operator new in conjunction with @j-hui's recent features about importing private C++ members into Swift. rdar://148285637
  • Stretch goal: Explore how this feature works in conjunction of C++ inheritance. rdar://148285715
  • The removing of feature flag would go through some internal qualifications on adopter projects. rdar://148285972

All comments which are not hidden or marked as resolved could be revisited either for refactoring, stress testing or other improvements later. All resolved/hidden comments have been addressed. rdar://148286196

@fahadnayyar fahadnayyar enabled auto-merge (squash) April 1, 2025 00:30
@fahadnayyar fahadnayyar merged commit 9694cc8 into swiftlang:main Apr 1, 2025
3 of 4 checks passed
fahadnayyar added a commit that referenced this pull request Apr 8, 2025
…pes as Swift Initializer (#80449)

Extends PR #79986 by adding support for calling parameterized C++ initializers from Swift. This patch synthesizes static factory methods corresponding to C++ parameterized constructors, allowing Swift to call them as Swift initializers (e.g., init(_:), init(_:_:), etc.). This patch also aded tests and logic to make sure that we emit no additional diagnostics when a C++ foreign ref type is just referred from Swift and its initializer is not explicitly called.

rdar://148285251
fahadnayyar added a commit that referenced this pull request Apr 10, 2025
…pes as Swift Initializer (#80449)

Extends PR #79986 by adding support for calling parameterized C++ initializers from Swift. This patch synthesizes static factory methods corresponding to C++ parameterized constructors, allowing Swift to call them as Swift initializers (e.g., init(_:), init(_:_:), etc.). This patch also aded tests and logic to make sure that we emit no additional diagnostics when a C++ foreign ref type is just referred from Swift and its initializer is not explicitly called.

rdar://148285251
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants