Skip to content

Commit 9850906

Browse files
authored
Merge pull request #62330 from zoecarver/conforms-to-attr
[cxx-interop] Add ability to specify protocol conformance on C++ side.
2 parents b481d05 + 1a0e1ad commit 9850906

File tree

10 files changed

+183
-36
lines changed

10 files changed

+183
-36
lines changed

include/swift/AST/Attr.h

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,7 @@ class DeclAttribute : public AttributeBase {
167167
kind : 1
168168
);
169169

170-
SWIFT_INLINE_BITFIELD(SynthesizedProtocolAttr, DeclAttribute,
171-
NumKnownProtocolKindBits+1,
172-
kind : NumKnownProtocolKindBits,
170+
SWIFT_INLINE_BITFIELD(SynthesizedProtocolAttr, DeclAttribute, 1,
173171
isUnchecked : 1
174172
);
175173

@@ -1364,22 +1362,22 @@ class ObjCBridgedAttr : public DeclAttribute {
13641362
/// synthesized conformances.
13651363
class SynthesizedProtocolAttr : public DeclAttribute {
13661364
LazyConformanceLoader *Loader;
1365+
ProtocolDecl *protocol;
13671366

13681367
public:
1369-
SynthesizedProtocolAttr(KnownProtocolKind protocolKind,
1368+
SynthesizedProtocolAttr(ProtocolDecl *protocol,
13701369
LazyConformanceLoader *Loader,
13711370
bool isUnchecked)
13721371
: DeclAttribute(DAK_SynthesizedProtocol, SourceLoc(), SourceRange(),
1373-
/*Implicit=*/true), Loader(Loader)
1372+
/*Implicit=*/true), Loader(Loader), protocol(protocol)
13741373
{
1375-
Bits.SynthesizedProtocolAttr.kind = unsigned(protocolKind);
13761374
Bits.SynthesizedProtocolAttr.isUnchecked = unsigned(isUnchecked);
13771375
}
13781376

13791377
/// Retrieve the known protocol kind naming the protocol to be
13801378
/// synthesized.
1381-
KnownProtocolKind getProtocolKind() const {
1382-
return KnownProtocolKind(Bits.SynthesizedProtocolAttr.kind);
1379+
ProtocolDecl *getProtocol() const {
1380+
return protocol;
13831381
}
13841382

13851383
bool isUnchecked() const {

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,16 @@ ERROR(foreign_reference_types_invalid_release,none,
199199
"specified release function '%0' is invalid. Release must have exactly "
200200
"one argument of type '%1'", (StringRef, StringRef))
201201

202+
ERROR(cannot_find_conforms_to,none,
203+
"specified protocol conformance could not be found '%0'.", (StringRef))
204+
205+
ERROR(conforms_to_ambiguous,none,
206+
"specified protocol conformance is ambiguous. "
207+
"Found multiple protocols named '%0'.", (StringRef))
208+
209+
ERROR(conforms_to_not_protocol,none,
210+
"specified protocol conformance '%0' is not a protocol.", (StringRef))
211+
202212
NOTE(unsupported_builtin_type, none, "built-in type '%0' not supported", (StringRef))
203213
NOTE(record_field_not_imported, none, "field %0 not imported", (const clang::NamedDecl*))
204214
NOTE(invoked_func_not_imported, none, "function %0 not imported", (const clang::NamedDecl*))

lib/AST/ASTPrinter.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7101,13 +7101,14 @@ swift::getInheritedForPrinting(
71017101
llvm::SetVector<ProtocolDecl *> protocols;
71027102
llvm::TinyPtrVector<ProtocolDecl *> uncheckedProtocols;
71037103
for (auto attr : decl->getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
7104-
if (auto *proto = ctx.getProtocol(attr->getProtocolKind())) {
7104+
if (auto *proto = attr->getProtocol()) {
71057105
// The SerialExecutor conformance is only synthesized on the root
71067106
// actor class, so we can just test resilience immediately.
71077107
if (proto->isSpecificProtocol(KnownProtocolKind::SerialExecutor) &&
71087108
cast<ClassDecl>(decl)->isResilient())
71097109
continue;
7110-
if (attr->getProtocolKind() == KnownProtocolKind::RawRepresentable &&
7110+
if (proto->getKnownProtocolKind() &&
7111+
*proto->getKnownProtocolKind() == KnownProtocolKind::RawRepresentable &&
71117112
isa<EnumDecl>(decl) &&
71127113
cast<EnumDecl>(decl)->hasRawType())
71137114
continue;

lib/AST/ConformanceLookupTable.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,7 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
920920
// Find a SynthesizedProtocolAttr corresponding to the protocol.
921921
for (auto attr : conformingNominal->getAttrs()
922922
.getAttributes<SynthesizedProtocolAttr>()) {
923-
auto otherProto = ctx.getProtocol(attr->getProtocolKind());
923+
auto otherProto = attr->getProtocol();
924924
if (otherProto == impliedProto) {
925925
// Set the conformance loader to the loader stashed inside
926926
// the attribute.

lib/AST/ProtocolConformance.cpp

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,49 +1052,50 @@ void NominalTypeDecl::prepareConformanceTable() const {
10521052

10531053
SmallPtrSet<ProtocolDecl *, 2> protocols;
10541054

1055-
auto addSynthesized = [&](KnownProtocolKind kind) {
1056-
if (auto *proto = getASTContext().getProtocol(kind)) {
1057-
if (protocols.count(proto) == 0) {
1058-
ConformanceTable->addSynthesizedConformance(
1059-
mutableThis, proto, mutableThis);
1060-
protocols.insert(proto);
1061-
}
1055+
auto addSynthesized = [&](ProtocolDecl *proto) {
1056+
if (!proto)
1057+
return;
1058+
1059+
if (protocols.count(proto) == 0) {
1060+
ConformanceTable->addSynthesizedConformance(
1061+
mutableThis, proto, mutableThis);
1062+
protocols.insert(proto);
10621063
}
10631064
};
10641065

10651066
// Add protocols for any synthesized protocol attributes.
10661067
for (auto attr : getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
1067-
addSynthesized(attr->getProtocolKind());
1068+
addSynthesized(attr->getProtocol());
10681069
}
10691070

10701071
// Add any implicit conformances.
10711072
if (auto theEnum = dyn_cast<EnumDecl>(mutableThis)) {
10721073
if (theEnum->hasCases() && theEnum->hasOnlyCasesWithoutAssociatedValues()) {
10731074
// Simple enumerations conform to Equatable.
1074-
addSynthesized(KnownProtocolKind::Equatable);
1075+
addSynthesized(ctx.getProtocol(KnownProtocolKind::Equatable));
10751076

10761077
// Simple enumerations conform to Hashable.
1077-
addSynthesized(KnownProtocolKind::Hashable);
1078+
addSynthesized(ctx.getProtocol(KnownProtocolKind::Hashable));
10781079
}
10791080

10801081
// Enumerations with a raw type conform to RawRepresentable.
10811082
if (theEnum->hasRawType() && !theEnum->getRawType()->hasError()) {
1082-
addSynthesized(KnownProtocolKind::RawRepresentable);
1083+
addSynthesized(ctx.getProtocol(KnownProtocolKind::RawRepresentable));
10831084
}
10841085
}
10851086

10861087
// Actor classes conform to the actor protocol.
10871088
if (auto classDecl = dyn_cast<ClassDecl>(mutableThis)) {
10881089
if (classDecl->isDistributedActor()) {
1089-
addSynthesized(KnownProtocolKind::DistributedActor);
1090+
addSynthesized(ctx.getProtocol(KnownProtocolKind::DistributedActor));
10901091
} else if (classDecl->isActor()) {
1091-
addSynthesized(KnownProtocolKind::Actor);
1092+
addSynthesized(ctx.getProtocol(KnownProtocolKind::Actor));
10921093
}
10931094
}
10941095

10951096
// Global actors conform to the GlobalActor protocol.
10961097
if (mutableThis->getAttrs().hasAttribute<GlobalActorAttr>()) {
1097-
addSynthesized(KnownProtocolKind::GlobalActor);
1098+
addSynthesized(ctx.getProtocol(KnownProtocolKind::GlobalActor));
10981099
}
10991100
}
11001101

lib/ClangImporter/ImportDecl.cpp

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,13 @@ void ClangImporter::Implementation::addSynthesizedProtocolAttrs(
429429
auto &ctx = nominal->getASTContext();
430430

431431
for (auto kind : synthesizedProtocolAttrs) {
432-
nominal->getAttrs().add(
433-
new (ctx) SynthesizedProtocolAttr(kind, this, isUnchecked));
432+
// This is unfortunately not an error because some test use mock protocols.
433+
// If those tests were updated, we could assert that
434+
// ctx.getProtocol(kind) != nulltpr which would be nice.
435+
if (auto proto = ctx.getProtocol(kind))
436+
nominal->getAttrs().add(
437+
new (ctx) SynthesizedProtocolAttr(ctx.getProtocol(kind), this,
438+
isUnchecked));
434439
}
435440
}
436441

@@ -2611,23 +2616,72 @@ namespace {
26112616
}
26122617

26132618
auto result = VisitRecordDecl(decl);
2619+
if (!result)
2620+
return nullptr;
26142621

2615-
if (auto classDecl = dyn_cast_or_null<ClassDecl>(result))
2622+
if (auto classDecl = dyn_cast<ClassDecl>(result))
26162623
validateForeignReferenceType(decl, classDecl);
26172624

26182625
// If this module is declared as a C++ module, try to synthesize
26192626
// conformances to Swift protocols from the Cxx module.
26202627
auto clangModule = decl->getOwningModule();
26212628
if (clangModule && requiresCPlusPlus(clangModule)) {
2622-
if (auto structDecl = dyn_cast_or_null<NominalTypeDecl>(result)) {
2623-
conformToCxxIteratorIfNeeded(Impl, structDecl, decl);
2624-
conformToCxxSequenceIfNeeded(Impl, structDecl, decl);
2625-
}
2629+
auto nominalDecl = cast<NominalTypeDecl>(result);
2630+
conformToCxxIteratorIfNeeded(Impl, nominalDecl, decl);
2631+
conformToCxxSequenceIfNeeded(Impl, nominalDecl, decl);
26262632
}
26272633

2634+
addExplicitProtocolConformances(cast<NominalTypeDecl>(result));
2635+
26282636
return result;
26292637
}
26302638

2639+
void addExplicitProtocolConformances(NominalTypeDecl *decl) {
2640+
auto clangDecl = decl->getClangDecl();
2641+
2642+
if (!clangDecl->hasAttrs())
2643+
return;
2644+
2645+
SmallVector<ValueDecl *, 1> results;
2646+
auto conformsToAttr =
2647+
llvm::find_if(clangDecl->getAttrs(), [](auto *attr) {
2648+
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr))
2649+
return swiftAttr->getAttribute().startswith("conforms_to:");
2650+
return false;
2651+
});
2652+
if (conformsToAttr == clangDecl->getAttrs().end())
2653+
return;
2654+
2655+
auto name = cast<clang::SwiftAttrAttr>(*conformsToAttr)
2656+
->getAttribute()
2657+
.drop_front(StringRef("conforms_to:").size())
2658+
.str();
2659+
2660+
for (auto &module : Impl.SwiftContext.getLoadedModules()) {
2661+
module.second->lookupValue(Impl.SwiftContext.getIdentifier(name),
2662+
NLKind::UnqualifiedLookup, results);
2663+
}
2664+
2665+
if (results.empty()) {
2666+
HeaderLoc attrLoc((*conformsToAttr)->getLocation());
2667+
Impl.diagnose(attrLoc, diag::cannot_find_conforms_to, name);
2668+
return;
2669+
} else if (results.size() != 1) {
2670+
HeaderLoc attrLoc((*conformsToAttr)->getLocation());
2671+
Impl.diagnose(attrLoc, diag::conforms_to_ambiguous, name);
2672+
return;
2673+
}
2674+
2675+
auto result = results.front();
2676+
if (auto protocol = dyn_cast<ProtocolDecl>(result)) {
2677+
decl->getAttrs().add(
2678+
new (Impl.SwiftContext) SynthesizedProtocolAttr(protocol, &Impl, false));
2679+
} else {
2680+
HeaderLoc attrLoc((*conformsToAttr)->getLocation());
2681+
Impl.diagnose(attrLoc, diag::conforms_to_not_protocol, name);
2682+
}
2683+
}
2684+
26312685
bool isSpecializationDepthGreaterThan(
26322686
const clang::ClassTemplateSpecializationDecl *decl, unsigned maxDepth) {
26332687
for (auto arg : decl->getTemplateArgs().asArray()) {
@@ -5126,10 +5180,11 @@ static bool conformsToProtocolInOriginalModule(NominalTypeDecl *nominal,
51265180
if (inheritanceListContainsProtocol(nominal, proto))
51275181
return true;
51285182

5129-
for (auto attr : nominal->getAttrs().getAttributes<SynthesizedProtocolAttr>())
5130-
if (auto *otherProto = ctx.getProtocol(attr->getProtocolKind()))
5131-
if (otherProto == proto || otherProto->inheritsFrom(proto))
5132-
return true;
5183+
for (auto attr : nominal->getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
5184+
auto *otherProto = attr->getProtocol();
5185+
if (otherProto == proto || otherProto->inheritsFrom(proto))
5186+
return true;
5187+
}
51335188

51345189
// Only consider extensions from the original module...or from an overlay
51355190
// or the Swift half of a mixed-source framework.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H
2+
#define TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H
3+
4+
struct
5+
__attribute__((swift_attr("conforms_to:Testable")))
6+
HasTest {
7+
void test() const;
8+
};
9+
10+
struct
11+
__attribute__((swift_attr("conforms_to:Playable")))
12+
__attribute__((swift_attr("import_reference")))
13+
__attribute__((swift_attr("retain:immortal")))
14+
__attribute__((swift_attr("release:immortal")))
15+
HasPlay {
16+
void play() const;
17+
};
18+
19+
20+
#endif // TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H

test/Interop/Cxx/class/Inputs/module.modulemap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,8 @@ module ForwardDeclaredInNamespace {
131131
header "forward-declared-in-namespace.h"
132132
requires cplusplus
133133
}
134+
135+
module ConformsTo {
136+
header "conforms-to.h"
137+
requires cplusplus
138+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: rm -rf %t
2+
// RUN: split-file %s %t
3+
// RUN: not %target-swift-frontend -typecheck -I %t/Inputs %t/test.swift -enable-experimental-cxx-interop 2>&1 | %FileCheck %s
4+
5+
//--- Inputs/module.modulemap
6+
module Test {
7+
header "test.h"
8+
requires cplusplus
9+
}
10+
11+
//--- Inputs/test.h
12+
13+
struct __attribute__((swift_attr("conforms_to:X"))) CX {};
14+
struct __attribute__((swift_attr("conforms_to:A"))) CA {};
15+
struct __attribute__((swift_attr("conforms_to:B"))) CB {};
16+
17+
//--- test.swift
18+
19+
import Test
20+
21+
struct B {}
22+
23+
protocol A {}
24+
protocol A {}
25+
26+
// CHECK: error: specified protocol conformance could not be found 'X'.
27+
// CHECK: error: specified protocol conformance is ambiguous. Found multiple protocols named 'A'.
28+
// CHECK: error: specified protocol conformance 'B' is not a protocol.
29+
30+
func test(_ x: CX, _ a: CA, _ b: CB) {}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs -enable-experimental-cxx-interop
2+
3+
import ConformsTo
4+
5+
protocol Testable {
6+
func test()
7+
}
8+
9+
protocol Playable {
10+
func play()
11+
}
12+
13+
func callee(_ _: Testable) {
14+
15+
}
16+
17+
func caller(_ x: HasTest) {
18+
callee(x)
19+
}
20+
21+
func callee(_ _: Playable) {
22+
23+
}
24+
25+
func caller(_ x: Playable) {
26+
callee(x)
27+
}

0 commit comments

Comments
 (0)