Skip to content

Commit f0be52a

Browse files
committed
[cxx-interop] Add conversion to Bool for types that define operator bool()
C++ `operator bool()` is currently imported into Swift as `__convertToBool()`, which shouldn't be used by clients directly. This adds a new protocol into the C++ stdlib overlay: `CxxConvertibleToBool`, along with an intitializer for `Swift.Bool` taking an instance of `CxxConvertibleToBool`. rdar://115074954
1 parent 9f225d6 commit f0be52a

14 files changed

+137
-1
lines changed

include/swift/AST/KnownProtocols.def

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

109109
// C++ Standard Library Overlay:
110+
PROTOCOL(CxxConvertibleToBool)
110111
PROTOCOL(CxxConvertibleToCollection)
111112
PROTOCOL(CxxDictionary)
112113
PROTOCOL(CxxOptional)

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
11311131
case KnownProtocolKind::DistributedTargetInvocationResultHandler:
11321132
M = getLoadedModule(Id_Distributed);
11331133
break;
1134+
case KnownProtocolKind::CxxConvertibleToBool:
11341135
case KnownProtocolKind::CxxConvertibleToCollection:
11351136
case KnownProtocolKind::CxxDictionary:
11361137
case KnownProtocolKind::CxxPair:

lib/ClangImporter/ClangDerivedConformances.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,39 @@ void swift::conformToCxxIteratorIfNeeded(
571571
decl, {KnownProtocolKind::UnsafeCxxRandomAccessIterator});
572572
}
573573

574+
void swift::conformToCxxConvertibleToBoolIfNeeded(
575+
ClangImporter::Implementation &impl, swift::NominalTypeDecl *decl,
576+
const clang::CXXRecordDecl *clangDecl) {
577+
PrettyStackTraceDecl trace("conforming to CxxConvertibleToBool", decl);
578+
579+
assert(decl);
580+
assert(clangDecl);
581+
ASTContext &ctx = decl->getASTContext();
582+
583+
auto conversionId = ctx.getIdentifier("__convertToBool");
584+
auto conversions = lookupDirectWithoutExtensions(decl, conversionId);
585+
586+
// Find a non-mutating overload of `__convertToBool`.
587+
FuncDecl *conversion = nullptr;
588+
for (auto c : conversions) {
589+
auto candidate = dyn_cast<FuncDecl>(c);
590+
if (!candidate || candidate->isMutating())
591+
continue;
592+
if (conversion)
593+
// Overload ambiguity?
594+
return;
595+
conversion = candidate;
596+
}
597+
if (!conversion)
598+
return;
599+
auto conversionTy = conversion->getResultInterfaceType();
600+
if (!conversionTy->isBool())
601+
return;
602+
603+
impl.addSynthesizedProtocolAttrs(decl,
604+
{KnownProtocolKind::CxxConvertibleToBool});
605+
}
606+
574607
void swift::conformToCxxOptionalIfNeeded(
575608
ClangImporter::Implementation &impl, NominalTypeDecl *decl,
576609
const clang::CXXRecordDecl *clangDecl) {

lib/ClangImporter/ClangDerivedConformances.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ void conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl,
2828
NominalTypeDecl *decl,
2929
const clang::CXXRecordDecl *clangDecl);
3030

31+
/// If the decl defines `operator bool()`, synthesize a conformance to the
32+
/// CxxConvertibleToBool protocol, which is defined in the Cxx module.
33+
void conformToCxxConvertibleToBoolIfNeeded(
34+
ClangImporter::Implementation &impl, NominalTypeDecl *decl,
35+
const clang::CXXRecordDecl *clangDecl);
36+
3137
/// If the decl is an instantiation of C++ `std::optional`, synthesize a
3238
/// conformance to CxxOptional protocol, which is defined in the Cxx module.
3339
void conformToCxxOptionalIfNeeded(ClangImporter::Implementation &impl,

lib/ClangImporter/ImportDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2818,6 +2818,7 @@ namespace {
28182818
auto nominalDecl = cast<NominalTypeDecl>(result);
28192819
conformToCxxIteratorIfNeeded(Impl, nominalDecl, decl);
28202820
conformToCxxSequenceIfNeeded(Impl, nominalDecl, decl);
2821+
conformToCxxConvertibleToBoolIfNeeded(Impl, nominalDecl, decl);
28212822
conformToCxxSetIfNeeded(Impl, nominalDecl, decl);
28222823
conformToCxxDictionaryIfNeeded(Impl, nominalDecl, decl);
28232824
conformToCxxPairIfNeeded(Impl, nominalDecl, decl);

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6519,6 +6519,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
65196519
case KnownProtocolKind::DistributedTargetInvocationEncoder:
65206520
case KnownProtocolKind::DistributedTargetInvocationDecoder:
65216521
case KnownProtocolKind::DistributedTargetInvocationResultHandler:
6522+
case KnownProtocolKind::CxxConvertibleToBool:
65226523
case KnownProtocolKind::CxxConvertibleToCollection:
65236524
case KnownProtocolKind::CxxDictionary:
65246525
case KnownProtocolKind::CxxPair:

stdlib/public/Cxx/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ if(SWIFT_STDLIB_SUPPORT_BACK_DEPLOYMENT)
77
endif()
88

99
add_swift_target_library(swiftCxx STATIC NO_LINK_NAME IS_STDLIB IS_SWIFT_ONLY IS_FRAGILE
10+
CxxConvertibleToBool.swift
1011
CxxConvertibleToCollection.swift
1112
CxxDictionary.swift
1213
CxxPair.swift
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 can be converted to a Boolean value.
14+
///
15+
/// Any C++ type that defines `operator bool()` conforms to this protocol.
16+
public protocol CxxConvertibleToBool {
17+
/// Do not implement this function manually in Swift.
18+
func __convertToBool() -> Bool
19+
}
20+
21+
extension Bool {
22+
@inlinable
23+
public init<B: CxxConvertibleToBool>(fromCxx convertible: __shared B) {
24+
self = convertible.__convertToBool()
25+
}
26+
}

test/Interop/Cxx/operators/member-inline-module-interface.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// CHECK: mutating func callAsFunction(_ x: Int32, _ y: Int32) -> Int32
1010
// CHECK: }
1111

12-
// CHECK: struct LoadableBoolWrapper {
12+
// CHECK: struct LoadableBoolWrapper
1313
// CHECK: prefix static func ! (lhs: inout LoadableBoolWrapper) -> LoadableBoolWrapper
1414
// CHECK: func __convertToBool() -> Bool
1515
// CHECK: }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
struct BoolBox {
2+
bool value;
3+
4+
operator bool() const { return value; }
5+
};
6+
7+
struct NonConstBoolBox {
8+
bool value;
9+
10+
operator bool() { return value; }
11+
};
12+
13+
struct DualOverloadBoolBox {
14+
bool value;
15+
16+
operator bool() const { return value; }
17+
operator bool() { return value; }
18+
};

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
module ConvertibleToBool {
2+
header "convertible-to-bool.h"
3+
requires cplusplus
4+
}
5+
16
module CustomSequence {
27
header "custom-iterator.h" // TODO: extract into another module
38
header "custom-sequence.h"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %target-swift-ide-test -print-module -module-to-print=ConvertibleToBool -source-filename=x -I %S/Inputs -enable-experimental-cxx-interop | %FileCheck %s
2+
3+
// CHECK: struct BoolBox : CxxConvertibleToBool {
4+
// CHECK: }
5+
6+
// CHECK: struct NonConstBoolBox {
7+
// CHECK: }
8+
9+
// CHECK: struct DualOverloadBoolBox : CxxConvertibleToBool {
10+
// CHECK: }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// RUN: %target-typecheck-verify-swift -I %S/Inputs -enable-experimental-cxx-interop
2+
3+
import ConvertibleToBool
4+
5+
let _ = Bool(fromCxx: BoolBox())
6+
let _ = Bool(fromCxx: NonConstBoolBox()) // expected-error {{initializer 'init(fromCxx:)' requires that 'NonConstBoolBox' conform to 'CxxConvertibleToBool'}}
7+
let _ = Bool(fromCxx: DualOverloadBoolBox())
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-experimental-cxx-interop)
2+
3+
// REQUIRES: executable_test
4+
5+
import StdlibUnittest
6+
import ConvertibleToBool
7+
8+
var CxxConvertibleToBoolTestSuite = TestSuite("CxxConvertibleToBool")
9+
10+
CxxConvertibleToBoolTestSuite.test("BoolBox as CxxConvertibleToBool") {
11+
let b1 = BoolBox(value: true)
12+
expectTrue(Bool(fromCxx: b1))
13+
14+
let b2 = BoolBox(value: false)
15+
expectFalse(Bool(fromCxx: b2))
16+
}
17+
18+
CxxConvertibleToBoolTestSuite.test("DualOverloadBoolBox as CxxConvertibleToBool") {
19+
let b1 = DualOverloadBoolBox(value: true)
20+
expectTrue(Bool(fromCxx: b1))
21+
22+
let b2 = DualOverloadBoolBox(value: false)
23+
expectFalse(Bool(fromCxx: b2))
24+
}
25+
26+
runAllTests()

0 commit comments

Comments
 (0)