[PrintAsObjC] Reintroduce +new when reintroducing -init. (#13320)

Follow-up to 49c65facc9 to account for subclasses. An unavailable
-init makes +new unavailable, but reintroducing it in a subclass
should reintroduce +new if the root class is NSObject (where +new is
implemented).

rdar://problem/35914080
This commit is contained in:
Jordan Rose
2017-12-07 15:15:43 -08:00
committed by GitHub
parent 1b24d23c1e
commit 2bc0106810
4 changed files with 107 additions and 6 deletions

View File

@@ -500,6 +500,12 @@ private:
return M.getASTContext().TheEmptyTupleType; return M.getASTContext().TheEmptyTupleType;
return result; 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, void printAbstractFunctionAsMethod(AbstractFunctionDecl *AFD,
bool isClassMethod, bool isClassMethod,
@@ -605,6 +611,7 @@ private:
bool skipAvailability = false; bool skipAvailability = false;
bool makeNewUnavailable = false; bool makeNewUnavailable = false;
bool makeNewExplicitlyAvailable = false;
// Swift designated initializers are Objective-C designated initializers. // Swift designated initializers are Objective-C designated initializers.
if (auto ctor = dyn_cast<ConstructorDecl>(AFD)) { if (auto ctor = dyn_cast<ConstructorDecl>(AFD)) {
if (ctor->hasStubImplementation() if (ctor->hasStubImplementation()
@@ -614,12 +621,34 @@ private:
os << " SWIFT_UNAVAILABLE"; os << " SWIFT_UNAVAILABLE";
skipAvailability = true; skipAvailability = true;
// If -init is unavailable, then +new should be, too: // If -init is unavailable, then +new should be, too:
const bool selectorIsInit = selector.getNumArgs() == 0 && selectorPieces.front().str() == "init"; makeNewUnavailable = selectorIsInit(selector);
makeNewUnavailable = selectorIsInit; } else {
} else if (ctor->isDesignatedInit() && if (ctor->isDesignatedInit() &&
!isa<ProtocolDecl>(ctor->getDeclContext())) { !isa<ProtocolDecl>(ctor->getDeclContext())) {
os << " OBJC_DESIGNATED_INITIALIZER"; 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())) { if (!looksLikeInitMethod(AFD->getObjCSelector())) {
os << " SWIFT_METHOD_FAMILY(init)"; os << " SWIFT_METHOD_FAMILY(init)";
} }
@@ -649,7 +678,10 @@ private:
os << ";\n"; os << ";\n";
if (makeNewUnavailable) { 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";
} }
} }

View File

@@ -433,6 +433,41 @@ class MyObject : NSObject {}
@objc class Subclass : NestedSuperclass {} @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 : // NEGATIVE-NOT: @interface Private :
private class Private : A1 {} private class Private : A1 {}

View File

@@ -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) }
}

View File

@@ -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: