Skip to content

Commit e3a3217

Browse files
committed
[cxx-interop] Synthesize conformances to CxxSequence
This makes ClangImporter automatically conform C++ sequence types to `Cxx.CxxSequence` protocol. We consider a C++ type to be a sequence type if it defines `begin()` & `end()` methods that return iterators of the same type which conforms to `UnsafeCxxInputIterator`.
1 parent f5787c9 commit e3a3217

File tree

10 files changed

+211
-16
lines changed

10 files changed

+211
-16
lines changed

include/swift/AST/KnownProtocols.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ PROTOCOL(DistributedTargetInvocationDecoder)
105105
PROTOCOL(DistributedTargetInvocationResultHandler)
106106

107107
// C++ Standard Library Overlay:
108+
PROTOCOL(CxxSequence)
108109
PROTOCOL(UnsafeCxxInputIterator)
109110

110111
PROTOCOL(AsyncSequence)

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
10541054
case KnownProtocolKind::DistributedTargetInvocationResultHandler:
10551055
M = getLoadedModule(Id_Distributed);
10561056
break;
1057+
case KnownProtocolKind::CxxSequence:
10571058
case KnownProtocolKind::UnsafeCxxInputIterator:
10581059
M = getLoadedModule(Id_Cxx);
10591060
break;

lib/ClangImporter/ClangDerivedConformances.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,80 @@ void swift::conformToCxxIteratorIfNeeded(
184184
impl.addSynthesizedProtocolAttrs(decl,
185185
{KnownProtocolKind::UnsafeCxxInputIterator});
186186
}
187+
188+
void swift::conformToCxxSequenceIfNeeded(
189+
ClangImporter::Implementation &impl, NominalTypeDecl *decl,
190+
const clang::CXXRecordDecl *clangDecl) {
191+
PrettyStackTraceDecl trace("conforming to CxxSequence", decl);
192+
193+
assert(decl);
194+
assert(clangDecl);
195+
ASTContext &ctx = decl->getASTContext();
196+
197+
ProtocolDecl *cxxIteratorProto =
198+
ctx.getProtocol(KnownProtocolKind::UnsafeCxxInputIterator);
199+
ProtocolDecl *cxxSequenceProto =
200+
ctx.getProtocol(KnownProtocolKind::CxxSequence);
201+
// If the Cxx module is missing, or does not include one of the necessary
202+
// protocols, bail.
203+
if (!cxxIteratorProto || !cxxSequenceProto)
204+
return;
205+
206+
// Check if present: `mutating func __beginUnsafe() -> RawIterator`
207+
auto beginId = ctx.getIdentifier("__beginUnsafe");
208+
auto begins = lookupDirectWithoutExtensions(decl, beginId);
209+
if (begins.size() != 1)
210+
return;
211+
auto begin = dyn_cast<FuncDecl>(begins.front());
212+
if (!begin)
213+
return;
214+
auto rawIteratorTy = begin->getResultInterfaceType();
215+
216+
// Check if present: `mutating func __endUnsafe() -> RawIterator`
217+
auto endId = ctx.getIdentifier("__endUnsafe");
218+
auto ends = lookupDirectWithoutExtensions(decl, endId);
219+
if (ends.size() != 1)
220+
return;
221+
auto end = dyn_cast<FuncDecl>(ends.front());
222+
if (!end)
223+
return;
224+
225+
// Check if `__beginUnsafe` and `__endUnsafe` have the same return type.
226+
auto endTy = end->getResultInterfaceType();
227+
if (!endTy || endTy->getCanonicalType() != rawIteratorTy->getCanonicalType())
228+
return;
229+
230+
// Check if RawIterator conforms to UnsafeCxxInputIterator.
231+
auto rawIteratorConformanceRef = decl->getModuleContext()->lookupConformance(
232+
rawIteratorTy, cxxIteratorProto);
233+
if (!rawIteratorConformanceRef.isConcrete())
234+
return;
235+
auto rawIteratorConformance = rawIteratorConformanceRef.getConcrete();
236+
auto pointeeDecl =
237+
cxxIteratorProto->getAssociatedType(ctx.getIdentifier("Pointee"));
238+
assert(pointeeDecl &&
239+
"UnsafeCxxInputIterator must have a Pointee associated type");
240+
auto pointeeTy = rawIteratorConformance->getTypeWitness(pointeeDecl);
241+
assert(pointeeTy && "valid conformance must have a Pointee witness");
242+
243+
// Take the default definition of `Iterator` from CxxSequence protocol. This
244+
// type is currently `CxxIterator<Self>`.
245+
auto iteratorDecl = cxxSequenceProto->getAssociatedType(ctx.Id_Iterator);
246+
auto iteratorTy = iteratorDecl->getDefaultDefinitionType();
247+
// Substitute generic `Self` parameter.
248+
auto cxxSequenceSelfTy = cxxSequenceProto->getSelfInterfaceType();
249+
auto declSelfTy = decl->getDeclaredInterfaceType();
250+
iteratorTy = iteratorTy.subst(
251+
[&](SubstitutableType *dependentType) {
252+
if (dependentType->isEqual(cxxSequenceSelfTy))
253+
return declSelfTy;
254+
return Type(dependentType);
255+
},
256+
LookUpConformanceInModule(decl->getModuleContext()));
257+
258+
impl.addSynthesizedTypealias(decl, ctx.Id_Element, pointeeTy);
259+
impl.addSynthesizedTypealias(decl, ctx.Id_Iterator, iteratorTy);
260+
impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawIterator"),
261+
rawIteratorTy);
262+
impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxSequence});
263+
}

lib/ClangImporter/ClangDerivedConformances.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@ namespace swift {
2121
bool isIterator(const clang::CXXRecordDecl *clangDecl);
2222

2323
/// If the decl is a C++ input iterator, synthesize a conformance to the
24-
/// UnsafeCxxInputIterator protocol, which is defined in the std overlay.
24+
/// UnsafeCxxInputIterator protocol, which is defined in the Cxx module.
2525
void conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl,
2626
NominalTypeDecl *decl,
2727
const clang::CXXRecordDecl *clangDecl);
2828

29+
/// If the decl is a C++ sequence, synthesize a conformance to the CxxSequence
30+
/// protocol, which is defined in the Cxx module.
31+
void conformToCxxSequenceIfNeeded(ClangImporter::Implementation &impl,
32+
NominalTypeDecl *decl,
33+
const clang::CXXRecordDecl *clangDecl);
34+
2935
} // namespace swift
3036

3137
#endif // SWIFT_CLANG_DERIVED_CONFORMANCES_H

lib/ClangImporter/ImportDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2612,6 +2612,7 @@ namespace {
26122612
if (clangModule && requiresCPlusPlus(clangModule)) {
26132613
if (auto structDecl = dyn_cast_or_null<NominalTypeDecl>(result)) {
26142614
conformToCxxIteratorIfNeeded(Impl, structDecl, decl);
2615+
conformToCxxSequenceIfNeeded(Impl, structDecl, decl);
26152616
}
26162617
}
26172618

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5808,6 +5808,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
58085808
case KnownProtocolKind::DistributedTargetInvocationEncoder:
58095809
case KnownProtocolKind::DistributedTargetInvocationDecoder:
58105810
case KnownProtocolKind::DistributedTargetInvocationResultHandler:
5811+
case KnownProtocolKind::CxxSequence:
58115812
case KnownProtocolKind::UnsafeCxxInputIterator:
58125813
case KnownProtocolKind::SerialExecutor:
58135814
case KnownProtocolKind::Sendable:

test/Interop/Cxx/stdlib/overlay/Inputs/custom-sequence.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,53 @@ struct SimpleEmptySequence {
3737
const int *end() const { return nullptr; }
3838
};
3939

40+
struct HasMutatingBeginEnd {
41+
ConstIterator begin() { return ConstIterator(1); }
42+
ConstIterator end() { return ConstIterator(5); }
43+
};
44+
45+
// TODO: this should conform to CxxSequence.
46+
struct __attribute__((swift_attr("import_reference"),
47+
swift_attr("retain:immortal"),
48+
swift_attr("release:immortal"))) ImmortalSequence {
49+
ConstIterator begin() { return ConstIterator(1); }
50+
ConstIterator end() { return ConstIterator(5); }
51+
};
52+
53+
// MARK: Types that are not actually sequences
54+
55+
struct HasNoBeginMethod {
56+
ConstIterator end() const { return ConstIterator(1); }
57+
};
58+
59+
struct HasNoEndMethod {
60+
ConstIterator begin() const { return ConstIterator(1); }
61+
};
62+
63+
struct HasBeginEndTypeMismatch {
64+
ConstIterator begin() const { return ConstIterator(1); }
65+
ConstIteratorOutOfLineEq end() const { return ConstIteratorOutOfLineEq(3); }
66+
};
67+
68+
struct HasBeginEndReturnNonIterators {
69+
struct NotIterator {};
70+
71+
NotIterator begin() const { return NotIterator(); }
72+
NotIterator end() const { return NotIterator(); }
73+
};
74+
75+
// TODO: this should not be conformed to CxxSequence, because
76+
// `const ConstIterator &` is imported as `UnsafePointer<ConstIterator>`, and
77+
// calling `successor()` is not actually going to call
78+
// `ConstIterator::operator++()`. It will increment the address instead.
79+
struct HasBeginEndReturnRef {
80+
private:
81+
ConstIterator b = ConstIterator(1);
82+
ConstIterator e = ConstIterator(5);
83+
84+
public:
85+
const ConstIterator &begin() const { return b; }
86+
const ConstIterator &end() const { return e; }
87+
};
88+
4089
#endif // TEST_INTEROP_CXX_STDLIB_INPUTS_CUSTOM_SEQUENCE_H
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %target-swift-ide-test -print-module -module-to-print=CustomSequence -source-filename=x -I %S/Inputs -enable-experimental-cxx-interop -module-cache-path %t | %FileCheck %s
2+
3+
// CHECK: import Cxx
4+
5+
// CHECK: struct SimpleSequence : CxxSequence {
6+
// CHECK: typealias Element = ConstIterator.Pointee
7+
// CHECK: typealias Iterator = CxxIterator<SimpleSequence>
8+
// CHECK: typealias RawIterator = ConstIterator
9+
// CHECK: }
10+
11+
// CHECK: struct SimpleSequenceWithOutOfLineEqualEqual : CxxSequence {
12+
// CHECK: typealias Element = ConstIteratorOutOfLineEq.Pointee
13+
// CHECK: typealias Iterator = CxxIterator<SimpleSequenceWithOutOfLineEqualEqual>
14+
// CHECK: typealias RawIterator = ConstIteratorOutOfLineEq
15+
// CHECK: }
16+
17+
// CHECK: struct SimpleArrayWrapper : CxxSequence {
18+
// CHECK: typealias Element = UnsafePointer<Int32>.Pointee
19+
// CHECK: typealias Iterator = CxxIterator<SimpleArrayWrapper>
20+
// CHECK: typealias RawIterator = UnsafePointer<Int32>
21+
// CHECK: }
22+
23+
// CHECK: struct SimpleArrayWrapperNullableIterators : CxxSequence {
24+
// CHECK: typealias Element = Optional<UnsafePointer<Int32>>.Pointee
25+
// CHECK: typealias Iterator = CxxIterator<SimpleArrayWrapperNullableIterators>
26+
// CHECK: typealias RawIterator = UnsafePointer<Int32>?
27+
// CHECK: }
28+
29+
// CHECK: struct SimpleEmptySequence : CxxSequence {
30+
// CHECK: typealias Element = Optional<UnsafePointer<Int32>>.Pointee
31+
// CHECK: typealias Iterator = CxxIterator<SimpleEmptySequence>
32+
// CHECK: typealias RawIterator = UnsafePointer<Int32>?
33+
// CHECK: }
34+
35+
// CHECK: struct HasMutatingBeginEnd : CxxSequence {
36+
// CHECK: typealias Element = ConstIterator.Pointee
37+
// CHECK: typealias Iterator = CxxIterator<HasMutatingBeginEnd>
38+
// CHECK: typealias RawIterator = ConstIterator
39+
// CHECK: }
40+
41+
// CHECK: struct HasNoBeginMethod {
42+
// CHECK-NOT: typealias Element
43+
// CHECK-NOT: typealias Iterator
44+
// CHECK-NOT: typealias RawIterator
45+
// CHECK: }
46+
// CHECK: struct HasNoEndMethod {
47+
// CHECK-NOT: typealias Element
48+
// CHECK-NOT: typealias Iterator
49+
// CHECK-NOT: typealias RawIterator
50+
// CHECK: }
51+
// CHECK: struct HasBeginEndTypeMismatch {
52+
// CHECK-NOT: typealias Element
53+
// CHECK-NOT: typealias Iterator
54+
// CHECK-NOT: typealias RawIterator
55+
// CHECK: }
56+
// CHECK: struct HasBeginEndReturnNonIterators {
57+
// CHECK-NOT: typealias Element
58+
// CHECK-NOT: typealias Iterator
59+
// CHECK-NOT: typealias RawIterator
60+
// CHECK: }

test/Interop/Cxx/stdlib/overlay/custom-sequence-typechecker.swift

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33
import CustomSequence
44
import Cxx
55

6-
// === SimpleSequence ===
7-
// Conformance to UnsafeCxxInputIterator is synthesized.
8-
extension SimpleSequence: CxxSequence {}
9-
10-
func checkSimpleSequence() {
11-
let seq = SimpleSequence()
6+
func checkIntSequence<S>(_ seq: S) where S: Sequence, S.Element == Int32 {
127
let contains = seq.contains(where: { $0 == 3 })
138
print(contains)
149

@@ -17,17 +12,26 @@ func checkSimpleSequence() {
1712
}
1813
}
1914

15+
// === SimpleSequence ===
16+
// Conformance to UnsafeCxxInputIterator is synthesized.
17+
// Conformance to CxxSequence is synthesized.
18+
checkIntSequence(SimpleSequence())
19+
2020
// === SimpleSequenceWithOutOfLineEqualEqual ===
21-
extension SimpleSequenceWithOutOfLineEqualEqual : CxxSequence {}
21+
// Conformance to CxxSequence is synthesized.
22+
checkIntSequence(SimpleSequenceWithOutOfLineEqualEqual())
2223

2324
// === SimpleArrayWrapper ===
2425
// No UnsafeCxxInputIterator conformance required, since the iterators are actually UnsafePointers here.
25-
extension SimpleArrayWrapper: CxxSequence {}
26+
// Conformance to CxxSequence is synthesized.
27+
checkIntSequence(SimpleArrayWrapper())
2628

2729
// === SimpleArrayWrapperNullableIterators ===
2830
// No UnsafeCxxInputIterator conformance required, since the iterators are actually optional UnsafePointers here.
29-
extension SimpleArrayWrapperNullableIterators: CxxSequence {}
31+
// Conformance to CxxSequence is synthesized.
32+
checkIntSequence(SimpleArrayWrapperNullableIterators())
3033

3134
// === SimpleEmptySequence ===
3235
// No UnsafeCxxInputIterator conformance required, since the iterators are actually optional UnsafePointers here.
33-
extension SimpleEmptySequence: CxxSequence {}
36+
// Conformance to CxxSequence is synthesized.
37+
checkIntSequence(SimpleEmptySequence())

test/Interop/Cxx/stdlib/overlay/custom-sequence.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@ import Cxx
99

1010
var CxxSequenceTestSuite = TestSuite("CxxSequence")
1111

12-
extension SimpleSequence: CxxSequence {}
13-
14-
extension SimpleEmptySequence: CxxSequence {}
15-
16-
1712
CxxSequenceTestSuite.test("SimpleSequence as Swift.Sequence") {
1813
let seq = SimpleSequence()
1914
let contains = seq.contains(where: { $0 == 3 })

0 commit comments

Comments
 (0)