[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
SafariServices
SceneKit
ScriptingBridge
SpriteKit
StoreKit
TVMLKit

View File

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

View File

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

View File

@@ -1713,16 +1713,25 @@ getAccessorPropertyType(const clang::FunctionDecl *accessor, bool isSetter,
/// Whether we should suppress importing the Objective-C generic type params
/// of this class as Swift generic type params.
static bool
shouldSuppressGenericParamsImport(const clang::ObjCInterfaceDecl *decl) {
while (decl) {
StringRef name = decl->getName();
if (name == "NSArray" || name == "NSDictionary" || name == "NSSet" ||
name == "NSOrderedSet" || name == "NSEnumerator" ||
name == "NSMeasurement") {
return true;
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) {
StringRef name = decl->getName();
if (name == "NSArray" || name == "NSDictionary" || name == "NSSet" ||
name == "NSOrderedSet" || name == "NSEnumerator" ||
name == "NSMeasurement") {
return true;
}
decl = decl->getSuperClass();
}
decl = decl->getSuperClass();
}
return false;
}
@@ -6149,7 +6158,7 @@ Optional<GenericParamList *> SwiftDeclConverter::importObjCGenericParams(
if (!typeParamList) {
return nullptr;
}
if (shouldSuppressGenericParamsImport(decl)) {
if (shouldSuppressGenericParamsImport(Impl.SwiftContext.LangOpts, decl)) {
return nullptr;
}
assert(typeParamList->size() > 0);

View File

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

View File

@@ -15,8 +15,13 @@ __attribute__((objc_root_class))
-(nonnull id)methodWithA:(nonnull id)a;
@end
__attribute__((objc_root_class))
@interface Base
@end
#endif // __OBJC__
#import <APINotesFrameworkTest/Classes.h>
#import <APINotesFrameworkTest/ImportAsMember.h>
#import <APINotesFrameworkTest/Properties.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__
#pragma clang assume_nonnull begin
__attribute__((objc_root_class))
@interface Base
@end
@interface TestProperties: Base
@property (nonatomic, readwrite, retain) id accessorsOnly;
@property (nonatomic, readwrite, retain, class) id accessorsOnlyForClass;

View File

@@ -13,4 +13,14 @@ class ProtoWithVersionedUnavailableMemberImpl: ProtoWithVersionedUnavailableMemb
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