-
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,6 +69,7 @@ | |
|
||
#include "llvm/ADT/STLExtras.h" | ||
#include "llvm/ADT/SmallBitVector.h" | ||
#include "llvm/ADT/SmallSet.h" | ||
#include "llvm/ADT/SmallString.h" | ||
#include "llvm/ADT/Statistic.h" | ||
#include "llvm/ADT/StringExtras.h" | ||
|
@@ -3105,6 +3106,8 @@ namespace { | |
return result; | ||
} | ||
|
||
using ProtocolSet = llvm::SmallSet<ProtocolDecl *, 4>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is a There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see, thanks! |
||
|
||
void | ||
addExplicitProtocolConformances(NominalTypeDecl *decl, | ||
const clang::CXXRecordDecl *clangDecl) { | ||
|
@@ -3119,57 +3122,69 @@ namespace { | |
if (!clangDecl->hasAttrs()) | ||
return; | ||
|
||
SmallVector<ValueDecl *, 1> results; | ||
auto conformsToAttr = | ||
llvm::find_if(clangDecl->getAttrs(), [](auto *attr) { | ||
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr)) | ||
return swiftAttr->getAttribute().starts_with("conforms_to:"); | ||
return false; | ||
}); | ||
if (conformsToAttr == clangDecl->getAttrs().end()) | ||
return; | ||
ProtocolSet alreadyAdded; | ||
llvm::for_each(clangDecl->getAttrs(), [&](auto *attr) { | ||
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr)) { | ||
if (swiftAttr->getAttribute().starts_with("conforms_to:")) | ||
addExplicitProtocolConformance(decl, swiftAttr, alreadyAdded); | ||
} | ||
}); | ||
} | ||
|
||
auto conformsToValue = cast<clang::SwiftAttrAttr>(*conformsToAttr) | ||
->getAttribute() | ||
void addExplicitProtocolConformance(NominalTypeDecl *decl, | ||
clang::SwiftAttrAttr *conformsToAttr, | ||
ProtocolSet &alreadyAdded) { | ||
auto conformsToValue = conformsToAttr->getAttribute() | ||
.drop_front(StringRef("conforms_to:").size()) | ||
.str(); | ||
auto names = StringRef(conformsToValue).split('.'); | ||
auto moduleName = names.first; | ||
auto protocolName = names.second; | ||
if (protocolName.empty()) { | ||
HeaderLoc attrLoc((*conformsToAttr)->getLocation()); | ||
HeaderLoc attrLoc(conformsToAttr->getLocation()); | ||
Impl.diagnose(attrLoc, diag::conforms_to_missing_dot, conformsToValue); | ||
return; | ||
} | ||
|
||
auto *mod = Impl.SwiftContext.getModuleByIdentifier( | ||
Impl.SwiftContext.getIdentifier(moduleName)); | ||
if (!mod) { | ||
HeaderLoc attrLoc((*conformsToAttr)->getLocation()); | ||
HeaderLoc attrLoc(conformsToAttr->getLocation()); | ||
Impl.diagnose(attrLoc, diag::cannot_find_conforms_to_module, | ||
conformsToValue, moduleName); | ||
return; | ||
} | ||
|
||
SmallVector<ValueDecl *, 1> results; | ||
mod->lookupValue(Impl.SwiftContext.getIdentifier(protocolName), | ||
NLKind::UnqualifiedLookup, results); | ||
if (results.empty()) { | ||
HeaderLoc attrLoc((*conformsToAttr)->getLocation()); | ||
HeaderLoc attrLoc(conformsToAttr->getLocation()); | ||
Impl.diagnose(attrLoc, diag::cannot_find_conforms_to, protocolName, | ||
moduleName); | ||
return; | ||
} else if (results.size() != 1) { | ||
HeaderLoc attrLoc((*conformsToAttr)->getLocation()); | ||
} | ||
|
||
if (results.size() != 1) { | ||
HeaderLoc attrLoc(conformsToAttr->getLocation()); | ||
Impl.diagnose(attrLoc, diag::conforms_to_ambiguous, protocolName, | ||
moduleName); | ||
return; | ||
} | ||
|
||
auto result = results.front(); | ||
if (auto protocol = dyn_cast<ProtocolDecl>(result)) { | ||
auto [_, inserted] = alreadyAdded.insert(protocol); | ||
if (!inserted) { | ||
HeaderLoc attrLoc(conformsToAttr->getLocation()); | ||
Impl.diagnose(attrLoc, diag::redundant_conformance_protocol, | ||
decl->getDeclaredInterfaceType(), conformsToValue); | ||
} | ||
|
||
decl->getAttrs().add( | ||
new (Impl.SwiftContext) SynthesizedProtocolAttr(protocol, &Impl, false)); | ||
} else { | ||
HeaderLoc attrLoc((*conformsToAttr)->getLocation()); | ||
HeaderLoc attrLoc((conformsToAttr)->getLocation()); | ||
Impl.diagnose(attrLoc, diag::conforms_to_not_protocol, result, | ||
conformsToValue); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,65 @@ struct __attribute__((swift_attr("conforms_to:SwiftTest.X"))) CX {}; | |
struct __attribute__((swift_attr("conforms_to:SwiftTest.A"))) CA {}; | ||
struct __attribute__((swift_attr("conforms_to:SwiftTest.B"))) CB {}; | ||
|
||
struct __attribute__((swift_attr("conforms_to:X"))) | ||
__attribute__((swift_attr("conforms_to:X"))) CXX {}; | ||
struct __attribute__((swift_attr("conforms_to:X"))) | ||
__attribute__((swift_attr("conforms_to:Mod.X"))) CXModX {}; | ||
struct __attribute__((swift_attr("conforms_to:X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.X"))) CXTextX {}; | ||
struct __attribute__((swift_attr("conforms_to:X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.A"))) CXA {}; | ||
struct __attribute__((swift_attr("conforms_to:X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.B"))) CXB {}; | ||
struct __attribute__((swift_attr("conforms_to:X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.C"))) CXC {}; | ||
|
||
|
||
struct __attribute__((swift_attr("conforms_to:Mod.X"))) | ||
__attribute__((swift_attr("conforms_to:Mod.X"))) CModXModX {}; | ||
struct __attribute__((swift_attr("conforms_to:Mod.X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.X"))) CModXTestX {}; | ||
struct __attribute__((swift_attr("conforms_to:Mod.X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.A"))) CModXA {}; | ||
struct __attribute__((swift_attr("conforms_to:Mod.X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.B"))) CModXB {}; | ||
struct __attribute__((swift_attr("conforms_to:Mod.X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.C"))) CModXC {}; | ||
|
||
struct __attribute__((swift_attr("conforms_to:SwiftTest.X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.X"))) CTestXTextX {}; | ||
struct __attribute__((swift_attr("conforms_to:SwiftTest.X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.A"))) CTextXA {}; | ||
struct __attribute__((swift_attr("conforms_to:SwiftTest.X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.B"))) CTextXB {}; | ||
struct __attribute__((swift_attr("conforms_to:SwiftTest.X"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.C"))) CTextXC {}; | ||
|
||
|
||
struct __attribute__((swift_attr("conforms_to:SwiftTest.A"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.A"))) CAA {}; | ||
struct __attribute__((swift_attr("conforms_to:SwiftTest.A"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.B"))) CAB {}; | ||
struct __attribute__((swift_attr("conforms_to:SwiftTest.A"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.C"))) CAC {}; | ||
|
||
struct __attribute__((swift_attr("conforms_to:SwiftTest.B"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.B"))) CBB {}; | ||
struct __attribute__((swift_attr("conforms_to:SwiftTest.B"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.C"))) CBC {}; | ||
|
||
struct __attribute__((swift_attr("conforms_to:SwiftTest.C"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.C"))) CCC {}; | ||
|
||
struct __attribute__((swift_attr("conforms_to:SwiftTest.D"))) CD {}; | ||
struct __attribute__((swift_attr("conforms_to:SwiftTest.D"))) CDD: CD {}; | ||
|
||
struct __attribute__((swift_attr("conforms_to:SwiftTest.D"))) CD2 {}; | ||
struct CCDCD2 : CD, CD2 {}; | ||
|
||
struct __attribute__((swift_attr("conforms_to:SwiftTest.D"))) | ||
__attribute__((swift_attr("conforms_to:SwiftTest.E"))) CDE {}; | ||
|
||
//--- test.swift | ||
|
||
import Test | ||
|
@@ -25,10 +84,78 @@ struct B {} | |
protocol A {} | ||
protocol A {} | ||
|
||
protocol C {} | ||
protocol D {} | ||
protocol E: D {} | ||
|
||
// CHECK: error: expected module name and protocol name separated by '.' in protocol conformance; 'X' is invalid | ||
// CHECK: module 'Mod' in specified protocol conformance 'Mod.X' is not found; did you mean to import it first? | ||
// CHECK: error: protocol 'X' in specified protocol conformance is not found in module 'SwiftTest' | ||
// CHECK: error: ambiguous reference to protocol 'A' in specified protocol conformance; module 'SwiftTest' contains multiple protocols named 'A' | ||
// CHECK: error: struct 'B' referenced in protocol conformance 'SwiftTest.B' is not a protocol | ||
|
||
func test(_ inv: CInv, _ invMod: CModInv, _ x: CX, _ a: CA, _ b: CB) {} | ||
|
||
// CHECK: error: expected module name and protocol name separated by '.' in protocol conformance; 'X' is invalid | ||
// CHECK: error: expected module name and protocol name separated by '.' in protocol conformance; 'X' is invalid | ||
|
||
// CHECK: error: expected module name and protocol name separated by '.' in protocol conformance; 'X' is invalid | ||
// CHECK: module 'Mod' in specified protocol conformance 'Mod.X' is not found; did you mean to import it first? | ||
|
||
// CHECK: error: expected module name and protocol name separated by '.' in protocol conformance; 'X' is invalid | ||
// CHECK: error: protocol 'X' in specified protocol conformance is not found in module 'SwiftTest' | ||
|
||
// CHECK: error: expected module name and protocol name separated by '.' in protocol conformance; 'X' is invalid | ||
// CHECK: error: ambiguous reference to protocol 'A' in specified protocol conformance; module 'SwiftTest' contains multiple protocols named 'A' | ||
|
||
// CHECK: error: expected module name and protocol name separated by '.' in protocol conformance; 'X' is invalid | ||
// CHECK: error: struct 'B' referenced in protocol conformance 'SwiftTest.B' is not a protocol | ||
|
||
// CHECK: error: expected module name and protocol name separated by '.' in protocol conformance; 'X' is invalid | ||
func test(_ xx: CXX, _ xModx: CXModX, _ xTextX: CXTextX, _ cxa: CXA, _ cxb: CXB, _ cxc: CXC) {} | ||
|
||
// CHECK: module 'Mod' in specified protocol conformance 'Mod.X' is not found; did you mean to import it first? | ||
// CHECK: module 'Mod' in specified protocol conformance 'Mod.X' is not found; did you mean to import it first? | ||
|
||
// CHECK: module 'Mod' in specified protocol conformance 'Mod.X' is not found; did you mean to import it first? | ||
// CHECK: error: protocol 'X' in specified protocol conformance is not found in module 'SwiftTest' | ||
|
||
// CHECK: module 'Mod' in specified protocol conformance 'Mod.X' is not found; did you mean to import it first? | ||
// CHECK: error: ambiguous reference to protocol 'A' in specified protocol conformance; module 'SwiftTest' contains multiple protocols named 'A' | ||
|
||
// CHECK: module 'Mod' in specified protocol conformance 'Mod.X' is not found; did you mean to import it first? | ||
// CHECK: error: struct 'B' referenced in protocol conformance 'SwiftTest.B' is not a protocol | ||
|
||
// CHECK: module 'Mod' in specified protocol conformance 'Mod.X' is not found; did you mean to import it first? | ||
func test(_ modXModX: CModXModX, _ modXTestX: CModXTestX, _ modXA: CModXA, _ modXB: CModXB, _ modXC: CModXC) {} | ||
|
||
// CHECK: error: protocol 'X' in specified protocol conformance is not found in module 'SwiftTest' | ||
// CHECK: error: protocol 'X' in specified protocol conformance is not found in module 'SwiftTest' | ||
|
||
// CHECK: error: protocol 'X' in specified protocol conformance is not found in module 'SwiftTest' | ||
// CHECK: error: ambiguous reference to protocol 'A' in specified protocol conformance; module 'SwiftTest' contains multiple protocols named 'A' | ||
|
||
// CHECK: error: protocol 'X' in specified protocol conformance is not found in module 'SwiftTest' | ||
// CHECK: error: struct 'B' referenced in protocol conformance 'SwiftTest.B' is not a protocol | ||
|
||
// CHECK: error: protocol 'X' in specified protocol conformance is not found in module 'SwiftTest' | ||
func test(_ testXTextX: CTestXTextX, _ textXA: CTextXA, _ textXB: CTextXB, _ textXC: CTextXC) {} | ||
|
||
// CHECK: error: ambiguous reference to protocol 'A' in specified protocol conformance; module 'SwiftTest' contains multiple protocols named 'A' | ||
// CHECK: error: ambiguous reference to protocol 'A' in specified protocol conformance; module 'SwiftTest' contains multiple protocols named 'A' | ||
|
||
// CHECK: error: ambiguous reference to protocol 'A' in specified protocol conformance; module 'SwiftTest' contains multiple protocols named 'A' | ||
// CHECK: error: struct 'B' referenced in protocol conformance 'SwiftTest.B' is not a protocol | ||
|
||
// CHECK: error: ambiguous reference to protocol 'A' in specified protocol conformance; module 'SwiftTest' contains multiple protocols named 'A' | ||
func test(_ aa: CAA, _ ab: CAB, _ ac: CAC) {} | ||
|
||
// CHECK: error: struct 'B' referenced in protocol conformance 'SwiftTest.B' is not a protocol | ||
func test(_ bb: CBB, _ bc: CBC) {} | ||
|
||
// CHECK: error: redundant conformance of 'CCC' to protocol 'SwiftTest.C' | ||
func test(_ cc: CCC) {} | ||
|
||
// 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 commentThe 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.
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:
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 likeconst 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.
Could you elaborate on this? I'm not quite following what this means
Uh oh!
There was an error while loading. Please reload this page.
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