diff --git a/lib/PrintAsObjC/PrintAsObjC.cpp b/lib/PrintAsObjC/PrintAsObjC.cpp index 24fe54fb9c0..9d64c740a73 100644 --- a/lib/PrintAsObjC/PrintAsObjC.cpp +++ b/lib/PrintAsObjC/PrintAsObjC.cpp @@ -500,6 +500,12 @@ private: return M.getASTContext().TheEmptyTupleType; return result; } + + /// Returns true if \p sel is the no-argument selector 'init'. + static bool selectorIsInit(ObjCSelector sel) { + return sel.getNumArgs() == 0 && + sel.getSelectorPieces().front().str() == "init"; + } void printAbstractFunctionAsMethod(AbstractFunctionDecl *AFD, bool isClassMethod, @@ -605,6 +611,7 @@ private: bool skipAvailability = false; bool makeNewUnavailable = false; + bool makeNewExplicitlyAvailable = false; // Swift designated initializers are Objective-C designated initializers. if (auto ctor = dyn_cast(AFD)) { if (ctor->hasStubImplementation() @@ -614,12 +621,34 @@ private: os << " SWIFT_UNAVAILABLE"; skipAvailability = true; // If -init is unavailable, then +new should be, too: - const bool selectorIsInit = selector.getNumArgs() == 0 && selectorPieces.front().str() == "init"; - makeNewUnavailable = selectorIsInit; - } else if (ctor->isDesignatedInit() && - !isa(ctor->getDeclContext())) { - os << " OBJC_DESIGNATED_INITIALIZER"; + makeNewUnavailable = selectorIsInit(selector); + } else { + if (ctor->isDesignatedInit() && + !isa(ctor->getDeclContext())) { + os << " OBJC_DESIGNATED_INITIALIZER"; + } + + // If -init is newly available, +new should be as well if the class + // inherits from NSObject. + if (selectorIsInit(selector) && !ctor->getOverriddenDecl()) { + auto container = ctor->getDeclContext(); + auto *classDecl = container->getAsClassOrClassExtensionContext(); + if (!classDecl) { + assert(container->getAsProtocolOrProtocolExtensionContext()); + } else { + while (classDecl->hasSuperclass()) { + classDecl = classDecl->getSuperclassDecl(); + assert(classDecl && + "shouldn't PrintAsObjC with invalid superclasses"); + } + if (classDecl->hasClangNode() && + classDecl->getNameStr() == "NSObject") { + makeNewExplicitlyAvailable = true; + } + } + } } + if (!looksLikeInitMethod(AFD->getObjCSelector())) { os << " SWIFT_METHOD_FAMILY(init)"; } @@ -649,7 +678,10 @@ private: os << ";\n"; if (makeNewUnavailable) { - os << "+ (nonnull instancetype)new SWIFT_UNAVAILABLE;\n"; + assert(!makeNewExplicitlyAvailable); + os << "+ (nonnull instancetype)new SWIFT_UNAVAILABLE;\n"; + } else if (makeNewExplicitlyAvailable) { + os << "+ (nonnull instancetype)new;\n"; } } diff --git a/test/PrintAsObjC/classes.swift b/test/PrintAsObjC/classes.swift index 4dca51c6564..663bd6060f0 100644 --- a/test/PrintAsObjC/classes.swift +++ b/test/PrintAsObjC/classes.swift @@ -433,6 +433,41 @@ class MyObject : NSObject {} @objc class Subclass : NestedSuperclass {} } +// CHECK-LABEL: @interface NewBanned +// CHECK-NEXT: - (nonnull instancetype)initWithArbitraryArgument:(NSInteger)arbitraryArgument OBJC_DESIGNATED_INITIALIZER; +// CHECK-NEXT: - (nonnull instancetype)init SWIFT_UNAVAILABLE; +// CHECK-NEXT: + (nonnull instancetype)new SWIFT_UNAVAILABLE; +// CHECK-NEXT: @end +@objc class NewBanned : NSObject { + init(arbitraryArgument: Int) { super.init() } +} + +// CHECK-LABEL: @interface NewBanned +// CHECK-NEXT: - (nonnull instancetype)initWithDifferentArbitraryArgument:(NSInteger)differentArbitraryArgument OBJC_DESIGNATED_INITIALIZER; +// CHECK-NEXT: - (nonnull instancetype)initWithArbitraryArgument:(NSInteger)arbitraryArgument SWIFT_UNAVAILABLE; +// CHECK-NEXT: @end +@objc class NewBannedStill : NewBanned { + init(differentArbitraryArgument: Int) { super.init(arbitraryArgument: 0) } +} + +// CHECK-LABEL: @interface NewUnbanned +// CHECK-NEXT: - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +// CHECK-NEXT: + (nonnull instancetype)new; +// CHECK-NEXT: - (nonnull instancetype)initWithArbitraryArgument:(NSInteger)arbitraryArgument SWIFT_UNAVAILABLE; +// CHECK-NEXT: @end +@objc class NewUnbanned : NewBanned { + init() { super.init(arbitraryArgument: 0) } +} + +// CHECK-LABEL: @interface NewUnbannedDouble +// CHECK-NEXT: - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +// CHECK-NEXT: + (nonnull instancetype)new; +// CHECK-NEXT: - (nonnull instancetype)initWithDifferentArbitraryArgument:(NSInteger)differentArbitraryArgument SWIFT_UNAVAILABLE; +// CHECK-NEXT: @end +@objc class NewUnbannedDouble : NewBannedStill { + init() { super.init(differentArbitraryArgument: 0) } +} + // NEGATIVE-NOT: @interface Private : private class Private : A1 {} diff --git a/validation-test/PrintAsObjC/Inputs/reintroduced-new.swift b/validation-test/PrintAsObjC/Inputs/reintroduced-new.swift new file mode 100644 index 00000000000..ec24ee63478 --- /dev/null +++ b/validation-test/PrintAsObjC/Inputs/reintroduced-new.swift @@ -0,0 +1,8 @@ +import ObjectiveC + +public class Base : NSObject { + public init(foo: Int) { super.init() } +} +public class Sub: Base { + @objc public init() { super.init(foo: 0) } +} diff --git a/validation-test/PrintAsObjC/reintroduced-new.m b/validation-test/PrintAsObjC/reintroduced-new.m new file mode 100644 index 00000000000..a595e794bbe --- /dev/null +++ b/validation-test/PrintAsObjC/reintroduced-new.m @@ -0,0 +1,26 @@ +// REQUIRES: objc_interop + +// RUN: %empty-directory(%t) + +// FIXME: BEGIN -enable-source-import hackaround +// RUN: %target-swift-frontend(mock-sdk: -sdk %S/../../test/Inputs/clang-importer-sdk -I %t) -emit-module -o %t %S/../../test/Inputs/clang-importer-sdk/swift-modules/ObjectiveC.swift +// FIXME: END -enable-source-import hackaround + +// RUN: %target-swift-frontend(mock-sdk: -sdk %S/../../test/Inputs/clang-importer-sdk -I %t) -emit-module -o %t %S/Inputs/reintroduced-new.swift -disable-objc-attr-requires-foundation-module -module-name main +// RUN: %target-swift-frontend(mock-sdk: -sdk %S/../../test/Inputs/clang-importer-sdk -I %t) -parse-as-library %t/main.swiftmodule -typecheck -emit-objc-header-path %t/generated.h -disable-objc-attr-requires-foundation-module +// RUN: not %clang -fsyntax-only -x objective-c %s -include %t/generated.h -fobjc-arc -fmodules -Werror -isysroot %S/../../test/Inputs/clang-importer-sdk 2>&1 | %FileCheck %s + +// CHECK-NOT: error: + +void test() { + // CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: 'init' is unavailable + (void)[[Base alloc] init]; + // CHECK-NOT: error: + (void)[[Sub alloc] init]; + // CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: 'new' is unavailable + (void)[Base new]; + // CHECK-NOT: error: + (void)[Sub new]; +} + +// CHECK-NOT: error: \ No newline at end of file