[Clang importer] Make sure that the first argument of Set/Dictionary types are Hashable.

Extend the check to make sure that the first type argument to an
imported Set or Dictionary type is Hashable actually checks
struct/enum types for Hashable conformances.

Fixes rdar://problem/30622665.
This commit is contained in:
Doug Gregor
2017-06-26 13:08:39 -07:00
parent 946b150398
commit aa215e7e54
5 changed files with 29 additions and 8 deletions

View File

@@ -984,13 +984,12 @@ namespace {
return Type(); return Type();
// The first type argument for Dictionary or Set needs // The first type argument for Dictionary or Set needs
// to be Hashable. Everything that inherits NSObject has a // to be Hashable. If something isn't Hashable, fall back
// -hash code in ObjC, but if something isn't NSObject, fall back
// to AnyHashable as a key type. // to AnyHashable as a key type.
if (unboundDecl == Impl.SwiftContext.getDictionaryDecl() || if (unboundDecl == Impl.SwiftContext.getDictionaryDecl() ||
unboundDecl == Impl.SwiftContext.getSetDecl()) { unboundDecl == Impl.SwiftContext.getSetDecl()) {
auto &keyType = importedTypeArgs[0]; auto &keyType = importedTypeArgs[0];
if (!Impl.matchesNSObjectBound(keyType)) { if (!Impl.matchesHashableBound(keyType)) {
if (auto anyHashable = Impl.SwiftContext.getAnyHashableDecl()) if (auto anyHashable = Impl.SwiftContext.getAnyHashableDecl())
keyType = anyHashable->getDeclaredType(); keyType = anyHashable->getDeclaredType();
else else
@@ -2413,7 +2412,7 @@ Type ClangImporter::Implementation::getNSObjectType() {
return Type(); return Type();
} }
bool ClangImporter::Implementation::matchesNSObjectBound(Type type) { bool ClangImporter::Implementation::matchesHashableBound(Type type) {
Type NSObjectType = getNSObjectType(); Type NSObjectType = getNSObjectType();
if (!NSObjectType) if (!NSObjectType)
return false; return false;
@@ -2425,8 +2424,14 @@ bool ClangImporter::Implementation::matchesNSObjectBound(Type type) {
// Struct or enum type must have been bridged. // Struct or enum type must have been bridged.
// TODO: Check that the bridged type is Hashable? // TODO: Check that the bridged type is Hashable?
if (type->getStructOrBoundGenericStruct() || if (type->getStructOrBoundGenericStruct() ||
type->getEnumOrBoundGenericEnum()) type->getEnumOrBoundGenericEnum()) {
return true; auto nominal = type->getAnyNominal();
auto hashable = SwiftContext.getProtocol(KnownProtocolKind::Hashable);
SmallVector<ProtocolConformance *, 2> conformances;
return hashable &&
nominal->lookupConformance(nominal->getParentModule(), hashable,
conformances);
}
return false; return false;
} }

View File

@@ -886,7 +886,7 @@ public:
/// \brief Determines whether the given type matches an implicit type /// \brief Determines whether the given type matches an implicit type
/// bound of "Hashable", which is used to validate NSDictionary/NSSet. /// bound of "Hashable", which is used to validate NSDictionary/NSSet.
bool matchesNSObjectBound(Type type); bool matchesHashableBound(Type type);
/// \brief Look up and attempt to import a Clang declaration with /// \brief Look up and attempt to import a Clang declaration with
/// the given name. /// the given name.

View File

@@ -0,0 +1,6 @@
@import ObjectiveC;
@import Foundation;
@interface ObjCBridgeNonconforming
@property NSSet<NSDictionary<NSString *, id> *> * _Nonnull foo;
@end

View File

@@ -175,3 +175,7 @@ module MacrosRedefB {
module IndirectFields { module IndirectFields {
header "IndirectFields.h" header "IndirectFields.h"
} }
module ObjCBridgeNonconforming {
header "ObjCBridgeNonconforming.h"
}

View File

@@ -1,9 +1,10 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -swift-version 4 %s // RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -swift-version 4 -I %S/Inputs/custom-modules %s
// REQUIRES: objc_interop // REQUIRES: objc_interop
import Foundation import Foundation
import objc_generics import objc_generics
import ObjCBridgeNonconforming
func testNSArrayBridging(_ hive: Hive) { func testNSArrayBridging(_ hive: Hive) {
_ = hive.bees as [Bee] _ = hive.bees as [Bee]
@@ -392,3 +393,8 @@ let third: Third! = Third()
func useThird() { func useThird() {
_ = third.description _ = third.description
} }
func testNonconforming(bnc: ObjCBridgeNonconforming) {
let _: Int = bnc.foo // expected-error{{cannot convert value of type 'Set<AnyHashable>' to specified type 'Int'}}
}