[ClangImporter] Add support for 'SwiftImportAsNonGeneric' in API notes (#6962)

Generic Objective-C classes with this annotation will be imported as
non-generic in Swift. The Swift 3 behavior hardcoded a certain set of
class /hierarchies/ as permanently non-generic, and this is preserved
in Swift 3 mode.

Actually using this API note in a versioned way (as opposed to just
marking the class non-generic in all language versions) will cause
horrible source compatibility problems in the mix-and-match cases,
where Swift 3 code presents a non-generic type that Swift 4 expects to
be generic or vice versa.  Fixes for this will come later; right now
it's more important to add support for the feature at all.

To avoid unwanted changes in Swift 4, this commit also adds API notes
to make any existing classes in the previously-hardcoded set continue
to import as non-generic even in Swift 4. The difference is that
/subclasses/ of these classes may come in as generic. (If we want to
make a change here, that can be a separate commit.)

rdar://problem/31226414 (Swift side of rdar://problem/28455962)
This commit is contained in:
Jordan Rose
2017-04-03 15:39:19 -07:00
committed by GitHub
parent 85915d9289
commit 08b6c5f0c9
9 changed files with 75 additions and 21 deletions

View File

@@ -38,6 +38,7 @@ set(SWIFT_API_NOTES_INPUTS
QuickLook QuickLook
SafariServices SafariServices
SceneKit SceneKit
ScriptingBridge
SpriteKit SpriteKit
StoreKit StoreKit
TVMLKit TVMLKit

View File

@@ -5,6 +5,7 @@ Classes:
SwiftBridge: AffineTransform SwiftBridge: AffineTransform
- Name: NSArray - Name: NSArray
SwiftBridge: Swift.Array SwiftBridge: Swift.Array
SwiftImportAsNonGeneric: true
Methods: Methods:
- Selector: 'pathsMatchingExtensions:' - Selector: 'pathsMatchingExtensions:'
SwiftName: pathsMatchingExtensions(_:) SwiftName: pathsMatchingExtensions(_:)
@@ -12,6 +13,15 @@ Classes:
- Selector: 'filteredArrayUsingPredicate:' - Selector: 'filteredArrayUsingPredicate:'
SwiftName: filtered(using:) SwiftName: filtered(using:)
MethodKind: Instance MethodKind: Instance
- Name: NSMutableArray
SwiftImportAsNonGeneric: true
Methods:
- Selector: 'removeObjectIdenticalTo:inRange:'
SwiftName: removeObject(identicalTo:in:)
MethodKind: Instance
- Selector: 'removeObjectIdenticalTo:'
SwiftName: removeObject(identicalTo:)
MethodKind: Instance
- Name: NSCachedURLResponse - Name: NSCachedURLResponse
SwiftName: CachedURLResponse SwiftName: CachedURLResponse
- Name: NSCharacterSet - Name: NSCharacterSet
@@ -51,6 +61,8 @@ Classes:
- Selector: 'hasMemberInPlane:' - Selector: 'hasMemberInPlane:'
SwiftName: hasMemberInPlane(_:) SwiftName: hasMemberInPlane(_:)
MethodKind: Instance MethodKind: Instance
- Name: NSCountedSet
SwiftImportAsNonGeneric: true
- Name: NSData - Name: NSData
SwiftBridge: Data SwiftBridge: Data
Methods: Methods:
@@ -85,6 +97,8 @@ Classes:
SwiftBridge: DateComponents SwiftBridge: DateComponents
- Name: NSDateInterval - Name: NSDateInterval
SwiftBridge: DateInterval SwiftBridge: DateInterval
- Name: NSEnumerator
SwiftImportAsNonGeneric: true
- Name: NSError - Name: NSError
SwiftBridge: Swift.Error SwiftBridge: Swift.Error
Methods: Methods:
@@ -93,12 +107,18 @@ Classes:
MethodKind: Class MethodKind: Class
- Name: NSDictionary - Name: NSDictionary
SwiftBridge: Swift.Dictionary SwiftBridge: Swift.Dictionary
SwiftImportAsNonGeneric: true
- Name: NSMutableDictionary
SwiftImportAsNonGeneric: true
- Name: NSSet - Name: NSSet
SwiftBridge: Swift.Set SwiftBridge: Swift.Set
SwiftImportAsNonGeneric: true
Methods: Methods:
- Selector: 'filteredSetUsingPredicate:' - Selector: 'filteredSetUsingPredicate:'
SwiftName: filtered(using:) SwiftName: filtered(using:)
MethodKind: Instance MethodKind: Instance
- Name: NSMutableSet
SwiftImportAsNonGeneric: true
- Name: NSString - Name: NSString
SwiftBridge: Swift.String SwiftBridge: Swift.String
Methods: Methods:
@@ -313,14 +333,7 @@ Classes:
MethodKind: Instance MethodKind: Instance
- Name: NSMeasurement - Name: NSMeasurement
SwiftBridge: Measurement SwiftBridge: Measurement
- Name: NSMutableArray SwiftImportAsNonGeneric: true
Methods:
- Selector: 'removeObjectIdenticalTo:inRange:'
SwiftName: removeObject(identicalTo:in:)
MethodKind: Instance
- Selector: 'removeObjectIdenticalTo:'
SwiftName: removeObject(identicalTo:)
MethodKind: Instance
- Name: NSMutableData - Name: NSMutableData
Methods: Methods:
- Selector: 'appendBytes:length:' - Selector: 'appendBytes:length:'
@@ -445,6 +458,7 @@ Classes:
- Name: unsignedIntegerValue - Name: unsignedIntegerValue
SwiftName: uintValue SwiftName: uintValue
- Name: NSOrderedSet - Name: NSOrderedSet
SwiftImportAsNonGeneric: true
Methods: Methods:
- Selector: 'enumerateObjectsWithOptions:usingBlock:' - Selector: 'enumerateObjectsWithOptions:usingBlock:'
SwiftName: enumerateObjects(options:using:) SwiftName: enumerateObjects(options:using:)
@@ -470,6 +484,8 @@ Classes:
- Selector: 'indexOfObjectWithOptions:passingTest:' - Selector: 'indexOfObjectWithOptions:passingTest:'
SwiftName: index(_:ofObjectPassingTest:) SwiftName: index(_:ofObjectPassingTest:)
MethodKind: Instance MethodKind: Instance
- Name: NSMutableOrderedSet
SwiftImportAsNonGeneric: true
- Name: NSTask - Name: NSTask
SwiftName: Process SwiftName: Process
Methods: Methods:
@@ -932,6 +948,7 @@ Classes:
SwiftName: ValueTransformer SwiftName: ValueTransformer
- Name: NSDirectoryEnumerator - Name: NSDirectoryEnumerator
SwiftName: FileManager.DirectoryEnumerator SwiftName: FileManager.DirectoryEnumerator
SwiftImportAsNonGeneric: true
- Name: NSDimension - Name: NSDimension
SwiftName: Dimension SwiftName: Dimension
- Name: NSUnit - Name: NSUnit

View File

@@ -0,0 +1,5 @@
---
Name: ScriptingBridge
Classes:
- Name: SBElementArray
SwiftImportAsNonGeneric: true

View File

@@ -1713,7 +1713,14 @@ getAccessorPropertyType(const clang::FunctionDecl *accessor, bool isSetter,
/// Whether we should suppress importing the Objective-C generic type params /// Whether we should suppress importing the Objective-C generic type params
/// of this class as Swift generic type params. /// of this class as Swift generic type params.
static bool static bool
shouldSuppressGenericParamsImport(const clang::ObjCInterfaceDecl *decl) { shouldSuppressGenericParamsImport(const LangOptions &langOpts,
const clang::ObjCInterfaceDecl *decl) {
if (decl->hasAttr<clang::SwiftImportAsNonGenericAttr>())
return true;
if (langOpts.isSwiftVersion3()) {
// In Swift 3 we used a hardcoded list of declarations, and made all of
// their subclasses drop their generic parameters when imported.
while (decl) { while (decl) {
StringRef name = decl->getName(); StringRef name = decl->getName();
if (name == "NSArray" || name == "NSDictionary" || name == "NSSet" || if (name == "NSArray" || name == "NSDictionary" || name == "NSSet" ||
@@ -1723,6 +1730,8 @@ shouldSuppressGenericParamsImport(const clang::ObjCInterfaceDecl *decl) {
} }
decl = decl->getSuperClass(); decl = decl->getSuperClass();
} }
}
return false; return false;
} }
@@ -6149,7 +6158,7 @@ Optional<GenericParamList *> SwiftDeclConverter::importObjCGenericParams(
if (!typeParamList) { if (!typeParamList) {
return nullptr; return nullptr;
} }
if (shouldSuppressGenericParamsImport(decl)) { if (shouldSuppressGenericParamsImport(Impl.SwiftContext.LangOpts, decl)) {
return nullptr; return nullptr;
} }
assert(typeParamList->size() > 0); assert(typeParamList->size() > 0);

View File

@@ -84,6 +84,8 @@ SwiftVersions:
- Name: accessorsOnlyRenamedRetypedClass - Name: accessorsOnlyRenamedRetypedClass
PropertyKind: Class PropertyKind: Class
SwiftImportAsAccessors: true SwiftImportAsAccessors: true
- Name: NewlyGenericSub
SwiftImportAsNonGeneric: true
Protocols: Protocols:
- Name: ProtoWithVersionedUnavailableMember - Name: ProtoWithVersionedUnavailableMember
Methods: Methods:

View File

@@ -15,8 +15,13 @@ __attribute__((objc_root_class))
-(nonnull id)methodWithA:(nonnull id)a; -(nonnull id)methodWithA:(nonnull id)a;
@end @end
__attribute__((objc_root_class))
@interface Base
@end
#endif // __OBJC__ #endif // __OBJC__
#import <APINotesFrameworkTest/Classes.h>
#import <APINotesFrameworkTest/ImportAsMember.h> #import <APINotesFrameworkTest/ImportAsMember.h>
#import <APINotesFrameworkTest/Properties.h> #import <APINotesFrameworkTest/Properties.h>
#import <APINotesFrameworkTest/Protocols.h> #import <APINotesFrameworkTest/Protocols.h>

View File

@@ -0,0 +1,9 @@
#ifdef __OBJC__
#pragma clang assume_nonnull begin
@interface NewlyGenericSub<Element> : Base
+ (Element)defaultElement;
@end
#pragma clang assume_nonnull end
#endif // __OBJC__

View File

@@ -1,10 +1,6 @@
#ifdef __OBJC__ #ifdef __OBJC__
#pragma clang assume_nonnull begin #pragma clang assume_nonnull begin
__attribute__((objc_root_class))
@interface Base
@end
@interface TestProperties: Base @interface TestProperties: Base
@property (nonatomic, readwrite, retain) id accessorsOnly; @property (nonatomic, readwrite, retain) id accessorsOnly;
@property (nonatomic, readwrite, retain, class) id accessorsOnlyForClass; @property (nonatomic, readwrite, retain, class) id accessorsOnlyForClass;

View File

@@ -13,4 +13,14 @@ class ProtoWithVersionedUnavailableMemberImpl: ProtoWithVersionedUnavailableMemb
func requirement() -> Any? { return nil } func requirement() -> Any? { return nil }
} }
func testNonGeneric() {
// CHECK-DIAGS-3:[[@LINE+1]]:{{[0-9]+}}: error: cannot convert value of type 'Any' to specified type 'Int'
let _: Int = NewlyGenericSub.defaultElement()
// CHECK-DIAGS-4:[[@LINE-1]]:{{[0-9]+}}: error: generic parameter 'Element' could not be inferred
// CHECK-DIAGS-3:[[@LINE+1]]:{{[0-9]+}}: error: cannot specialize non-generic type 'NewlyGenericSub'
let _: Int = NewlyGenericSub<Base>.defaultElement()
// CHECK-DIAGS-4:[[@LINE-1]]:{{[0-9]+}}: error: cannot convert value of type 'Base' to specified type 'Int'
}
let unrelatedDiagnostic: Int = nil let unrelatedDiagnostic: Int = nil