Skip to content

Commit c49f174

Browse files
authored
Merge pull request #59492 from apple/egorzhdan/cxx-overlay-sequence
[cxx-interop] Add `CxxSequence` protocol to the stdlib overlay
2 parents 2c425f0 + 6754c3c commit c49f174

File tree

9 files changed

+315
-21
lines changed

9 files changed

+315
-21
lines changed

stdlib/public/Cxx/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ add_dependencies(sdk-overlay libstdcxx-modulemap)
129129
#
130130
add_swift_target_library(swiftstd ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_SDK_OVERLAY
131131
std.swift
132+
CxxSequence.swift
132133
String.swift
133134

134135
SWIFT_MODULE_DEPENDS_OSX Darwin

stdlib/public/Cxx/CxxSequence.swift

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 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+
/// Bridged C++ iterator that allows to traverse the elements of a sequence
14+
/// using a for-in loop.
15+
///
16+
/// Mostly useful for conforming a type to the `CxxSequence` protocol and should
17+
/// not generally be used directly.
18+
///
19+
/// - SeeAlso: https://en.cppreference.com/w/cpp/named_req/InputIterator
20+
public protocol UnsafeCxxInputIterator: Equatable {
21+
associatedtype Pointee
22+
23+
/// Returns the unwrapped result of C++ `operator*()`.
24+
///
25+
/// Generally, Swift creates this property automatically for C++ types that
26+
/// define `operator*()`.
27+
var pointee: Pointee { get }
28+
29+
/// Returns an iterator pointing to the next item in the sequence.
30+
///
31+
/// Generally, Swift creates this property automatically for C++ types that
32+
/// define pre-increment `operator++()`.
33+
func successor() -> Self
34+
}
35+
36+
extension UnsafePointer: UnsafeCxxInputIterator {}
37+
38+
extension UnsafeMutablePointer: UnsafeCxxInputIterator {}
39+
40+
extension Optional: UnsafeCxxInputIterator where Wrapped: UnsafeCxxInputIterator {
41+
public typealias Pointee = Wrapped.Pointee
42+
43+
public var pointee: Pointee {
44+
if let value = self {
45+
return value.pointee
46+
}
47+
fatalError("Could not dereference nullptr")
48+
}
49+
50+
public func successor() -> Self {
51+
if let value = self {
52+
return value.successor()
53+
}
54+
fatalError("Could not increment nullptr")
55+
}
56+
}
57+
58+
/// Use this protocol to conform custom C++ sequence types to Swift's `Sequence`
59+
/// protocol like this:
60+
///
61+
/// extension MyCxxSequenceType : CxxSequence {}
62+
///
63+
/// This requires the C++ sequence type to define const methods `begin()` and
64+
/// `end()` which return input iterators into the C++ sequence. The iterator
65+
/// types must conform to `UnsafeCxxInputIterator`.
66+
public protocol CxxSequence: Sequence {
67+
associatedtype RawIterator: UnsafeCxxInputIterator
68+
associatedtype Element = RawIterator.Pointee
69+
70+
// `begin()` and `end()` have to be mutating, otherwise calling
71+
// `self.sequence.begin()` will copy `self.sequence` into a temporary value,
72+
// and the result will be dangling. This does not mean that the implementing
73+
// methods _have_ to be mutating.
74+
75+
/// Do not implement this function manually in Swift.
76+
mutating func begin() -> RawIterator
77+
78+
/// Do not implement this function manually in Swift.
79+
mutating func end() -> RawIterator
80+
}
81+
82+
public class CxxIterator<T>: IteratorProtocol where T: CxxSequence {
83+
// Declared as a class instead of a struct to avoid copies of this object,
84+
// which would result in dangling pointers for some C++ sequence types.
85+
86+
public typealias Element = T.RawIterator.Pointee
87+
private var sequence: T
88+
private var rawIterator: T.RawIterator
89+
private let endIterator: T.RawIterator
90+
91+
public init(sequence: T) {
92+
self.sequence = sequence
93+
self.rawIterator = self.sequence.begin()
94+
self.endIterator = self.sequence.end()
95+
}
96+
97+
public func next() -> Element? {
98+
if self.rawIterator == self.endIterator {
99+
return nil
100+
}
101+
let object = self.rawIterator.pointee
102+
self.rawIterator = self.rawIterator.successor()
103+
return object
104+
}
105+
}
106+
107+
extension CxxSequence {
108+
public func makeIterator() -> CxxIterator<Self> {
109+
return CxxIterator(sequence: self)
110+
}
111+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#ifndef TEST_INTEROP_CXX_STDLIB_INPUTS_CUSTOM_SEQUENCE_H
2+
#define TEST_INTEROP_CXX_STDLIB_INPUTS_CUSTOM_SEQUENCE_H
3+
4+
#include <cstddef>
5+
#include <iterator>
6+
7+
struct SimpleSequence {
8+
struct ConstIterator {
9+
private:
10+
int value;
11+
12+
public:
13+
using iterator_category = std::input_iterator_tag;
14+
using value_type = int;
15+
using pointer = int *;
16+
using reference = int &;
17+
using difference_type = int;
18+
19+
ConstIterator(int value) : value(value) {}
20+
ConstIterator(const ConstIterator &other) = default;
21+
22+
const int &operator*() const { return value; }
23+
24+
ConstIterator &operator++() {
25+
value++;
26+
return *this;
27+
}
28+
ConstIterator operator++(int) {
29+
auto tmp = ConstIterator(value);
30+
value++;
31+
return tmp;
32+
}
33+
34+
bool operator==(const ConstIterator &other) const {
35+
return value == other.value;
36+
}
37+
bool operator!=(const ConstIterator &other) const {
38+
return value != other.value;
39+
}
40+
};
41+
42+
ConstIterator begin() const { return ConstIterator(1); }
43+
ConstIterator end() const { return ConstIterator(5); }
44+
};
45+
46+
struct SimpleArrayWrapper {
47+
private:
48+
int a[5] = {10, 20, 30, 40, 50};
49+
50+
public:
51+
const int *begin() const __attribute__((returns_nonnull)) { return &a[0]; }
52+
const int *end() const __attribute__((returns_nonnull)) { return &a[5]; }
53+
};
54+
55+
struct SimpleArrayWrapperNullableIterators {
56+
private:
57+
int a[5] = {10, 20, 30, 40, 50};
58+
59+
public:
60+
const int *begin() const { return &a[0]; }
61+
const int *end() const { return &a[5]; }
62+
};
63+
64+
struct SimpleEmptySequence {
65+
const int *begin() const { return nullptr; }
66+
const int *end() const { return nullptr; }
67+
};
68+
69+
#endif // TEST_INTEROP_CXX_STDLIB_INPUTS_CUSTOM_SEQUENCE_H
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module CustomSequence {
2+
header "custom-sequence.h"
3+
requires cplusplus
4+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// RUN: %target-typecheck-verify-swift -I %S/Inputs -enable-experimental-cxx-interop
2+
//
3+
// REQUIRES: OS=macosx || OS=linux-gnu
4+
5+
import CustomSequence
6+
import std
7+
8+
// === SimpleSequence ===
9+
10+
extension SimpleSequence.ConstIterator: UnsafeCxxInputIterator {}
11+
extension SimpleSequence: CxxSequence {}
12+
13+
func checkSimpleSequence() {
14+
let seq = SimpleSequence()
15+
let contains = seq.contains(where: { $0 == 3 })
16+
print(contains)
17+
18+
for item in seq {
19+
print(item)
20+
}
21+
}
22+
23+
// === SimpleArrayWrapper ===
24+
// No UnsafeCxxInputIterator conformance required, since the iterators are actually UnsafePointers here.
25+
extension SimpleArrayWrapper: CxxSequence {}
26+
27+
// === SimpleArrayWrapperNullableIterators ===
28+
// No UnsafeCxxInputIterator conformance required, since the iterators are actually optional UnsafePointers here.
29+
extension SimpleArrayWrapperNullableIterators: CxxSequence {}
30+
31+
// === SimpleEmptySequence ===
32+
// No UnsafeCxxInputIterator conformance required, since the iterators are actually optional UnsafePointers here.
33+
extension SimpleEmptySequence: CxxSequence {}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-experimental-cxx-interop)
2+
//
3+
// REQUIRES: executable_test
4+
// REQUIRES: OS=macosx || OS=linux-gnu
5+
6+
import StdlibUnittest
7+
import CustomSequence
8+
import std
9+
10+
var CxxSequenceTestSuite = TestSuite("CxxSequence")
11+
12+
extension SimpleSequence.ConstIterator: UnsafeCxxInputIterator {}
13+
extension SimpleSequence: CxxSequence {}
14+
15+
extension SimpleEmptySequence: CxxSequence {}
16+
17+
18+
CxxSequenceTestSuite.test("SimpleSequence as Swift.Sequence") {
19+
let seq = SimpleSequence()
20+
let contains = seq.contains(where: { $0 == 3 })
21+
expectTrue(contains)
22+
23+
var items: [Int32] = []
24+
for item in seq {
25+
items.append(item)
26+
}
27+
expectEqual([1, 2, 3, 4] as [Int32], items)
28+
}
29+
30+
CxxSequenceTestSuite.test("SimpleEmptySequence as Swift.Sequence") {
31+
let seq = SimpleEmptySequence()
32+
33+
var iterated = false
34+
for _ in seq {
35+
iterated = true
36+
}
37+
expectFalse(iterated)
38+
}
39+
40+
runAllTests()
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-cxx-interop -Xfrontend -validate-tbd-against-ir=none)
2+
//
3+
// REQUIRES: executable_test
4+
// REQUIRES: OS=macosx || OS=linux-gnu
5+
6+
import StdlibUnittest
7+
import std
8+
9+
var StdStringOverlayTestSuite = TestSuite("std::string overlay")
10+
11+
StdStringOverlayTestSuite.test("std::string <=> Swift.String") {
12+
let cxx1 = std.string()
13+
let swift1 = String(cxxString: cxx1)
14+
expectEqual(swift1, "")
15+
16+
let cxx2 = std.string("something123")
17+
let swift2 = String(cxxString: cxx2)
18+
expectEqual(swift2, "something123")
19+
}
20+
21+
extension std.string.const_iterator: UnsafeCxxInputIterator {
22+
// This func should not be required.
23+
public static func ==(lhs: std.string.const_iterator,
24+
rhs: std.string.const_iterator) -> Bool {
25+
#if os(Linux)
26+
// In libstdc++, `base()` returns UnsafePointer<Optional<UnsafePointer<CChar>>>.
27+
return lhs.base().pointee == rhs.base().pointee
28+
#else
29+
// In libc++, `base()` returns UnsafePointer<CChar>.
30+
return lhs.base() == rhs.base()
31+
#endif
32+
}
33+
}
34+
35+
extension std.string: CxxSequence {}
36+
37+
StdStringOverlayTestSuite.test("std::string as Swift.Sequence") {
38+
let cxx1 = std.string()
39+
var iterated = false
40+
for _ in cxx1 {
41+
iterated = true
42+
}
43+
expectFalse(iterated)
44+
45+
let cxx2 = std.string("abc123")
46+
var chars = 0
47+
var sum = 0
48+
for it in cxx2 {
49+
chars += 1
50+
sum += Int(it)
51+
}
52+
expectEqual(6, chars)
53+
expectEqual(97 + 98 + 99 + 49 + 50 + 51, sum)
54+
}
55+
56+
runAllTests()

test/Interop/Cxx/stdlib/overlay/string.swift

Lines changed: 0 additions & 21 deletions
This file was deleted.

validation-test/ParseableInterface/verify_all_overlays.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"DifferentiationUnittest",
4747
"Swift",
4848
"SwiftLang",
49+
"std", # swiftstd uses `-module-interface-preserve-types-as-written`
4950
]:
5051
continue
5152

0 commit comments

Comments
 (0)