Skip to content

Commit 8f61c74

Browse files
authored
NSNull: fix === returning false when both operands are nil (#5486)
The current `===(NSNull?, NSNull?)` overload uses `guard let _ = lhs, let _ = rhs` and falls through to `return false` when either operand is nil. That makes `nil === nil` return false, contradicting the standard identity-operator contract. Two non-nil NSNull instances still compare ===, so the existing test_alwaysEqual case continues to pass. Replace the guard with a switch over the optional pair so the cases are explicit: (nil, nil) is true, mixed-nil is false, and two non-nil NSNull instances remain identical (preserving the singleton-like behavior). Add a matching !== overload so the symmetric operator agrees with === for the NSNull? signature. Without it, Swift's default !== for AnyObject? would return true for two distinct NSNull instances even though === says they are identical. Resolves #5248.
1 parent e04c57c commit 8f61c74

2 files changed

Lines changed: 39 additions & 8 deletions

File tree

Sources/Foundation/NSNull.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ open class NSNull : NSObject, NSCopying, NSSecureCoding, @unchecked Sendable {
4444
}
4545

4646
public func ===(lhs: NSNull?, rhs: NSNull?) -> Bool {
47-
guard let _ = lhs, let _ = rhs else { return false }
48-
return true
47+
switch (lhs, rhs) {
48+
case (nil, nil):
49+
return true
50+
case (nil, _), (_, nil):
51+
return false
52+
default:
53+
return true
54+
}
55+
}
56+
57+
public func !==(lhs: NSNull?, rhs: NSNull?) -> Bool {
58+
return !(lhs === rhs)
4959
}

Tests/Foundation/TestNSNull.swift

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,44 @@ class TestNSNull : XCTestCase {
1111
func test_alwaysEqual() {
1212
let null_1 = NSNull()
1313
let null_2 = NSNull()
14-
14+
1515
let null_3: NSNull? = NSNull()
1616
let null_4: NSNull? = nil
17-
17+
1818
//Check that any two NSNull's are ==
1919
XCTAssertEqual(null_1, null_2)
2020

2121
//Check that any two NSNull's are ===, preserving the singleton behavior
2222
XCTAssertTrue(null_1 === null_2)
23-
23+
XCTAssertFalse(null_1 !== null_2)
24+
2425
//Check that NSNull() == .Some(NSNull)
2526
XCTAssertEqual(null_1, null_3)
26-
27+
2728
//Make sure that NSNull() != .None
28-
XCTAssertNotEqual(null_1, null_4)
29+
XCTAssertNotEqual(null_1, null_4)
2930
}
30-
31+
32+
func test_identityOperator() {
33+
let null_1: NSNull? = NSNull()
34+
let null_2: NSNull? = NSNull()
35+
let null_nil: NSNull? = nil
36+
37+
//Check that two non-nil NSNull values are ===
38+
XCTAssertTrue(null_1 === null_2)
39+
XCTAssertFalse(null_1 !== null_2)
40+
41+
//Check that nil === nil for the NSNull? overload
42+
XCTAssertTrue(null_nil === null_nil)
43+
XCTAssertFalse(null_nil !== null_nil)
44+
45+
//Check that a non-nil NSNull is not === to nil in either order
46+
XCTAssertFalse(null_1 === null_nil)
47+
XCTAssertFalse(null_nil === null_1)
48+
XCTAssertTrue(null_1 !== null_nil)
49+
XCTAssertTrue(null_nil !== null_1)
50+
}
51+
3152
func test_description() {
3253
XCTAssertEqual(NSNull().description, "<null>")
3354
}

0 commit comments

Comments
 (0)