[ClangImporter] Avoid full conformance checks for swift_wrapper types.

Avoids yet another circular import issue: conformance on type X ->
associated types -> members -> members of superclass type (for naming
conflicts) -> SwiftWrapper type -> conformances on underlying type X.
Normally this circularity is fine, but I still want to put back the
assertion I reverted in 19a2ad5f17.

I unfortunately don't have a test for the actual failure here; it came
up on some Apple-internal code and heavily depended on both Foundation
and the standard library. I did confirm that this fixes it, and it
looks like it does so without causing any issues for swift_wrapper
types.

More rdar://problem/30785976
This commit is contained in:
Jordan Rose
2017-04-03 14:45:50 -07:00
parent 65a36e43ad
commit d1f0a245df
6 changed files with 152 additions and 9 deletions

View File

@@ -4718,6 +4718,51 @@ Decl *SwiftDeclConverter::importCompatibilityTypeAlias(
return alias; return alias;
} }
static bool inheritanceListContainsProtocol(ArrayRef<TypeLoc> inherited,
const ProtocolDecl *proto) {
return llvm::any_of(inherited, [proto](TypeLoc type) -> bool {
SmallVector<ProtocolDecl *, 8> protos;
(void)type.getType()->isExistentialType(protos);
return ProtocolType::visitAllProtocols(protos,
[proto](const ProtocolDecl *next) {
return next == proto;
});
});
}
static bool conformsToProtocolInOriginalModule(NominalTypeDecl *nominal,
const ProtocolDecl *proto,
ModuleDecl *foundationModule,
LazyResolver *resolver) {
if (resolver)
resolver->resolveInheritanceClause(nominal);
if (inheritanceListContainsProtocol(nominal->getInherited(), proto))
return true;
// Only consider extensions from the original module...or from an overlay
// or the Swift half of a mixed-source framework.
const DeclContext *containingFile = nominal->getModuleScopeContext();
ModuleDecl *originalModule = containingFile->getParentModule();
ModuleDecl *adapterModule = nullptr;
if (auto *clangUnit = dyn_cast<ClangModuleUnit>(containingFile))
adapterModule = clangUnit->getAdapterModule();
for (ExtensionDecl *extension : nominal->getExtensions()) {
ModuleDecl *extensionModule = extension->getParentModule();
if (extensionModule != originalModule && extensionModule != adapterModule &&
extensionModule != foundationModule) {
continue;
}
if (resolver)
resolver->resolveInheritanceClause(extension);
if (inheritanceListContainsProtocol(extension->getInherited(), proto))
return true;
}
return false;
}
Decl * Decl *
SwiftDeclConverter::importSwiftNewtype(const clang::TypedefNameDecl *decl, SwiftDeclConverter::importSwiftNewtype(const clang::TypedefNameDecl *decl,
clang::SwiftNewtypeAttr *newtypeAttr, clang::SwiftNewtypeAttr *newtypeAttr,
@@ -4802,9 +4847,11 @@ SwiftDeclConverter::importSwiftNewtype(const clang::TypedefNameDecl *decl,
if (!proto) if (!proto)
return; return;
SmallVector<ProtocolConformance *, 1> conformances; // Break circularity by only looking for declared conformances in the
if (computedNominal->lookupConformance(computedNominal->getParentModule(), // original module, or possibly its adapter.
proto, conformances)) { if (conformsToProtocolInOriginalModule(computedNominal, proto,
Impl.tryLoadFoundationModule(),
Impl.getTypeResolver())) {
protocols.push_back(proto); protocols.push_back(proto);
synthesizedProtocols.push_back(kind); synthesizedProtocols.push_back(kind);
} }

View File

@@ -0,0 +1,41 @@
// Helper for newtype_conformance.swift
@_exported import MoreSwiftNewtypes
import Foundation
extension UnbridgedNonNSObject: Equatable /* but not Hashable */ {
public static func ==(lhs: UnbridgedNonNSObject, rhs: UnbridgedNonNSObject) -> Bool { return true }
}
// Pick something other than "Equatable" to test that we're not just looking at
// immediately inherited protocols.
protocol EquatablePlus: Equatable {}
public struct BridgedValue : EquatablePlus /* but not Hashable */ {
public static func ==(lhs: BridgedValue, rhs: BridgedValue) -> Bool { return true }
}
extension BridgedValue: _ObjectiveCBridgeable {
public func _bridgeToObjectiveC() -> BridgedNonNSObject {
return BridgedNonNSObject()
}
public static func _forceBridgeFromObjectiveC(
_ x: BridgedNonNSObject,
result: inout BridgedValue?) {
}
public static func _conditionallyBridgeFromObjectiveC(
_ x: BridgedNonNSObject,
result: inout BridgedValue?
) -> Bool {
return true
}
public static func _unconditionallyBridgeFromObjectiveC(_ source: BridgedNonNSObject?)
-> BridgedValue {
var result: BridgedValue?
_forceBridgeFromObjectiveC(source!, result: &result)
return result!
}
}

View File

@@ -0,0 +1,24 @@
// Helper for newtype_conformance.swift
@_exported import MoreSwiftNewtypes
func acceptEquatable<T: Equatable>(_: T) {}
func testEquatable(wrappedRef: WrappedRef, wrappedValue: WrappedValue) {
acceptEquatable(wrappedRef)
acceptEquatable(wrappedValue)
}
func intIfNonHashable<T>(_: T) -> Int { return 0 }
func intIfNonHashable<T: Hashable>(_: T) -> Bool { return false }
func testNotHashable(wrappedRef: WrappedRef, wrappedValue: WrappedValue) {
let refResult = intIfNonHashable(wrappedRef)
let _: Int = refResult
let valueResult = intIfNonHashable(wrappedValue)
let _: Int = valueResult
let baselineResult = intIfNonHashable(0)
let _: Bool = baselineResult
}

View File

@@ -0,0 +1,16 @@
@import Foundation;
__attribute__((objc_root_class))
@interface Base
- (instancetype)init;
@end
@interface UnbridgedNonNSObject : Base
@end
__attribute__((swift_bridge("BridgedValue")))
@interface BridgedNonNSObject : Base
@end
typedef UnbridgedNonNSObject *WrappedRef __attribute__((swift_wrapper(struct)));
typedef BridgedNonNSObject *WrappedValue __attribute__((swift_wrapper(struct)));

View File

@@ -69,6 +69,10 @@ module MissingHeader {
header "this-header-does-not-exist.h" header "this-header-does-not-exist.h"
} }
module MoreSwiftNewtypes {
header "MoreSwiftNewtypes.h"
}
module Newtype { module Newtype {
header "Newtype.h" header "Newtype.h"
} }

View File

@@ -1,12 +1,15 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules %s // RUN: rm -rf %t && mkdir -p %t
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules -primary-file %S/Inputs/MoreSwiftNewtypes_conformances.swift %S/Inputs/MoreSwiftNewtypes_tests.swift -module-name MoreSwiftNewtypes
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules %S/Inputs/MoreSwiftNewtypes_conformances.swift -primary-file %S/Inputs/MoreSwiftNewtypes_tests.swift -module-name MoreSwiftNewtypes
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -I %S/Inputs/custom-modules -o %t %S/Inputs/MoreSwiftNewtypes_conformances.swift %S/Inputs/MoreSwiftNewtypes_tests.swift -module-name MoreSwiftNewtypes
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules -I %t %s -verify
// REQUIRES: objc_interop // REQUIRES: objc_interop
// This test can't use '-verify' mode, because the potential error wouldn't
// belong to any file.
// e.g.:
// <unknown>:0: error: type 'NSNotification.Name' does not conform to protocol 'Comparable'
import Foundation import Foundation
import MoreSwiftNewtypes
func acceptEquatable<T: Equatable>(_: T) {} func acceptEquatable<T: Equatable>(_: T) {}
func acceptHashable<T: Hashable>(_: T) {} func acceptHashable<T: Hashable>(_: T) {}
@@ -26,3 +29,11 @@ func testNewTypeWrapperComparable(x: NSNotification.Name, y: NSNotification.Name
_ = x >= y _ = x >= y
_ = x as NSString _ = x as NSString
} }
func testCustomWrappers(wrappedRef: WrappedRef, wrappedValue: WrappedValue) {
acceptEquatable(wrappedRef)
acceptEquatable(wrappedValue)
acceptHashable(wrappedRef) // expected-error {{does not conform to expected type 'Hashable'}}
acceptHashable(wrappedValue) // expected-error {{does not conform to expected type 'Hashable'}}
}