Skip to content

Commit 2bd5da5

Browse files
committed
[stdlib] Fix Set/Dictionary casting issues
- Fix Set/Dictionary up/downcasting with String keys. - Improve error handling.
1 parent 39745a1 commit 2bd5da5

File tree

4 files changed

+131
-21
lines changed

4 files changed

+131
-21
lines changed

stdlib/public/core/DictionaryCasting.swift

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@
2121
public func _dictionaryUpCast<DerivedKey, DerivedValue, BaseKey, BaseValue>(
2222
_ source: Dictionary<DerivedKey, DerivedValue>
2323
) -> Dictionary<BaseKey, BaseValue> {
24-
var builder = _DictionaryBuilder<BaseKey, BaseValue>(count: source.count)
25-
24+
// String and NSString have different concepts of equality, so
25+
// NSString-keyed Dictionaries may generate key collisions when "upcasted"
26+
// to String. See rdar://problem/35995647
27+
let allowDuplicates = (BaseKey.self == String.self)
28+
var builder = _NativeDictionary<BaseKey, BaseValue>(capacity: source.count)
2629
for (k, v) in source {
27-
builder.add(key:k as! BaseKey, value: v as! BaseValue)
30+
builder.insertWithGuaranteedCapacity(
31+
key: k as! BaseKey,
32+
value: v as! BaseValue,
33+
allowingDuplicates: allowDuplicates)
2834
}
29-
return builder.take()
35+
return Dictionary(_native: builder)
3036
}
3137

3238
/// Called by the casting machinery.
@@ -66,7 +72,24 @@ public func _dictionaryDownCast<BaseKey, BaseValue, DerivedKey, DerivedValue>(
6672
_immutableCocoaDictionary: source._variant.asNative.bridged())
6773
}
6874
#endif
69-
return _dictionaryDownCastConditional(source)!
75+
76+
// Note: We can't delegate this call to _dictionaryDownCastConditional,
77+
// because we rely on as! to generate nice runtime errors when the downcast
78+
// fails.
79+
80+
// String and NSString have different concepts of equality, so
81+
// NSString-keyed Dictionaries may generate key collisions when downcasted
82+
// to String. See rdar://problem/35995647
83+
let allowDuplicates = (DerivedKey.self == String.self)
84+
var builder = _NativeDictionary<DerivedKey, DerivedValue>(
85+
capacity: source.count)
86+
for (k, v) in source {
87+
builder.insertWithGuaranteedCapacity(
88+
key: k as! DerivedKey,
89+
value: v as! DerivedValue,
90+
allowingDuplicates: allowDuplicates)
91+
}
92+
return Dictionary(_native: builder)
7093
}
7194

7295
/// Called by the casting machinery.
@@ -98,11 +121,20 @@ public func _dictionaryDownCastConditional<
98121
_ source: Dictionary<BaseKey, BaseValue>
99122
) -> Dictionary<DerivedKey, DerivedValue>? {
100123

101-
var builder = _DictionaryBuilder<DerivedKey, DerivedValue>(count: source.count)
124+
// String and NSString have different concepts of equality, so
125+
// NSString-keyed Dictionaries may generate key collisions when downcasted
126+
// to String. See rdar://problem/35995647
127+
let allowDuplicates = (DerivedKey.self == String.self)
128+
129+
var builder = _NativeDictionary<DerivedKey, DerivedValue>(
130+
capacity: source.count)
102131
for (k, v) in source {
103132
guard let k1 = k as? DerivedKey, let v1 = v as? DerivedValue
104133
else { return nil }
105-
builder.add(key: k1, value: v1)
134+
builder.insertWithGuaranteedCapacity(
135+
key: k1,
136+
value: v1,
137+
allowingDuplicates: allowDuplicates)
106138
}
107-
return builder.take()
139+
return Dictionary(_native: builder)
108140
}

stdlib/public/core/NativeDictionary.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,28 @@ internal func KEY_TYPE_OF_DICTIONARY_VIOLATES_HASHABLE_REQUIREMENTS(
460460
}
461461

462462
extension _NativeDictionary { // Insertions
463+
/// Insert a new element into uniquely held storage. Storage must be uniquely
464+
/// referenced with adequate capacity. If `allowingDuplicates` is false,
465+
/// `element` must not be already present in the dictionary.
466+
@_alwaysEmitIntoClient @inlinable // Introduced in 5.1
467+
internal mutating func insertWithGuaranteedCapacity(
468+
key: __owned Key,
469+
value: __owned Value,
470+
allowingDuplicates: Bool
471+
) {
472+
_precondition(count < capacity)
473+
if !allowingDuplicates {
474+
_unsafeInsertNew(key: key, value: value)
475+
return
476+
}
477+
let (bucket, found) = find(key)
478+
if found {
479+
(_values + bucket.offset).pointee = value
480+
} else {
481+
_insert(at: bucket, key: key, value: value)
482+
}
483+
}
484+
463485
/// Insert a new element into uniquely held storage.
464486
/// Storage must be uniquely referenced with adequate capacity.
465487
/// The `key` must not be already present in the Dictionary.

stdlib/public/core/NativeSet.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,20 @@ extension _NativeSet { // Low-level unchecked operations
117117
@inline(__always)
118118
internal func uncheckedInitialize(
119119
at bucket: Bucket,
120-
to element: __owned Element) {
120+
to element: __owned Element
121+
) {
121122
_internalInvariant(hashTable.isValid(bucket))
122123
(_elements + bucket.offset).initialize(to: element)
123124
}
125+
126+
@_alwaysEmitIntoClient @inlinable // Introduced in 5.1
127+
internal func uncheckedAssign(
128+
at bucket: Bucket,
129+
to element: __owned Element
130+
) {
131+
_internalInvariant(hashTable.isOccupied(bucket))
132+
(_elements + bucket.offset).pointee = element
133+
}
124134
}
125135

126136
extension _NativeSet { // Low-level lookup operations
@@ -346,6 +356,27 @@ internal func ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS(
346356
}
347357

348358
extension _NativeSet { // Insertions
359+
/// Insert a new element into uniquely held storage. Storage must be uniquely
360+
/// referenced with adequate capacity. If `allowingDuplicates` is false,
361+
/// `element` must not be already present in the set.
362+
@_alwaysEmitIntoClient @inlinable // Introduced in 5.1
363+
internal mutating func insertWithGuaranteedCapacity(
364+
_ element: __owned Element,
365+
allowingDuplicates: Bool
366+
) {
367+
_precondition(count < capacity)
368+
if !allowingDuplicates {
369+
_unsafeInsertNew(element)
370+
return
371+
}
372+
let (bucket, found) = find(element)
373+
if found {
374+
uncheckedAssign(at: bucket, to: element)
375+
} else {
376+
_unsafeInsertNew(element, at: bucket)
377+
}
378+
}
379+
349380
/// Insert a new element into uniquely held storage.
350381
/// Storage must be uniquely referenced with adequate capacity.
351382
/// The `element` must not be already present in the Set.

stdlib/public/core/SetCasting.swift

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@
1919
@inlinable
2020
public func _setUpCast<DerivedValue, BaseValue>(_ source: Set<DerivedValue>)
2121
-> Set<BaseValue> {
22-
var builder = _SetBuilder<BaseValue>(count: source.count)
23-
for x in source {
24-
builder.add(member: x as! BaseValue)
22+
// String and NSString have different concepts of equality, so Set<NSString>
23+
// may generate key collisions when "upcasted" to Set<String>.
24+
// See rdar://problem/35995647
25+
let allowDuplicates = (BaseValue.self == String.self)
26+
27+
var builder = _NativeSet<BaseValue>(capacity: source.count)
28+
for member in source {
29+
_ = builder.insertWithGuaranteedCapacity(
30+
member as! BaseValue,
31+
allowingDuplicates: allowDuplicates)
2532
}
26-
return builder.take()
33+
return Set(_native: builder)
2734
}
2835

2936
/// Called by the casting machinery.
@@ -54,7 +61,21 @@ public func _setDownCast<BaseValue, DerivedValue>(_ source: Set<BaseValue>)
5461
return Set(_immutableCocoaSet: source._variant.asNative.bridged())
5562
}
5663
#endif
57-
return _setDownCastConditional(source)!
64+
// We can't just delegate to _setDownCastConditional here because we rely on
65+
// `as!` to generate nice runtime errors when the downcast fails.
66+
67+
// String and NSString have different concepts of equality, so
68+
// NSString-keyed Sets may generate key collisions when downcasted
69+
// to String. See rdar://problem/35995647
70+
let allowDuplicates = (DerivedValue.self == String.self)
71+
72+
var builder = _NativeSet<DerivedValue>(capacity: source.count)
73+
for member in source {
74+
builder.insertWithGuaranteedCapacity(
75+
member as! DerivedValue,
76+
allowingDuplicates: allowDuplicates)
77+
}
78+
return Set(_native: builder)
5879
}
5980

6081
/// Called by the casting machinery.
@@ -81,13 +102,17 @@ internal func _setDownCastConditionalIndirect<SourceValue, TargetValue>(
81102
public func _setDownCastConditional<BaseValue, DerivedValue>(
82103
_ source: Set<BaseValue>
83104
) -> Set<DerivedValue>? {
84-
var result = Set<DerivedValue>(minimumCapacity: source.count)
105+
// String and NSString have different concepts of equality, so
106+
// NSString-keyed Sets may generate key collisions when downcasted
107+
// to String. See rdar://problem/35995647
108+
let allowDuplicates = (DerivedValue.self == String.self)
109+
110+
var builder = _NativeSet<DerivedValue>(capacity: source.count)
85111
for member in source {
86-
if let derivedMember = member as? DerivedValue {
87-
result.insert(derivedMember)
88-
continue
89-
}
90-
return nil
112+
guard let derivedMember = member as? DerivedValue else { return nil }
113+
_ = builder.insertWithGuaranteedCapacity(
114+
derivedMember,
115+
allowingDuplicates: allowDuplicates)
91116
}
92-
return result
117+
return Set(_native: builder)
93118
}

0 commit comments

Comments
 (0)