Skip to content

Make NS_TYPED_ENUMS ObjectiveCBridgeable when they wrap an object #15270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5358,8 +5358,37 @@ SwiftDeclConverter::importSwiftNewtype(const clang::TypedefNameDecl *decl,
// implementation in the standard library.
transferKnown(KnownProtocolKind::Equatable);
transferKnown(KnownProtocolKind::Hashable);
bool transferredObjCBridgeable =
transferKnown(KnownProtocolKind::ObjectiveCBridgeable);
bool hasObjCBridgeable =
transferKnown(KnownProtocolKind::ObjectiveCBridgeable);
bool wantsObjCBridgeableTypealias = hasObjCBridgeable && isBridged;

// Wrappers around ObjC classes and protocols are also bridgeable.
if (!hasObjCBridgeable) {
if (isBridged) {
if (auto *proto = dyn_cast_or_null<ProtocolDecl>(computedNominal))
if (proto->getKnownProtocolKind() == KnownProtocolKind::Error)
hasObjCBridgeable = true;
} else {
if (auto *objcClass = dyn_cast_or_null<ClassDecl>(computedNominal)) {
switch (objcClass->getForeignClassKind()) {
case ClassDecl::ForeignKind::Normal:
case ClassDecl::ForeignKind::RuntimeOnly:
if (objcClass->hasClangNode())
hasObjCBridgeable = true;
break;
case ClassDecl::ForeignKind::CFType:
break;
}
} else if (storedUnderlyingType->isObjCExistentialType()) {
hasObjCBridgeable = true;
}
}

if (hasObjCBridgeable) {
addKnown(KnownProtocolKind::ObjectiveCBridgeable);
wantsObjCBridgeableTypealias = true;
}
}

if (!isBridged) {
// Simple, our stored type is equivalent to our computed
Expand All @@ -5377,10 +5406,11 @@ SwiftDeclConverter::importSwiftNewtype(const clang::TypedefNameDecl *decl,
computedPropertyUnderlyingType,
synthesizedProtocols,
/*makeUnlabeledValueInit=*/unlabeledCtor);
}

if (transferredObjCBridgeable)
addSynthesizedTypealias(structDecl, ctx.Id_ObjectiveCType,
storedUnderlyingType);
if (wantsObjCBridgeableTypealias) {
addSynthesizedTypealias(structDecl, ctx.Id_ObjectiveCType,
storedUnderlyingType);
}

Impl.ImportedDecls[{decl->getCanonicalDecl(), getVersion()}] = structDecl;
Expand Down
33 changes: 33 additions & 0 deletions stdlib/public/SDK/Foundation/NSError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,38 @@ extension _BridgedStoredNSError {
}
}

extension _SwiftNewtypeWrapper where Self.RawValue == Error {
@_inlineable // FIXME(sil-serialize-all)
public func _bridgeToObjectiveC() -> NSError {
return rawValue as NSError
}

@_inlineable // FIXME(sil-serialize-all)
public static func _forceBridgeFromObjectiveC(
_ source: NSError,
result: inout Self?
) {
result = Self(rawValue: source)
}

@_inlineable // FIXME(sil-serialize-all)
public static func _conditionallyBridgeFromObjectiveC(
_ source: NSError,
result: inout Self?
) -> Bool {
result = Self(rawValue: source)
return result != nil
}

@_inlineable // FIXME(sil-serialize-all)
public static func _unconditionallyBridgeFromObjectiveC(
_ source: NSError?
) -> Self {
return Self(rawValue: _convertNSErrorToError(source))!
}
}


@available(*, unavailable, renamed: "CocoaError")
public typealias NSCocoaError = CocoaError

Expand Down Expand Up @@ -3219,6 +3251,7 @@ extension MachError {
}

public struct ErrorUserInfoKey : RawRepresentable, _SwiftNewtypeWrapper, Equatable, Hashable, _ObjectiveCBridgeable {
public typealias _ObjectiveCType = NSString
public init(rawValue: String) { self.rawValue = rawValue }
public var rawValue: String
}
Expand Down
34 changes: 34 additions & 0 deletions stdlib/public/core/NewtypeWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ extension _SwiftNewtypeWrapper where Self.RawValue : Hashable {

#if _runtime(_ObjC)
extension _SwiftNewtypeWrapper where Self.RawValue : _ObjectiveCBridgeable {
// Note: This is the only default typealias for _ObjectiveCType, because
// constrained extensions aren't allowed to define types in different ways.
// Fortunately the others don't need it.
public typealias _ObjectiveCType = Self.RawValue._ObjectiveCType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't trust the compiler to get this right consistently, because a typealias with the same name as an associated type, but in a constrained extension, isn't something modeled well in the conformance checker. You're already adding a synthesized typealias in the importer, so why do you need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the one case the importer is skipping; I didn't want to have to resolve the _ObjectiveCType of the RawValue, especially when compiling an overlay where we may not have actually determined it yet. How bad do you think this is? (Note that it's not necessarily the original type; a swift_wrapper of an int is bridged to NSNumber.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking the that the swift_bridge attribute had all of the information, but of course it doesn't for NSNumber or NSValue. Slightly concerned that we'll be unable to form this type witness if the Clang importer imports one of these types after the type checker is gone, but I supposed #15254 should make that no longer an issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if that happens we're hosed anyway for all the default value requirements, so I'm not going to worry about it. :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Associated type witnesses get used in Type::subst calls a lot, and we've had a number of compiler crashes due to missing associated type witnesses in the importer. Value witnesses are less often used---only really by the specializer or (in this case) the SILGen bridging code---and hasn't been as much of an issue. Anyway, I'm okay with this solution.


@_inlineable // FIXME(sil-serialize-all)
Expand Down Expand Up @@ -61,5 +64,36 @@ extension _SwiftNewtypeWrapper where Self.RawValue : _ObjectiveCBridgeable {
rawValue: Self.RawValue._unconditionallyBridgeFromObjectiveC(source))!
}
}

extension _SwiftNewtypeWrapper where Self.RawValue: AnyObject {
@_inlineable // FIXME(sil-serialize-all)
public func _bridgeToObjectiveC() -> Self.RawValue {
return rawValue
}

@_inlineable // FIXME(sil-serialize-all)
public static func _forceBridgeFromObjectiveC(
_ source: Self.RawValue,
result: inout Self?
) {
result = Self(rawValue: source)
}

@_inlineable // FIXME(sil-serialize-all)
public static func _conditionallyBridgeFromObjectiveC(
_ source: Self.RawValue,
result: inout Self?
) -> Bool {
result = Self(rawValue: source)
return result != nil
}

@_inlineable // FIXME(sil-serialize-all)
public static func _unconditionallyBridgeFromObjectiveC(
_ source: Self.RawValue?
) -> Self {
return Self(rawValue: source!)!
}
}
#endif

5 changes: 5 additions & 0 deletions test/ClangImporter/Inputs/custom-modules/Newtype.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,8 @@ __attribute((swift_name("NSSomeContext.Name")));

extern const NSSomeContextName NSMyContextName;


typedef NSError *ErrorNewType __attribute((swift_newtype(struct)));

void testErrorDictionary(NSDictionary<NSError *, NSString *> * _Nonnull);
void testErrorDictionaryNewtype(NSDictionary<ErrorNewType, NSString *> * _Nonnull);
16 changes: 15 additions & 1 deletion test/ClangImporter/objc_parse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,10 @@ class NewtypeUser {
@objc func stringNewtypeOptional(a: SNTErrorDomain?) {} // expected-error {{'SNTErrorDomain' has been renamed to 'ErrorDomain'}}{{39-53=ErrorDomain}}
@objc func intNewtype(a: MyInt) {}
@objc func intNewtypeOptional(a: MyInt?) {} // expected-error {{method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C}}
@objc func intNewtypeArray(a: [MyInt]) {} // expected-error {{method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C}}
@objc func intNewtypeDictionary(a: [MyInt: NSObject]) {} // expected-error {{method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C}}
@objc func cfNewtype(a: CFNewType) {}
@objc func cfNewtypeArray(a: [CFNewType]) {} // expected-error {{method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C}}
}

func testTypeAndValue() {
Expand All @@ -676,4 +680,14 @@ func testNonTrivialStructs() {
_ = NonTrivialToCopy() // expected-error {{use of unresolved identifier 'NonTrivialToCopy'}}
_ = NonTrivialToCopyWrapper() // expected-error {{use of unresolved identifier 'NonTrivialToCopyWrapper'}}
_ = TrivialToCopy() // okay
}
}

func testErrorNewtype() {
_ = ErrorNewType(3) // expected-error {{argument type 'Int' does not conform to expected type 'Error'}}

// Since we import NSError as Error, and Error is not Hashable...we end up
// losing the types for these functions, even though the above assignment
// works.
testErrorDictionary(3) // expected-error {{cannot convert value of type 'Int' to expected argument type '[AnyHashable : String]'}}
testErrorDictionaryNewtype(3) // expected-error {{cannot convert value of type 'Int' to expected argument type '[AnyHashable : String]'}}
}
2 changes: 1 addition & 1 deletion test/IDE/print_omit_needless_words.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
// CHECK-FOUNDATION: func copy(with: NSZone? = nil) -> Any!

// Note: Objective-C type parameter names.
// CHECK-FOUNDATION: func object(forKey: NSCopying) -> Any?
// CHECK-FOUNDATION: func object(forKey: Any) -> Any?
// CHECK-FOUNDATION: func removeObject(forKey: NSCopying)

// Note: Don't drop the name of the first parameter in an initializer entirely.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,34 @@ public func _convertErrorToNSError(_ error: Error) -> NSError {
return error as NSError
}

extension _SwiftNewtypeWrapper where Self.RawValue == Error {
@_inlineable // FIXME(sil-serialize-all)
public func _bridgeToObjectiveC() -> NSError {
return rawValue as NSError
}

@_inlineable // FIXME(sil-serialize-all)
public static func _forceBridgeFromObjectiveC(
_ source: NSError,
result: inout Self?
) {
result = Self(rawValue: source)
}

@_inlineable // FIXME(sil-serialize-all)
public static func _conditionallyBridgeFromObjectiveC(
_ source: NSError,
result: inout Self?
) -> Bool {
result = Self(rawValue: source)
return result != nil
}

@_inlineable // FIXME(sil-serialize-all)
public static func _unconditionallyBridgeFromObjectiveC(
_ source: NSError?
) -> Self {
return Self(rawValue: _convertNSErrorToError(source))!
}
}

32 changes: 32 additions & 0 deletions test/Inputs/clang-importer-sdk/swift-modules/Foundation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,38 @@ public func _convertErrorToNSError(_ x: Error) -> NSError {
return x as NSError
}

extension _SwiftNewtypeWrapper where Self.RawValue == Error {
@_inlineable // FIXME(sil-serialize-all)
public func _bridgeToObjectiveC() -> NSError {
return rawValue as NSError
}

@_inlineable // FIXME(sil-serialize-all)
public static func _forceBridgeFromObjectiveC(
_ source: NSError,
result: inout Self?
) {
result = Self(rawValue: source)
}

@_inlineable // FIXME(sil-serialize-all)
public static func _conditionallyBridgeFromObjectiveC(
_ source: NSError,
result: inout Self?
) -> Bool {
result = Self(rawValue: source)
return result != nil
}

@_inlineable // FIXME(sil-serialize-all)
public static func _unconditionallyBridgeFromObjectiveC(
_ source: NSError?
) -> Self {
return Self(rawValue: _convertNSErrorToError(source))!
}
}



extension NSArray {
@objc(methodIntroducedInOverlay) public func introducedInOverlay() { }
Expand Down
2 changes: 1 addition & 1 deletion test/Inputs/clang-importer-sdk/usr/include/Foundation.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ __attribute__((availability(ios,introduced=8.0)))
- (id)copyWithZone:(nullable NSZone *)zone;
@end

@interface NSDictionary<KeyType : id<NSCopying>, ObjectType> : NSObject /*<NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>*/
@interface NSDictionary<KeyType, ObjectType> : NSObject /*<NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>*/
@property (readonly) NSUInteger count;
- (nullable ObjectType)objectForKey:(nonnull KeyType)aKey;
- (nonnull NSEnumerator *)keyEnumerator;
Expand Down
80 changes: 80 additions & 0 deletions test/Interpreter/SDK/Inputs/objc_bridge_cast_newtype.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@import Foundation;

#define GET_UNTYPED_AND_OPAQUE(Name) \
static NSArray * _Nonnull getUntypedNSArrayOf##Name##s() { \
return getTypedNSArrayOf##Name##Wrappers(); \
} \
static id _Nonnull getOpaqueNSArrayOf##Name##s() { \
return getTypedNSArrayOf##Name##Wrappers(); \
}
#define CHECK_UNTYPED_AND_OPAQUE(Name) \
static BOOL checkUntypedNSArrayOf##Name##s(NSArray * _Nonnull array) { \
return checkTypedNSArrayOf##Name##Wrappers(array); \
} \
static BOOL checkOpaqueNSArrayOf##Name##s(id _Nonnull array) { \
return checkTypedNSArrayOf##Name##Wrappers(array); \
}

typedef NSString *StringWrapper NS_TYPED_EXTENSIBLE_ENUM;
static NSArray<StringWrapper> * _Nonnull getTypedNSArrayOfStringWrappers() {
return @[@"abc", @"def"];
}
static BOOL
checkTypedNSArrayOfStringWrappers(NSArray<StringWrapper> * _Nonnull array) {
return [array isEqual:getTypedNSArrayOfStringWrappers()];
}
GET_UNTYPED_AND_OPAQUE(String)
CHECK_UNTYPED_AND_OPAQUE(String)


typedef id <NSCopying, NSCoding> CopyingAndCodingWrapper
NS_TYPED_EXTENSIBLE_ENUM;
static NSArray<CopyingAndCodingWrapper> * _Nonnull
getTypedNSArrayOfCopyingAndCodingWrappers() {
return @[@"abc", @[]];
}
static BOOL checkTypedNSArrayOfCopyingAndCodingWrappers(
NSArray<CopyingAndCodingWrapper> * _Nonnull array) {
return [array isEqual:getTypedNSArrayOfCopyingAndCodingWrappers()];
}
GET_UNTYPED_AND_OPAQUE(CopyingAndCoding)
CHECK_UNTYPED_AND_OPAQUE(CopyingAndCoding)

typedef id <NSCopying> CopyingWrapper NS_TYPED_EXTENSIBLE_ENUM;
static NSArray<CopyingWrapper> * _Nonnull getTypedNSArrayOfCopyingWrappers() {
return getTypedNSArrayOfCopyingAndCodingWrappers();
}
static BOOL
checkTypedNSArrayOfCopyingWrappers(NSArray<CopyingWrapper> * _Nonnull array) {
return [array isEqual:getTypedNSArrayOfCopyingWrappers()];
}
GET_UNTYPED_AND_OPAQUE(Copying)
CHECK_UNTYPED_AND_OPAQUE(Copying)

typedef NSObject *ObjectWrapper NS_TYPED_EXTENSIBLE_ENUM;
static NSArray<ObjectWrapper> * _Nonnull getTypedNSArrayOfObjectWrappers() {
return (NSArray<ObjectWrapper> *)getTypedNSArrayOfCopyingAndCodingWrappers();
}
static BOOL
checkTypedNSArrayOfObjectWrappers(NSArray<ObjectWrapper> * _Nonnull array) {
return [array isEqual:getTypedNSArrayOfObjectWrappers()];
}
GET_UNTYPED_AND_OPAQUE(Object)
CHECK_UNTYPED_AND_OPAQUE(Object)

typedef NSError *ErrorWrapper NS_TYPED_EXTENSIBLE_ENUM;
static NSArray<ErrorWrapper> * _Nonnull getTypedNSArrayOfErrorWrappers() {
return @[[NSError errorWithDomain:@"x" code:11 userInfo:nil],
[NSError errorWithDomain:@"x" code:22 userInfo:nil]];
}
static BOOL
checkTypedNSArrayOfErrorWrappers(NSArray<ErrorWrapper> * _Nonnull array) {
return [[array valueForKey:@"code"] isEqual:@[@11, @22]] &&
NSNotFound == [array indexOfObjectPassingTest:^BOOL(ErrorWrapper error,
NSUInteger idx,
BOOL *stop) {
return ![error isKindOfClass:[NSError class]];
}];
}
GET_UNTYPED_AND_OPAQUE(Error)
CHECK_UNTYPED_AND_OPAQUE(Error)
Loading