Skip to content

Commit 4751b8f

Browse files
committed
[cxx-interop] Add CxxStack protocol for std::stack ergonomics.
1 parent 22a3ec0 commit 4751b8f

File tree

11 files changed

+216
-0
lines changed

11 files changed

+216
-0
lines changed

include/swift/AST/KnownProtocols.def

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ PROTOCOL(CxxSet)
137137
PROTOCOL(CxxRandomAccessCollection)
138138
PROTOCOL(CxxMutableRandomAccessCollection)
139139
PROTOCOL(CxxSequence)
140+
PROTOCOL(CxxStack)
140141
PROTOCOL(CxxUniqueSet)
141142
PROTOCOL(CxxVector)
142143
PROTOCOL(CxxSpan)

lib/AST/ASTContext.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
14751475
case KnownProtocolKind::CxxMutableRandomAccessCollection:
14761476
case KnownProtocolKind::CxxSet:
14771477
case KnownProtocolKind::CxxSequence:
1478+
case KnownProtocolKind::CxxStack:
14781479
case KnownProtocolKind::CxxUniqueSet:
14791480
case KnownProtocolKind::CxxVector:
14801481
case KnownProtocolKind::CxxSpan:

lib/ClangImporter/ClangDerivedConformances.cpp

+39
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,45 @@ void swift::conformToCxxSequenceIfNeeded(
865865
}
866866
}
867867

868+
void swift::conformToCxxStackIfNeeded(
869+
ClangImporter::Implementation &impl, NominalTypeDecl *decl,
870+
const clang::CXXRecordDecl *clangDecl) {
871+
PrettyStackTraceDecl trace("conforming to CxxStack", decl);
872+
assert(decl);
873+
assert(clangDecl);
874+
ASTContext &ctx = decl->getASTContext();
875+
if (!isStdDecl(clangDecl, {"stack"}))
876+
return;
877+
auto valueType = lookupDirectSingleWithoutExtensions<TypeAliasDecl>(
878+
decl, ctx.getIdentifier("value_type"));
879+
auto sizeType = lookupDirectSingleWithoutExtensions<TypeAliasDecl>(
880+
decl, ctx.getIdentifier("size_type"));
881+
if (!valueType ||!sizeType)
882+
return;
883+
impl.addSynthesizedTypealias(decl, ctx.Id_Element,
884+
valueType->getUnderlyingType());
885+
impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"),
886+
sizeType->getUnderlyingType());
887+
888+
ProtocolDecl *cxxInputIteratorProto =
889+
ctx.getProtocol(KnownProtocolKind::UnsafeCxxInputIterator);
890+
if (!cxxInputIteratorProto)
891+
return;
892+
893+
auto rawReferenceType = lookupDirectSingleWithoutExtensions<TypeAliasDecl>(
894+
decl, ctx.getIdentifier("const_reference"));
895+
if (!rawReferenceType)
896+
return;
897+
898+
auto rawReferenceTy = rawReferenceType->getUnderlyingType();
899+
900+
if (!checkConformance(rawReferenceTy, cxxInputIteratorProto))
901+
return;
902+
903+
impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawReference"), rawReferenceTy);
904+
impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxStack});
905+
}
906+
868907
static bool isStdSetType(const clang::CXXRecordDecl *clangDecl) {
869908
return isStdDecl(clangDecl, {"set", "unordered_set", "multiset"});
870909
}

lib/ClangImporter/ClangDerivedConformances.h

+6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ void conformToCxxSequenceIfNeeded(ClangImporter::Implementation &impl,
4646
NominalTypeDecl *decl,
4747
const clang::CXXRecordDecl *clangDecl);
4848

49+
/// If the decl is an instantiation of C++ `std::stack`,
50+
/// synthesize a conformance to CxxStack, which is defined in the Cxx module.
51+
void conformToCxxStackIfNeeded(ClangImporter::Implementation &impl,
52+
NominalTypeDecl *decl,
53+
const clang::CXXRecordDecl *clangDecl);
54+
4955
/// If the decl is an instantiation of C++ `std::set`, `std::unordered_set` or
5056
/// `std::multiset`, synthesize a conformance to CxxSet, which is defined in the
5157
/// Cxx module.

lib/ClangImporter/ImportDecl.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -3156,6 +3156,7 @@ namespace {
31563156
conformToCxxVectorIfNeeded(Impl, nominalDecl, decl);
31573157
conformToCxxFunctionIfNeeded(Impl, nominalDecl, decl);
31583158
conformToCxxSpanIfNeeded(Impl, nominalDecl, decl);
3159+
conformToCxxStackIfNeeded(Impl, nominalDecl, decl);
31593160
}
31603161
}
31613162

lib/IRGen/GenMeta.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -7128,6 +7128,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
71287128
case KnownProtocolKind::CxxMutableRandomAccessCollection:
71297129
case KnownProtocolKind::CxxSet:
71307130
case KnownProtocolKind::CxxSequence:
7131+
case KnownProtocolKind::CxxStack:
71317132
case KnownProtocolKind::CxxUniqueSet:
71327133
case KnownProtocolKind::CxxVector:
71337134
case KnownProtocolKind::CxxSpan:

stdlib/public/Cxx/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_swift_target_library(swiftCxx STATIC NO_LINK_NAME IS_STDLIB IS_SWIFT_ONLY
1515
CxxSet.swift
1616
CxxRandomAccessCollection.swift
1717
CxxSequence.swift
18+
CxxStack.swift
1819
CxxVector.swift
1920
CxxSpan.swift
2021
UnsafeCxxIterators.swift

stdlib/public/Cxx/CxxStack.swift

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
/// A C++ type that represents a stack.
14+
///
15+
/// C++ standard library types `std::stack` conforms to this protocol.
16+
public protocol CxxStack<Element> {
17+
associatedtype Element
18+
associatedtype Size: BinaryInteger
19+
associatedtype RawReference: UnsafeCxxInputIterator
20+
where RawReference.Pointee == Element
21+
22+
/// Do not implement this function manually in Swift.
23+
func empty() -> Bool
24+
/// Do not implement this function manually in Swift.
25+
func size() -> Size
26+
27+
/// Do not implement this function manually in Swift.
28+
func __topUnsafe() -> RawReference
29+
30+
/// Do not implement this function manually in Swift.
31+
mutating func pop()
32+
33+
/// Do not implement this function manually in Swift.
34+
mutating func push(_ element: Element)
35+
}
36+
37+
public extension CxxStack {
38+
@inlinable var count: Int { Int(self.size()) }
39+
@inlinable var isEmpty: Bool { self.empty() }
40+
41+
@inlinable
42+
func top() -> Element? {
43+
if self.empty() {
44+
return nil
45+
}
46+
return self.__topUnsafe().pointee
47+
}
48+
49+
@inlinable
50+
@discardableResult
51+
mutating func safePop() -> Element? {
52+
if self.empty() {
53+
return nil
54+
}
55+
let result: Element = self.__topUnsafe().pointee
56+
self.pop()
57+
return result
58+
}
59+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ module StdStringView {
5252
export *
5353
}
5454

55+
module StdStack {
56+
header "std-stack.h"
57+
requires cplusplus
58+
export *
59+
}
60+
5561
module StdPair {
5662
header "std-pair.h"
5763
requires cplusplus
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#ifndef TEST_INTEROP_CXX_STDLIB_INPUTS_STD_STACK_H
2+
#define TEST_INTEROP_CXX_STDLIB_INPUTS_STD_STACK_H
3+
4+
#include <stack>
5+
6+
using StackOfInt = std::stack<int>;
7+
8+
StackOfInt initStackOfCInt() {
9+
StackOfInt s;
10+
s.push(1);
11+
s.push(2);
12+
s.push(3);
13+
return s;
14+
}
15+
16+
StackOfInt initEmptyStackOfCInt() {
17+
StackOfInt s;
18+
return s;
19+
}
20+
21+
#endif // TEST_INTEROP_CXX_STDLIB_INPUTS_STD_STACK_H
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: %target-run-simple-swift(-I %S/Inputs -cxx-interoperability-mode=swift-6)
2+
// RUN: %target-run-simple-swift(-I %S/Inputs -cxx-interoperability-mode=upcoming-swift)
3+
// RUN: %target-run-simple-swift(-I %S/Inputs -cxx-interoperability-mode=upcoming-swift -Xcc -std=c++14)
4+
// RUN: %target-run-simple-swift(-I %S/Inputs -cxx-interoperability-mode=upcoming-swift -Xcc -std=c++17)
5+
// RUN: %target-run-simple-swift(-I %S/Inputs -cxx-interoperability-mode=upcoming-swift -Xcc -std=c++20)
6+
7+
// Also test this with a bridging header instead of the StdSet module.
8+
// RUN: %empty-directory(%t2)
9+
// RUN: cp %S/Inputs/std-stack.h %t2/std-stack-bridging-header.h
10+
// RUN: %target-run-simple-swift(-D BRIDGING_HEADER -import-objc-header %t2/std-stack-bridging-header.h -cxx-interoperability-mode=swift-6)
11+
// RUN: %target-run-simple-swift(-D BRIDGING_HEADER -import-objc-header %t2/std-stack-bridging-header.h -cxx-interoperability-mode=upcoming-swift)
12+
13+
// REQUIRES: executable_test
14+
//
15+
// Enable this everywhere once we have a solution for modularizing other C++ stdlibs: rdar://87654514
16+
// REQUIRES: OS=macosx || OS=linux-gnu
17+
18+
import StdlibUnittest
19+
#if !BRIDGING_HEADER
20+
import StdStack
21+
#endif
22+
import CxxStdlib
23+
24+
var StdStackTestSuite = TestSuite("StdStack")
25+
26+
StdStackTestSuite.test("stack count") {
27+
let s1 = initStackOfCInt()
28+
expectEqual(s1.count, 3)
29+
30+
let s2 = initEmptyStackOfCInt()
31+
expectEqual(s2.count, 0)
32+
}
33+
34+
StdStackTestSuite.test("stack isEmpty") {
35+
let s1 = initStackOfCInt()
36+
expectFalse(s1.isEmpty)
37+
38+
let s2 = initEmptyStackOfCInt()
39+
expectTrue(s2.isEmpty)
40+
}
41+
42+
StdStackTestSuite.test("stack top") {
43+
let s1 = initStackOfCInt()
44+
expectEqual(s1.top(), 3)
45+
46+
let s2 = initEmptyStackOfCInt()
47+
expectNil(s2.top())
48+
}
49+
50+
StdStackTestSuite.test("stack push") {
51+
var s1 = initStackOfCInt()
52+
53+
s1.push(4)
54+
expectEqual(s1.top(), 4)
55+
expectEqual(s1.count, 4)
56+
s1.safePop()
57+
expectEqual(s1.top(), 3)
58+
expectEqual(s1.count, 3)
59+
60+
var s2 = initEmptyStackOfCInt()
61+
62+
s2.push(4)
63+
expectEqual(s2.top(), 4)
64+
expectEqual(s2.count, 1)
65+
}
66+
67+
StdStackTestSuite.test("stack safePop") {
68+
var s1 = initStackOfCInt()
69+
70+
expectEqual(s1.safePop(), 3)
71+
expectEqual(s1.safePop(), 2)
72+
expectEqual(s1.safePop(), 1)
73+
expectNil(s1.safePop())
74+
expectTrue(s1.isEmpty)
75+
76+
var s2 = initEmptyStackOfCInt()
77+
expectNil(s2.safePop())
78+
}
79+
80+
runAllTests()

0 commit comments

Comments
 (0)