Skip to content

[c++ interop] Swift should allow multiple SWIFT_CONFORMS_TO_PROTOCOL attributes on a C++ class #79425

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

CrazyFanFan
Copy link
Contributor

Resolves #75831

Copy link
Contributor

@j-hui j-hui left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution @CrazyFanFan !

What happens when multiple base classes conform to the same protocol? I'm guessing (hoping) the redundant protocol conformance error would catch that, but I'm not actually sure what would happen without it.

@j-hui
Copy link
Contributor

j-hui commented Feb 17, 2025

@swift-ci please smoke test

@CrazyFanFan
Copy link
Contributor Author

@j-hui Thank you for the review. CCC tests cases where a single class redundantly implements the same protocol. CCD was intended to test a correct combination, but since the combination issue was already tested in another file, the CCD here is indeed redundant. As for the multiple base classes, it was indeed overlooked, and I'll add it later.

@CrazyFanFan CrazyFanFan force-pushed the feature/cxx_multiple_swift_conforms_to_protocol branch from 089780d to 868e999 Compare February 17, 2025 05:35
@Xazax-hun
Copy link
Contributor

@swift-ci please smoke test

@egorzhdan egorzhdan added the c++ interop Feature: Interoperability with C++ label Feb 28, 2025
@CrazyFanFan CrazyFanFan force-pushed the feature/cxx_multiple_swift_conforms_to_protocol branch from 0eb22a6 to 1f89ba1 Compare March 3, 2025 23:17
@CrazyFanFan CrazyFanFan requested a review from j-hui March 4, 2025 02:13
@j-hui
Copy link
Contributor

j-hui commented Mar 4, 2025

@swift-ci please smoke test

Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

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

Awesome, thanks @CrazyFanFan!

I'm not convinced that we need to emit an error for redundant conformances – declaring duplicate conformances seems harmless and could be convenient with some class hierarchies. Do you think we could extract the diagnostic out of this pull request?

Comment on lines +292 to +298
ERROR(redundant_conformance_protocol,none,
"redundant conformance of %0 to protocol '%1'", (Type, StringRef))
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 not sure if redundant conformances should trigger an error. There are legitimate cases where you would end up with redundant conformances, for instance:

struct Base1 __attribute__((swift_attr("conforms_to:Mod.Proto"))) {};
struct Base2 __attribute__((swift_attr("conforms_to:Mod.Proto"))) {};
struct Derived : Base1, Base2 {};

Would the compiler emit an error for this code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your review. I have added a flag to skip the checks for protocol conformance originating from bases. With this change, redundant conformance errors will only occur when a type itself declares conformance to the same protocol multiple times.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Xazax-hun @j-hui what's your take on this diagnostic?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for missing the ping!

I'll say first that I share Egor's reservations here: I don't see what is semantically wrong with declaring duplicate conformances.

Insofar as consistency with the language conventions, I'm also unsure. Swift throws an error when you write something like struct S : P, P {}, which is inline with @CrazyFanFan's design here. Meanwhile, C/C++ are fairly lenient with respect to redundant specifiers: something like const const unsigned unsigned int * __nullable __nullable x; only raises warnings.

From a strategic standpoint, it's more difficult to convert an error into a warning (or remove it altogether), so unless there's a compelling use case for specifying redundant conformances, perhaps we should treat that as an error for now (i.e., keep this code as it is).

Copy link
Contributor

Choose a reason for hiding this comment

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

it's more difficult to convert an error into a warning

Could you elaborate on this? I'm not quite following what this means

Copy link
Contributor

@j-hui j-hui Apr 3, 2025

Choose a reason for hiding this comment

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

Oops, I meant to say, it's less difficult to convert an error into a warning (than the other way around).

Copy link
Contributor

Choose a reason for hiding this comment

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

Aha! Agreed

@CrazyFanFan CrazyFanFan force-pushed the feature/cxx_multiple_swift_conforms_to_protocol branch from 1f89ba1 to 34d9804 Compare March 11, 2025 05:14
@CrazyFanFan CrazyFanFan marked this pull request as draft March 11, 2025 05:31
@CrazyFanFan CrazyFanFan force-pushed the feature/cxx_multiple_swift_conforms_to_protocol branch from 34d9804 to add29b1 Compare March 11, 2025 08:47
@CrazyFanFan CrazyFanFan marked this pull request as ready for review March 11, 2025 08:51
@CrazyFanFan CrazyFanFan requested a review from egorzhdan March 11, 2025 13:39
Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

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

Thanks, I have a small comment, but this look exciting!

});
if (conformsToAttr == clangDecl->getAttrs().end())
return;
DeclAttribute *baseAttrsSentry = *decl->getAttrs().begin();
Copy link
Contributor

Choose a reason for hiding this comment

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

Having a long-lived iterator like this makes me worried about possible iterator invalidation. Can we call getAttrs().add(...) to add the attributes instead?

Copy link
Contributor Author

@CrazyFanFan CrazyFanFan Mar 31, 2025

Choose a reason for hiding this comment

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

@egorzhdan Do you mean insert a sentry into attrs list?

Copy link
Contributor

@egorzhdan egorzhdan Apr 1, 2025

Choose a reason for hiding this comment

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

Yeah, I'm just thinking if we can avoid having a long-lived iterator like this. If we can have a list instead of it, that sounds great!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The baseAttrsSentry is used only within the scope of addExplicitProtocolConformance, and it appears to be safe in the current context.
I also attempted to add a SentryAttribute to clangDecl->getAttrs(), but encountered an issue: a new DeclAttrKind is required to correctly identify the SentryAttribute in the list, which seems excessive for this purpose.

Copy link
Contributor

Choose a reason for hiding this comment

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

Using a sentry here like this seems to depend on the order of decl->getAttrs().getAttributes() being stable, and the implementation of decl->getAttrs().add() pushing to the front of a linked list. I'm not sure we should rely on these implementation details. Until looking at those details, I it wasn't immediately obvious to me how or why this code worked.

Can we instead create a SmallSet<ProtocolDecl *> that we populate across different calls to addExplicitProtocolConformance(), and use that to check for redundant conformances?

@egorzhdan
Copy link
Contributor

@swift-ci please smoke test

@ghost

This comment was marked as spam.

@egorzhdan
Copy link
Contributor

@swift-ci please smoke test Windows

Copy link
Contributor

@j-hui j-hui left a comment

Choose a reason for hiding this comment

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

This mostly looks good to me! I'm fine with treating redundant protocols as a compiler error, but I'm concerned about the implementation of it. I'd like that to be addressed (and will give explicit approval if it is) but don't consider it to be blocking this PR.

Comment on lines +292 to +298
ERROR(redundant_conformance_protocol,none,
"redundant conformance of %0 to protocol '%1'", (Type, StringRef))
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for missing the ping!

I'll say first that I share Egor's reservations here: I don't see what is semantically wrong with declaring duplicate conformances.

Insofar as consistency with the language conventions, I'm also unsure. Swift throws an error when you write something like struct S : P, P {}, which is inline with @CrazyFanFan's design here. Meanwhile, C/C++ are fairly lenient with respect to redundant specifiers: something like const const unsigned unsigned int * __nullable __nullable x; only raises warnings.

From a strategic standpoint, it's more difficult to convert an error into a warning (or remove it altogether), so unless there's a compelling use case for specifying redundant conformances, perhaps we should treat that as an error for now (i.e., keep this code as it is).

});
if (conformsToAttr == clangDecl->getAttrs().end())
return;
DeclAttribute *baseAttrsSentry = *decl->getAttrs().begin();
Copy link
Contributor

Choose a reason for hiding this comment

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

Using a sentry here like this seems to depend on the order of decl->getAttrs().getAttributes() being stable, and the implementation of decl->getAttrs().add() pushing to the front of a linked list. I'm not sure we should rely on these implementation details. Until looking at those details, I it wasn't immediately obvious to me how or why this code worked.

Can we instead create a SmallSet<ProtocolDecl *> that we populate across different calls to addExplicitProtocolConformance(), and use that to check for redundant conformances?

@CrazyFanFan CrazyFanFan force-pushed the feature/cxx_multiple_swift_conforms_to_protocol branch from da7931d to 14235e9 Compare April 3, 2025 03:36
@CrazyFanFan CrazyFanFan requested review from egorzhdan and j-hui April 3, 2025 16:07
@j-hui
Copy link
Contributor

j-hui commented Apr 3, 2025

@swift-ci please smoke test

Copy link
Contributor

@j-hui j-hui left a comment

Choose a reason for hiding this comment

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

Thank you for addressing my comments @CrazyFanFan ! This looks great to me.

// CHECK-NOT: error: redundant conformance of 'CDD' to protocol 'SwiftTest.D'
// CHECK-NOT: error: redundant conformance of 'CCDCD2' to protocol 'SwiftTest.D'
// CHECK-NOT: error: redundant conformance of 'CDE' to protocol 'SwiftTest.D'
func test(_ dd: CDD, _ dd2: CCDCD2, de: CDE) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add newline at end of file

Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

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

This looks great, thanks @CrazyFanFan!
I have one last comment, everything besides it looks awesome and good to be merged.

@@ -3040,6 +3041,8 @@ namespace {
return result;
}

using ProtocolSet = llvm::SmallSet<ProtocolDecl *, 4>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is a Set, I wonder if the non-deterministic order of protocols would bite us later. Looks like the compiler might start emitting diagnostics in a different order. These kind of things are usually harmless to users, but make testing harder. Could we switch to a SmallVector here to preserve the order here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, the set is only used to record whether a protocol has been added. The order of protocol addition and the generation of diagnostics is determined by clangDecl->getAttrs() in llvm::for_each(clangDecl->getAttrs(), [&](auto *attr) {}). As long as the order of clangDecl->getAttrs() remains unchanged, the set will not affect the order in which diagnostics are generated.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see, thanks!

@CrazyFanFan CrazyFanFan force-pushed the feature/cxx_multiple_swift_conforms_to_protocol branch from 7ca46dc to c05d1fc Compare April 6, 2025 14:50
@CrazyFanFan
Copy link
Contributor Author

@egorzhdan I have resolved the conflicts with the main branch. Could you please help to re-trigger the test job?

@Xazax-hun
Copy link
Contributor

@swift-ci please smoke test

@CrazyFanFan
Copy link
Contributor Author

I noticed that my PR test failed, but it seems to be unrelated to the changes I made.

@Xazax-hun
Copy link
Contributor

@swift-ci please smoke test macos

@egorzhdan egorzhdan merged commit 91e730a into swiftlang:main Apr 7, 2025
3 checks passed
@CrazyFanFan CrazyFanFan deleted the feature/cxx_multiple_swift_conforms_to_protocol branch April 7, 2025 22:28
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.

Swift should allow multiple SWIFT_CONFORMS_TO_PROTOCOL attributes on a C++ class
5 participants