-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[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
[c++ interop] Swift should allow multiple SWIFT_CONFORMS_TO_PROTOCOL attributes on a C++ class #79425
Conversation
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.
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.
@swift-ci please smoke test |
@j-hui Thank you for the review. |
089780d
to
868e999
Compare
@swift-ci please smoke test |
0eb22a6
to
1f89ba1
Compare
@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.
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?
ERROR(redundant_conformance_protocol,none, | ||
"redundant conformance of %0 to protocol '%1'", (Type, StringRef)) |
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'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?
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.
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.
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.
@Xazax-hun @j-hui what's your take on this diagnostic?
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.
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).
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.
it's more difficult to convert an error into a warning
Could you elaborate on this? I'm not quite following what this means
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.
Oops, I meant to say, it's less difficult to convert an error into a warning (than the other way around).
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.
Aha! Agreed
1f89ba1
to
34d9804
Compare
34d9804
to
add29b1
Compare
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.
Thanks, I have a small comment, but this look exciting!
lib/ClangImporter/ImportDecl.cpp
Outdated
}); | ||
if (conformsToAttr == clangDecl->getAttrs().end()) | ||
return; | ||
DeclAttribute *baseAttrsSentry = *decl->getAttrs().begin(); |
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.
Having a long-lived iterator like this makes me worried about possible iterator invalidation. Can we call getAttrs().add(...)
to add the attributes instead?
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.
@egorzhdan Do you mean insert a sentry into attrs list?
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, 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!
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 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.
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.
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?
@swift-ci please smoke test |
This comment was marked as spam.
This comment was marked as spam.
@swift-ci please smoke test Windows |
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.
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.
ERROR(redundant_conformance_protocol,none, | ||
"redundant conformance of %0 to protocol '%1'", (Type, StringRef)) |
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.
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).
lib/ClangImporter/ImportDecl.cpp
Outdated
}); | ||
if (conformsToAttr == clangDecl->getAttrs().end()) | ||
return; | ||
DeclAttribute *baseAttrsSentry = *decl->getAttrs().begin(); |
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.
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?
da7931d
to
14235e9
Compare
@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.
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) {} |
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.
nit: add newline at end of file
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.
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>; |
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.
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?
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.
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.
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.
Ah, I see, thanks!
…attributes on a C++ class.
7ca46dc
to
c05d1fc
Compare
@egorzhdan I have resolved the conflicts with the main branch. Could you please help to re-trigger the test job? |
@swift-ci please smoke test |
I noticed that my PR test failed, but it seems to be unrelated to the changes I made. |
@swift-ci please smoke test macos |
Resolves #75831