Skip to content

Commit 6366878

Browse files
committed
[cxx-interop] Add CxxSet protocol for std::set ergonomics
This adds a protocol to the C++ standard library overlay which will improve the ergonomics of `std::set`, `std::unordered_set` and `std::multiset` when used from Swift code. As of now, `CxxSet` adds a `contains` function to C++ sets. C++ stdlib set types are automatically conformed to `CxxSet`: `std::set`, `unordered_set`, `std::multiset`. Custom user types are not conformed to `CxxSet` automatically: while a custom type might have an interface similar to `std::set`, the semantics might differ, and adding a conformance would cause confusion.
1 parent b1707ef commit 6366878

File tree

10 files changed

+105
-4
lines changed

10 files changed

+105
-4
lines changed

include/swift/AST/KnownProtocols.def

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

107107
// C++ Standard Library Overlay:
108108
PROTOCOL(CxxConvertibleToCollection)
109+
PROTOCOL(CxxSet)
109110
PROTOCOL(CxxRandomAccessCollection)
110111
PROTOCOL(CxxSequence)
111112
PROTOCOL(UnsafeCxxInputIterator)

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
11251125
break;
11261126
case KnownProtocolKind::CxxConvertibleToCollection:
11271127
case KnownProtocolKind::CxxRandomAccessCollection:
1128+
case KnownProtocolKind::CxxSet:
11281129
case KnownProtocolKind::CxxSequence:
11291130
case KnownProtocolKind::UnsafeCxxInputIterator:
11301131
case KnownProtocolKind::UnsafeCxxRandomAccessIterator:

lib/ClangImporter/ClangDerivedConformances.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,41 @@ void swift::conformToCxxSequenceIfNeeded(
520520
impl.addSynthesizedProtocolAttrs(
521521
decl, {KnownProtocolKind::CxxConvertibleToCollection});
522522
}
523+
524+
void swift::conformToCxxSetIfNeeded(ClangImporter::Implementation &impl,
525+
NominalTypeDecl *decl,
526+
const clang::CXXRecordDecl *clangDecl) {
527+
PrettyStackTraceDecl trace("conforming to CxxSet", decl);
528+
529+
assert(decl);
530+
assert(clangDecl);
531+
ASTContext &ctx = decl->getASTContext();
532+
533+
// Only auto-conform types from the C++ standard library. Custom user types
534+
// might have a similar interface but different semantics.
535+
if (!clangDecl->isInStdNamespace())
536+
return;
537+
if (!clangDecl->getIdentifier())
538+
return;
539+
StringRef name = clangDecl->getName();
540+
if (name != "set" && name != "unordered_set" && name != "multiset")
541+
return;
542+
543+
auto valueTypeId = ctx.getIdentifier("value_type");
544+
auto valueTypes = lookupDirectWithoutExtensions(decl, valueTypeId);
545+
if (valueTypes.size() != 1)
546+
return;
547+
auto valueType = dyn_cast<TypeAliasDecl>(valueTypes.front());
548+
549+
auto sizeTypeId = ctx.getIdentifier("size_type");
550+
auto sizeTypes = lookupDirectWithoutExtensions(decl, sizeTypeId);
551+
if (sizeTypes.size() != 1)
552+
return;
553+
auto sizeType = dyn_cast<TypeAliasDecl>(sizeTypes.front());
554+
555+
impl.addSynthesizedTypealias(decl, ctx.Id_Element,
556+
valueType->getUnderlyingType());
557+
impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"),
558+
sizeType->getUnderlyingType());
559+
impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxSet});
560+
}

lib/ClangImporter/ClangDerivedConformances.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ void conformToCxxSequenceIfNeeded(ClangImporter::Implementation &impl,
3232
NominalTypeDecl *decl,
3333
const clang::CXXRecordDecl *clangDecl);
3434

35+
/// If the decl is an instantiation of C++ `std::set`, `std::unordered_set` or
36+
/// `std::multiset`, synthesize a conformance to CxxSet, which is defined in the
37+
/// Cxx module.
38+
void conformToCxxSetIfNeeded(ClangImporter::Implementation &impl,
39+
NominalTypeDecl *decl,
40+
const clang::CXXRecordDecl *clangDecl);
41+
3542
} // namespace swift
3643

3744
#endif // SWIFT_CLANG_DERIVED_CONFORMANCES_H

lib/ClangImporter/ImportDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2586,6 +2586,7 @@ namespace {
25862586
auto nominalDecl = cast<NominalTypeDecl>(result);
25872587
conformToCxxIteratorIfNeeded(Impl, nominalDecl, decl);
25882588
conformToCxxSequenceIfNeeded(Impl, nominalDecl, decl);
2589+
conformToCxxSetIfNeeded(Impl, nominalDecl, decl);
25892590
}
25902591

25912592
addExplicitProtocolConformances(cast<NominalTypeDecl>(result));

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5876,6 +5876,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
58765876
case KnownProtocolKind::DistributedTargetInvocationResultHandler:
58775877
case KnownProtocolKind::CxxConvertibleToCollection:
58785878
case KnownProtocolKind::CxxRandomAccessCollection:
5879+
case KnownProtocolKind::CxxSet:
58795880
case KnownProtocolKind::CxxSequence:
58805881
case KnownProtocolKind::UnsafeCxxInputIterator:
58815882
case KnownProtocolKind::UnsafeCxxRandomAccessIterator:

stdlib/public/Cxx/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ endif()
55

66
add_swift_target_library(swiftCxx ${SWIFT_CXX_LIBRARY_KIND} NO_LINK_NAME IS_STDLIB
77
CxxConvertibleToCollection.swift
8+
CxxSet.swift
89
CxxRandomAccessCollection.swift
910
CxxSequence.swift
1011

stdlib/public/Cxx/CxxSet.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
public protocol CxxSet<Element> {
14+
associatedtype Element
15+
associatedtype Size: BinaryInteger
16+
17+
func count(_ element: Element) -> Size
18+
}
19+
20+
extension CxxSet {
21+
public func contains(_ element: Element) -> Bool {
22+
return count(element) > 0
23+
}
24+
}

test/Interop/Cxx/stdlib/Inputs/std-set.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
#define TEST_INTEROP_CXX_STDLIB_INPUTS_STD_SET_H
33

44
#include <set>
5+
#include <unordered_set>
56

67
using SetOfCInt = std::set<int>;
8+
using UnorderedSetOfCInt = std::unordered_set<int>;
9+
using MultisetOfCInt = std::multiset<int>;
710

811
inline SetOfCInt initSetOfCInt() { return {1, 5, 3}; }
12+
inline UnorderedSetOfCInt initUnorderedSetOfCInt() { return {2, 4, 6}; }
13+
inline MultisetOfCInt initMultisetOfCInt() { return {2, 2, 4, 6}; }
914

1015
#endif // TEST_INTEROP_CXX_STDLIB_INPUTS_STD_SET_H

test/Interop/Cxx/stdlib/use-std-set.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@ import Cxx
1212

1313
var StdSetTestSuite = TestSuite("StdSet")
1414

15-
extension SetOfCInt : CxxSequence { }
16-
17-
StdSetTestSuite.test("iterate") {
18-
let s = initSetOfCInt()
15+
StdSetTestSuite.test("iterate over Swift.Array") {
16+
let s = Array(initSetOfCInt())
1917
var result = [CInt]()
2018
for x in s {
2119
result.append(x)
@@ -25,4 +23,28 @@ StdSetTestSuite.test("iterate") {
2523
expectEqual(result[2], 5)
2624
}
2725

26+
StdSetTestSuite.test("SetOfCInt.contains") {
27+
// This relies on the `std::set` conformance to `CxxSet` protocol.
28+
let s = initSetOfCInt()
29+
expectTrue(s.contains(1))
30+
expectFalse(s.contains(2))
31+
expectTrue(s.contains(3))
32+
}
33+
34+
StdSetTestSuite.test("UnorderedSetOfCInt.contains") {
35+
// This relies on the `std::unordered_set` conformance to `CxxSet` protocol.
36+
let s = initUnorderedSetOfCInt()
37+
expectFalse(s.contains(1))
38+
expectTrue(s.contains(2))
39+
expectFalse(s.contains(3))
40+
}
41+
42+
StdSetTestSuite.test("MultisetOfCInt.contains") {
43+
// This relies on the `std::multiset` conformance to `CxxSet` protocol.
44+
let s = initMultisetOfCInt()
45+
expectFalse(s.contains(1))
46+
expectTrue(s.contains(2))
47+
expectFalse(s.contains(3))
48+
}
49+
2850
runAllTests()

0 commit comments

Comments
 (0)