Skip to content

Commit 76cf339

Browse files
committed
Implement the first half of SE-0109: Remove the Boolean protocol
This removes conformance of DarwinBool and ObjCBool to the Boolean protocol, and makes the &&/||/! operators be concrete w.r.t. Bool instead of abstract on Boolean. This fixes some outstanding bugs w.r.t diagnostics, but exposes some cases where an existing diagnostic is not great. I'll fix that in a later patch (tracked by rdar://27391581).
1 parent e97ed13 commit 76cf339

File tree

13 files changed

+142
-239
lines changed

13 files changed

+142
-239
lines changed

include/swift/AST/Types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,9 @@ class alignas(1 << TypeAlignInBits) TypeBase {
585585
/// \brief Check if this type is equal to the empty tuple type.
586586
bool isVoid();
587587

588+
/// \brief Check if this type is equal to Swift.Bool.
589+
bool isBool();
590+
588591
/// \brief Check if this type is equal to Builtin.IntN.
589592
bool isBuiltinIntegerType(unsigned bitWidth);
590593

lib/AST/Type.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,9 +510,20 @@ bool TypeBase::isLegalSILType() {
510510
}
511511

512512
bool TypeBase::isVoid() {
513-
return isEqual(getASTContext().TheEmptyTupleType);
513+
if (auto TT = getAs<TupleType>())
514+
return TT->getNumElements() == 0;
515+
return false;
514516
}
515517

518+
/// \brief Check if this type is equal to Swift.Bool.
519+
bool TypeBase::isBool() {
520+
if (auto NTD = getAnyNominal())
521+
if (isa<StructDecl>(NTD))
522+
return getASTContext().getBoolDecl() == NTD;
523+
return false;
524+
}
525+
526+
516527
bool TypeBase::isAssignableType() {
517528
if (isLValueType()) return true;
518529
if (auto tuple = getAs<TupleType>()) {

lib/Sema/CSDiag.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3491,7 +3491,32 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
34913491
.highlight(expr->getSourceRange());
34923492
return true;
34933493
}
3494-
3494+
3495+
// If we're trying to convert something from optional type to Bool, then a
3496+
// comparision against nil was probably expected.
3497+
// TODO: It would be nice to handle "!x" --> x == false, but we have no way to
3498+
// get to the parent expr at present.
3499+
if (contextualType->isBool())
3500+
if (exprType->getAnyOptionalObjectType()) {
3501+
StringRef prefix = "((";
3502+
StringRef suffix = ") != nil)";
3503+
3504+
// Check if we need the inner parentheses.
3505+
// Technically we only need them if there's something in 'expr' with
3506+
// lower precedence than '!=', but the code actually comes out nicer
3507+
// in most cases with parens on anything non-trivial.
3508+
if (expr->canAppendCallParentheses()) {
3509+
prefix = prefix.drop_back();
3510+
suffix = suffix.drop_front();
3511+
}
3512+
// FIXME: The outer parentheses may be superfluous too.
3513+
3514+
diagnose(expr->getLoc(), diag::optional_used_as_boolean, exprType)
3515+
.fixItInsert(expr->getStartLoc(), prefix)
3516+
.fixItInsertAfter(expr->getEndLoc(), suffix);
3517+
return true;
3518+
}
3519+
34953520
exprType = exprType->getRValueType();
34963521

34973522
// Special case of some common conversions involving Swift.String

lib/Sema/CSSimplify.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2303,7 +2303,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
23032303

23042304
if (!shouldAttemptFixes())
23052305
return SolutionKind::Error;
2306-
2306+
#if 1
23072307
// See if there's anything we can do to fix the conformance:
23082308
OptionalTypeKind optionalKind;
23092309
if (auto optionalObjectType = type->getAnyOptionalObjectType(optionalKind)) {
@@ -2326,6 +2326,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
23262326
return result;
23272327
}
23282328
}
2329+
#endif
23292330

23302331
// There's nothing more we can do; fail.
23312332
return SolutionKind::Error;

stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ public func expectRandomAccessCollectionAssociatedTypes<X : RandomAccessCollecti
450450

451451
public func expectIsBooleanType<X : Boolean>(_ x: inout X) -> X { return x }
452452

453-
public struct AssertionResult : CustomStringConvertible, Boolean {
453+
public struct AssertionResult : CustomStringConvertible {
454454
init(isPass: Bool) {
455455
self._isPass = isPass
456456
}
@@ -490,13 +490,13 @@ public func expectUnreachableCatch(_ error: Error, ${TRACE}) {
490490
%for BoolType in ['Bool', 'AssertionResult']:
491491

492492
public func expectTrue(_ actual: ${BoolType}, ${TRACE}) {
493-
if !actual {
493+
if !actual.boolValue {
494494
expectationFailure("expected: true", trace: ${trace})
495495
}
496496
}
497497

498498
public func expectFalse(_ actual: ${BoolType}, ${TRACE}) {
499-
if actual {
499+
if actual.boolValue {
500500
expectationFailure("expected: false", trace: ${trace})
501501
}
502502
}

stdlib/public/Platform/Platform.swift

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public var noErr: OSStatus { return 0 }
2222
///
2323
/// The C type is a typedef for `unsigned char`.
2424
@_fixed_layout
25-
public struct DarwinBoolean : Boolean, ExpressibleByBooleanLiteral {
25+
public struct DarwinBoolean : ExpressibleByBooleanLiteral {
2626
var _value: UInt8
2727

2828
public init(_ value: Bool) {
@@ -66,26 +66,7 @@ func _convertBoolToDarwinBoolean(_ x: Bool) -> DarwinBoolean {
6666
}
6767
public // COMPILER_INTRINSIC
6868
func _convertDarwinBooleanToBool(_ x: DarwinBoolean) -> Bool {
69-
return Bool(x)
70-
}
71-
72-
// FIXME: We can't make the fully-generic versions @_transparent due to
73-
// rdar://problem/19418937, so here are some @_transparent overloads
74-
// for DarwinBoolean.
75-
@_transparent
76-
public func && <T : Boolean>(
77-
lhs: T,
78-
rhs: @autoclosure () -> DarwinBoolean
79-
) -> Bool {
80-
return lhs.boolValue ? rhs().boolValue : false
81-
}
82-
83-
@_transparent
84-
public func || <T : Boolean>(
85-
lhs: T,
86-
rhs: @autoclosure () -> DarwinBoolean
87-
) -> Bool {
88-
return lhs.boolValue ? true : rhs().boolValue
69+
return x.boolValue
8970
}
9071

9172
#endif

stdlib/public/SDK/ObjectiveC/ObjectiveC.swift

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ import ObjectiveC
1717
// Objective-C Primitive Types
1818
//===----------------------------------------------------------------------===//
1919

20-
public typealias Boolean = Swift.Boolean
2120
/// The Objective-C BOOL type.
2221
///
2322
/// On 64-bit iOS, the Objective-C BOOL type is a typedef of C/C++
2423
/// bool. Elsewhere, it is "signed char". The Clang importer imports it as
2524
/// ObjCBool.
2625
@_fixed_layout
27-
public struct ObjCBool : Boolean, ExpressibleByBooleanLiteral {
26+
public struct ObjCBool : ExpressibleByBooleanLiteral {
2827
#if os(OSX) || (os(iOS) && (arch(i386) || arch(arm)))
2928
// On OS X and 32-bit iOS, Objective-C's BOOL type is a "signed char".
3029
var _value: Int8
@@ -85,7 +84,7 @@ func _convertBoolToObjCBool(_ x: Bool) -> ObjCBool {
8584

8685
public // COMPILER_INTRINSIC
8786
func _convertObjCBoolToBool(_ x: ObjCBool) -> Bool {
88-
return Bool(x)
87+
return x.boolValue
8988
}
9089

9190
/// The Objective-C SEL type.
@@ -210,23 +209,6 @@ public var NO: ObjCBool {
210209
fatalError("can't retrieve unavailable property")
211210
}
212211

213-
// FIXME: We can't make the fully-generic versions @_transparent due to
214-
// rdar://problem/19418937, so here are some @_transparent overloads
215-
// for ObjCBool
216-
@_transparent
217-
public func && <T : Boolean>(
218-
lhs: T, rhs: @autoclosure () -> ObjCBool
219-
) -> Bool {
220-
return lhs.boolValue ? rhs().boolValue : false
221-
}
222-
223-
@_transparent
224-
public func || <T : Boolean>(
225-
lhs: T, rhs: @autoclosure () -> ObjCBool
226-
) -> Bool {
227-
return lhs.boolValue ? true : rhs().boolValue
228-
}
229-
230212
//===----------------------------------------------------------------------===//
231213
// NSObject
232214
//===----------------------------------------------------------------------===//

stdlib/public/core/Bool.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,79 @@ public prefix func !(a: Bool) -> Bool {
174174
public func ==(lhs: Bool, rhs: Bool) -> Bool {
175175
return Bool(Builtin.cmp_eq_Int1(lhs._value, rhs._value))
176176
}
177+
178+
179+
/// Performs a logical AND operation on two Bool values.
180+
///
181+
/// The logical AND operator (`&&`) combines two Bool values and returns
182+
/// `true` if both of the values are `true`. If either of the values is
183+
/// `false`, the operator returns `false`.
184+
///
185+
/// This operator uses short-circuit evaluation: The left-hand side (`lhs`) is
186+
/// evaluated first, and the right-hand side (`rhs`) is evaluated only if
187+
/// `lhs` evaluates to `true`. For example:
188+
///
189+
/// let measurements = [7.44, 6.51, 4.74, 5.88, 6.27, 6.12, 7.76]
190+
/// let sum = measurements.reduce(0, combine: +)
191+
///
192+
/// if measurements.count > 0 && sum / Double(measurements.count) < 6.5 {
193+
/// print("Average measurement is less than 6.5")
194+
/// }
195+
/// // Prints "Average measurement is less than 6.5"
196+
///
197+
/// In this example, `lhs` tests whether `measurements.count` is greater than
198+
/// zero. Evaluation of the `&&` operator is one of the following:
199+
///
200+
/// - When `measurements.count` is equal to zero, `lhs` evaluates to `false`
201+
/// and `rhs` is not evaluated, preventing a divide-by-zero error in the
202+
/// expression `sum / Double(measurements.count)`. The result of the
203+
/// operation is `false`.
204+
/// - When `measurements.count` is greater than zero, `lhs` evaluates to `true`
205+
/// and `rhs` is evaluated. The result of evaluating `rhs` is the result of
206+
/// the `&&` operation.
207+
///
208+
/// - Parameters:
209+
/// - lhs: The left-hand side of the operation.
210+
/// - rhs: The right-hand side of the operation.
211+
@inline(__always)
212+
public func &&(lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool{
213+
return lhs ? try rhs() : false
214+
}
215+
216+
/// Performs a logical OR operation on two Bool values.
217+
///
218+
/// The logical OR operator (`||`) combines two Bool values and returns
219+
/// `true` if at least one of the values is `true`. If both values are
220+
/// `false`, the operator returns `false`.
221+
///
222+
/// This operator uses short-circuit evaluation: The left-hand side (`lhs`) is
223+
/// evaluated first, and the right-hand side (`rhs`) is evaluated only if
224+
/// `lhs` evaluates to `false`. For example:
225+
///
226+
/// let majorErrors: Set = ["No first name", "No last name", ...]
227+
/// let error = ""
228+
///
229+
/// if error.isEmpty || !majorErrors.contains(error) {
230+
/// print("No major errors detected")
231+
/// } else {
232+
/// print("Major error: \(error)")
233+
/// }
234+
/// // Prints "No major errors detected")
235+
///
236+
/// In this example, `lhs` tests whether `error` is an empty string. Evaluation
237+
/// of the `||` operator is one of the following:
238+
///
239+
/// - When `error` is an empty string, `lhs` evaluates to `true` and `rhs` is
240+
/// not evaluated, skipping the call to `majorErrors.contains(_:)`. The
241+
/// result of the operation is `true`.
242+
/// - When `error` is not an empty string, `lhs` evaluates to `false` and `rhs`
243+
/// is evaluated. The result of evaluating `rhs` is the result of the `||`
244+
/// operation.
245+
///
246+
/// - Parameters:
247+
/// - lhs: The left-hand side of the operation.
248+
/// - rhs: The right-hand side of the operation.
249+
@inline(__always)
250+
public func ||(lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool{
251+
return lhs ? true : try rhs()
252+
}

0 commit comments

Comments
 (0)