Skip to content

Commit 5b3f2af

Browse files
authored
Merge pull request #3756 from jckarter/turn-on-anyhashable
Turn on AnyHashable
2 parents a18d490 + cac313d commit 5b3f2af

23 files changed

+167
-56
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ Note: This is in reverse chronological order, so newer entries are added to the
33
Swift 3.0
44
---------
55

6+
* [SE-0131](https://github.com/apple/swift-evolution/blob/master/proposals/0131-anyhashable.md):
7+
The standard library provides a new type `AnyHashable` for use in heterogenous
8+
hashed collections. Untyped `NSDictionary` and `NSSet` APIs from Objective-C
9+
now import as `[AnyHashable: Any]` and `Set<AnyHashable>`.
10+
611
* [SE-0102](https://github.com/apple/swift-evolution/blob/master/proposals/0102-noreturn-bottom-type.md)
712
The `@noreturn` attribute on function declarations and function types has been removed,
813
in favor of an empty `Never` type:
@@ -18,7 +23,7 @@ Swift 3.0
1823
* [SE-0116](https://github.com/apple/swift-evolution/blob/master/proposals/0116-id-as-any.md):
1924
Objective-C APIs using `id` now import into Swift as `Any` instead of as `AnyObject`.
2025
Similarly, APIs using untyped `NSArray` and `NSDictionary` import as `[Any]` and
21-
`[NSObject: Any]`, respectively.
26+
`[AnyHashable: Any]`, respectively.
2227

2328
* [SE-0072](https://github.com/apple/swift-evolution/blob/master/proposals/0072-eliminate-implicit-bridging-conversions.md):
2429
Bridging conversions are no longer implicit. The conversion from a Swift value type to

include/swift/AST/ASTContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ class ASTContext {
401401
/// Retrieve the declaration of Swift.Dictionary<K, V>.
402402
NominalTypeDecl *getDictionaryDecl() const;
403403

404+
/// Retrieve the declaration of Swift.AnyHashable.
405+
NominalTypeDecl *getAnyHashableDecl() const;
406+
404407
/// Retrieve the declaration of Swift.Optional or ImplicitlyUnwrappedOptional.
405408
EnumDecl *getOptionalDecl(OptionalTypeKind kind) const;
406409

include/swift/SIL/SILBuilder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ class SILBuilder {
424424
}
425425

426426
LoadInst *createLoad(SILLocation Loc, SILValue LV) {
427+
assert(LV->getType().isLoadable(F.getModule()));
427428
return insert(new (F.getModule())
428429
LoadInst(getSILDebugLocation(Loc), LV));
429430
}

lib/AST/ASTContext.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ struct ASTContext::Implementation {
138138
/// The declaration of Swift.Dictionary<T>.
139139
NominalTypeDecl *DictionaryDecl = nullptr;
140140

141+
/// The declaration of Swift.AnyHashable.
142+
NominalTypeDecl *AnyHashableDecl = nullptr;
143+
141144
/// The declaration of Swift.Optional<T>.
142145
EnumDecl *OptionalDecl = nullptr;
143146

@@ -637,6 +640,12 @@ NominalTypeDecl *ASTContext::getDictionaryDecl() const {
637640
return Impl.DictionaryDecl;
638641
}
639642

643+
NominalTypeDecl *ASTContext::getAnyHashableDecl() const {
644+
if (!Impl.AnyHashableDecl)
645+
Impl.AnyHashableDecl = findStdlibType(*this, "AnyHashable", 0);
646+
return Impl.AnyHashableDecl;
647+
}
648+
640649
EnumDecl *ASTContext::getOptionalDecl(OptionalTypeKind kind) const {
641650
switch (kind) {
642651
case OTK_None:
@@ -4011,6 +4020,7 @@ bool ASTContext::isStandardLibraryTypeBridgedInFoundation(
40114020
nominal == getSetDecl() ||
40124021
nominal == getStringDecl() ||
40134022
nominal == getErrorDecl() ||
4023+
nominal == getAnyHashableDecl() ||
40144024
// Weird one-off case where CGFloat is bridged to NSNumber.
40154025
nominal->getName() == Id_CGFloat);
40164026
}

lib/ClangImporter/ImportType.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -919,12 +919,18 @@ namespace {
919919
return Type();
920920

921921
// The first type argument for Dictionary or Set needs
922-
// to be NSObject-bound.
922+
// to be Hashable. Everything that inherits NSObject has a
923+
// -hash code in ObjC, but if something isn't NSObject, fall back
924+
// to AnyHashable as a key type.
923925
if (unboundDecl == Impl.SwiftContext.getDictionaryDecl() ||
924926
unboundDecl == Impl.SwiftContext.getSetDecl()) {
925927
auto &keyType = importedTypeArgs[0];
926-
if (!Impl.matchesNSObjectBound(keyType))
927-
keyType = Impl.getNSObjectType();
928+
if (!Impl.matchesNSObjectBound(keyType)) {
929+
if (auto anyHashable = Impl.SwiftContext.getAnyHashableDecl())
930+
keyType = anyHashable->getDeclaredType();
931+
else
932+
keyType = Type();
933+
}
928934
}
929935

930936
// Form the specialized type.
@@ -2635,6 +2641,7 @@ bool ClangImporter::Implementation::matchesNSObjectBound(Type type) {
26352641
return true;
26362642

26372643
// Struct or enum type must have been bridged.
2644+
// TODO: Check that the bridged type is Hashable?
26382645
if (type->getStructOrBoundGenericStruct() ||
26392646
type->getEnumOrBoundGenericEnum())
26402647
return true;

lib/ClangImporter/ImporterImpl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1160,7 +1160,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
11601160
Type getCFStringRefType();
11611161

11621162
/// \brief Determines whether the given type matches an implicit type
1163-
/// bound of "NSObject", which is used to validate NSDictionary/NSSet.
1163+
/// bound of "Hashable", which is used to validate NSDictionary/NSSet.
11641164
bool matchesNSObjectBound(Type type);
11651165

11661166
/// \brief Look up and attempt to import a Clang declaration with

lib/SIL/SILModule.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,8 @@ SILFunction *SILModule::getOrCreateFunction(SILLocation loc,
316316

317317
if (auto fn = lookUpFunction(name)) {
318318
assert(fn->getLoweredFunctionType() == constantType);
319-
assert(fn->getLinkage() == linkage);
319+
assert(fn->getLinkage() == constant.getLinkage(ForDefinition)
320+
|| fn->getLinkage() == constant.getLinkage(NotForDefinition));
320321
if (forDefinition) {
321322
// In all the cases where getConstantLinkage returns something
322323
// different for ForDefinition, it returns an available-externally

lib/SILOptimizer/Utils/Local.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,6 +1426,11 @@ optimizeBridgedObjCToSwiftCast(SILInstruction *Inst,
14261426
assert(Src->getType().isAddress() && "Source should have an address type");
14271427
assert(Dest->getType().isAddress() && "Source should have an address type");
14281428

1429+
if (!Src->getType().isLoadable(M) || !Dest->getType().isLoadable(M)) {
1430+
// TODO: Handle address only types.
1431+
return nullptr;
1432+
}
1433+
14291434
if (SILBridgedTy != Src->getType()) {
14301435
// Check if we can simplify a cast into:
14311436
// - ObjCTy to _ObjectiveCBridgeable._ObjectiveCType.
@@ -1603,6 +1608,11 @@ optimizeBridgedSwiftToObjCCast(SILInstruction *Inst,
16031608

16041609
auto &M = Inst->getModule();
16051610
auto Loc = Inst->getLoc();
1611+
1612+
if (!Src->getType().isLoadable(M) || !Dest->getType().isLoadable(M)) {
1613+
// TODO: Handle address-only types.
1614+
return nullptr;
1615+
}
16061616

16071617
// Find the _BridgedToObjectiveC protocol.
16081618
auto BridgedProto =

lib/Sema/TypeCheckType.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2370,9 +2370,11 @@ Type TypeResolver::resolveTupleType(TupleTypeRepr *repr,
23702370
complained = true;
23712371
}
23722372

2373-
// Single-element labeled tuples are not permitted, either.
2374-
if (elements.size() == 1 && elements[0].hasName() &&
2375-
!(options & TR_EnumCase)) {
2373+
// Single-element labeled tuples are not permitted outside of declarations
2374+
// or SIL, either.
2375+
if (elements.size() == 1 && elements[0].hasName()
2376+
&& !(options & TR_SILType)
2377+
&& !(options & TR_EnumCase)) {
23762378
if (!complained) {
23772379
auto named = cast<NamedTypeRepr>(repr->getElement(0));
23782380
TC.diagnose(repr->getElement(0)->getStartLoc(),

stdlib/public/SDK/CoreImage/CoreImage.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ extension CIFilter {
5050
extension CISampler {
5151
// - (id)initWithImage:(CIImage *)im keysAndValues:key0, ...;
5252
convenience init(im: CIImage, elements: (String, Any)...) {
53-
var dict: [NSObject : Any] = [:]
53+
var dict: [AnyHashable : Any] = [:]
5454
for (key, value) in elements {
55-
dict[key as NSObject] = value
55+
dict[key] = value
5656
}
5757

5858
// @objc(initWithImage:options:)

stdlib/public/SDK/Foundation/Foundation.swift

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ extension NSString : ExpressibleByStringLiteral {
7272
}
7373

7474
extension NSString : _HasCustomAnyHashableRepresentation {
75+
// Must be @nonobjc to prevent infinite recursion trying to bridge
76+
// AnyHashable to NSObject.
77+
@nonobjc
7578
public func _toCustomAnyHashable() -> AnyHashable? {
7679
// Consistently use Swift equality and hashing semantics for all strings.
7780
return AnyHashable(self as String)
@@ -429,6 +432,9 @@ extension NSNumber
429432
}
430433

431434
extension NSNumber : _HasCustomAnyHashableRepresentation {
435+
// Must be @nonobjc to prevent infinite recursion trying to bridge
436+
// AnyHashable to NSObject.
437+
@nonobjc
432438
public func _toCustomAnyHashable() -> AnyHashable? {
433439
guard let kind = _SwiftTypePreservingNSNumberTag(
434440
rawValue: Int(_swift_Foundation_TypePreservingNSNumberGetKind(self))
@@ -1231,7 +1237,8 @@ extension NSSet {
12311237
/// receiver.
12321238
@objc(_swiftInitWithSet_NSSet:)
12331239
public convenience init(set anSet: NSSet) {
1234-
self.init(set: anSet as Set)
1240+
// FIXME: This is a bit weird. Maybe there's a better way?
1241+
self.init(set: anSet as Set<NSObject> as Set<AnyHashable>)
12351242
}
12361243
}
12371244

@@ -1244,7 +1251,9 @@ extension NSDictionary {
12441251
/// found in `otherDictionary`.
12451252
@objc(_swiftInitWithDictionary_NSDictionary:)
12461253
public convenience init(dictionary otherDictionary: NSDictionary) {
1247-
self.init(dictionary: otherDictionary as Dictionary)
1254+
// FIXME: This is a bit weird. Maybe there's a better way?
1255+
self.init(dictionary: otherDictionary as [NSObject: AnyObject]
1256+
as [AnyHashable: Any])
12481257
}
12491258
}
12501259

@@ -1495,3 +1504,39 @@ typealias KeyedUnarchiver = NSKeyedUnarchiver
14951504
@available(*, deprecated, renamed:"NSKeyedArchiver", message: "Please use NSKeyedArchiver")
14961505
typealias KeyedArchiver = NSKeyedArchiver
14971506

1507+
//===----------------------------------------------------------------------===//
1508+
// AnyHashable
1509+
//===----------------------------------------------------------------------===//
1510+
1511+
extension AnyHashable : _ObjectiveCBridgeable {
1512+
public func _bridgeToObjectiveC() -> NSObject {
1513+
// This is unprincipled, but pretty much any object we'll encounter in
1514+
// Swift is NSObject-conforming enough to have -hash and -isEqual:.
1515+
return unsafeBitCast(base as AnyObject, to: NSObject.self)
1516+
}
1517+
1518+
public static func _forceBridgeFromObjectiveC(
1519+
_ x: NSObject,
1520+
result: inout AnyHashable?
1521+
) {
1522+
result = AnyHashable(x)
1523+
}
1524+
1525+
public static func _conditionallyBridgeFromObjectiveC(
1526+
_ x: NSObject,
1527+
result: inout AnyHashable?
1528+
) -> Bool {
1529+
self._forceBridgeFromObjectiveC(x, result: &result)
1530+
return result != nil
1531+
}
1532+
1533+
public static func _unconditionallyBridgeFromObjectiveC(
1534+
_ source: NSObject?
1535+
) -> AnyHashable {
1536+
// `nil` has historically been used as a stand-in for an empty
1537+
// string; map it to an empty string.
1538+
if _slowPath(source == nil) { return AnyHashable(String()) }
1539+
return AnyHashable(source!)
1540+
}
1541+
}
1542+

stdlib/public/SDK/Foundation/NSError.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,11 @@ public protocol _BridgedStoredNSError :
389389
}
390390

391391
/// TODO: Better way to do this?
392-
internal func _stringDictToNSObjectDict(_ input: [String : Any])
393-
-> [NSObject : Any] {
394-
var result: [NSObject : Any] = [:]
392+
internal func _stringDictToAnyHashableDict(_ input: [String : Any])
393+
-> [AnyHashable : Any] {
394+
var result: [AnyHashable : Any] = [:]
395395
for (k, v) in input {
396-
result[k as NSString] = v
396+
result[k] = v
397397
}
398398
return result
399399
}
@@ -411,7 +411,7 @@ public extension _BridgedStoredNSError
411411
public init(_ code: Code, userInfo: [String : Any] = [:]) {
412412
self.init(_nsError: NSError(domain: Self._nsErrorDomain,
413413
code: numericCast(code.rawValue),
414-
userInfo: _stringDictToNSObjectDict(userInfo)))
414+
userInfo: _stringDictToAnyHashableDict(userInfo)))
415415
}
416416

417417
/// The user-info dictionary for an error that was bridged from
@@ -432,7 +432,7 @@ public extension _BridgedStoredNSError
432432
public init(_ code: Code, userInfo: [String : Any] = [:]) {
433433
self.init(_nsError: NSError(domain: Self._nsErrorDomain,
434434
code: numericCast(code.rawValue),
435-
userInfo: _stringDictToNSObjectDict(userInfo)))
435+
userInfo: _stringDictToAnyHashableDict(userInfo)))
436436
}
437437
}
438438

@@ -461,7 +461,7 @@ public extension _BridgedStoredNSError {
461461
var errorUserInfo: [String : Any] {
462462
var result: [String : Any] = [:]
463463
for (key, value) in _nsError.userInfo {
464-
guard let stringKey = key as? String else { continue }
464+
guard let stringKey = key.base as? String else { continue }
465465
result[stringKey] = value
466466
}
467467
return result
@@ -528,7 +528,7 @@ public struct CocoaError : _BridgedStoredNSError {
528528
}
529529

530530
public extension CocoaError {
531-
private var _nsUserInfo: [NSObject : Any] {
531+
private var _nsUserInfo: [AnyHashable : Any] {
532532
return (self as NSError).userInfo
533533
}
534534

@@ -1329,7 +1329,7 @@ public struct URLError : _BridgedStoredNSError {
13291329
}
13301330

13311331
public extension URLError {
1332-
private var _nsUserInfo: [NSObject : Any] {
1332+
private var _nsUserInfo: [AnyHashable : Any] {
13331333
return (self as NSError).userInfo
13341334
}
13351335

stdlib/public/SDK/Foundation/NSStringAPI.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,8 @@ extension String {
11801180
/// values found in the `String`.
11811181
public
11821182
func propertyListFromStringsFileFormat() -> [String : String] {
1183-
return _ns.propertyListFromStringsFileFormat() as! [String : String]
1183+
return _ns.propertyListFromStringsFileFormat()! as [NSObject : AnyObject]
1184+
as! [String : String]
11841185
}
11851186

11861187
// - (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)aSet

test/1_stdlib/ErrorBridged.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ ErrorBridgingTests.test("NSError-to-enum bridging") {
9797
let ns = NSError(domain: NSCocoaErrorDomain,
9898
code: NSFileNoSuchFileError,
9999
userInfo: [
100-
NSFilePathErrorKey as NSObject : "/dev/null",
101-
NSStringEncodingErrorKey as NSObject: /*ASCII=*/1,
102-
NSUnderlyingErrorKey as NSObject: underlyingError,
103-
NSURLErrorKey as NSObject: testURL
100+
AnyHashable(NSFilePathErrorKey): "/dev/null",
101+
AnyHashable(NSStringEncodingErrorKey): /*ASCII=*/1,
102+
AnyHashable(NSUnderlyingErrorKey): underlyingError,
103+
AnyHashable(NSURLErrorKey): testURL
104104
])
105105

106106
objc_setAssociatedObject(ns, &CanaryHandle, NoisyError(),
@@ -143,7 +143,7 @@ ErrorBridgingTests.test("NSError-to-enum bridging") {
143143
// URLError domain
144144
let nsURL = NSError(domain: NSURLErrorDomain,
145145
code: NSURLErrorBadURL,
146-
userInfo: [NSURLErrorFailingURLErrorKey as NSObject : testURL])
146+
userInfo: [AnyHashable(NSURLErrorFailingURLErrorKey): testURL])
147147
let eURL: Error = nsURL
148148
let isBadURLError: Bool
149149
switch eURL {
@@ -162,7 +162,7 @@ ErrorBridgingTests.test("NSError-to-enum bridging") {
162162
// CoreLocation error domain
163163
let nsCL = NSError(domain: kCLErrorDomain,
164164
code: CLError.headingFailure.rawValue,
165-
userInfo: [NSURLErrorKey as NSObject: testURL])
165+
userInfo: [AnyHashable(NSURLErrorKey): testURL])
166166
let eCL: Error = nsCL
167167
let isHeadingFailure: Bool
168168
switch eCL {
@@ -318,7 +318,7 @@ class SomeNSErrorSubclass: NSError {}
318318
ErrorBridgingTests.test("Thrown NSError identity is preserved") {
319319
do {
320320
let e = NSError(domain: "ClericalError", code: 219,
321-
userInfo: ["yeah": "yeah"])
321+
userInfo: [AnyHashable("yeah"): "yeah"])
322322
do {
323323
throw e
324324
} catch let e2 as NSError {
@@ -331,7 +331,7 @@ ErrorBridgingTests.test("Thrown NSError identity is preserved") {
331331

332332
do {
333333
let f = SomeNSErrorSubclass(domain: "ClericalError", code: 219,
334-
userInfo: ["yeah": "yeah"])
334+
userInfo: [AnyHashable("yeah"): "yeah"])
335335
do {
336336
throw f
337337
} catch let f2 as NSError {

test/1_stdlib/NSEnumeratorAPI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ NSEnumeratorAPI.test("Sequence") {
1818
NSEnumeratorAPI.test("keyEnumerator") {
1919
let result = [1 as NSNumber: "one", 2 as NSNumber: "two"]
2020
expectEqualsUnordered(
21-
[1, 2], NSDictionary(dictionary: result).keyEnumerator()) {
21+
[1, 2], NSDictionary(dictionary: result as [AnyHashable: Any]).keyEnumerator()) {
2222
switch ($0 as! Int, $1 as! Int) {
2323
case let (x, y) where x == y: return .eq
2424
case let (x, y) where x < y: return .lt

0 commit comments

Comments
 (0)