Skip to content

Commit 78bb298

Browse files
committed
ABI/API checker: diagnose adding new designated initializers to open classes
If client's subclass provides an implementation of all of its superclass designated initializers, it automatically inherits all of the superclass convenience initializers. This means if a new designated init is added to the base class, the inherited convenience init may be missing and cause breakage. SR-11454
1 parent ec862c5 commit 78bb298

File tree

14 files changed

+83
-9
lines changed

14 files changed

+83
-9
lines changed

include/swift/AST/DiagnosticsModuleDiffer.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ ERROR(new_decl_without_intro,none,"%0 is a new API without @available attribute"
9898

9999
ERROR(objc_name_change,none,"%0 has ObjC name change from %1 to %2", (StringRef, StringRef, StringRef))
100100

101+
ERROR(desig_init_added,none,"%0 has been added as a designated initializer to an open class", (StringRef))
102+
101103
#ifndef DIAG_NO_UNDEF
102104
# if defined(DIAG)
103105
# undef DIAG

include/swift/IDE/DigesterEnums.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ KEY_STRING(IntrotvOS, intro_tvOS)
152152
KEY_STRING(IntrowatchOS, intro_watchOS)
153153
KEY_STRING(Introswift, intro_swift)
154154
KEY_STRING(ObjCName, objc_name)
155+
KEY_STRING(InitKind, init_kind)
155156

156157
KEY_STRING_ARR(SuperclassNames, superclassNames)
157158
KEY_STRING_ARR(ToolArgs, tool_arguments)

test/api-digester/Inputs/cake_baseline/cake.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,12 @@ public class SwiftObjcClass {
201201
@objc(OldObjCFool:OldObjCA:OldObjCB:)
202202
public func foo(a:Int, b:Int, c: Int) {}
203203
}
204+
205+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
206+
open class AddingNewDesignatedInit {
207+
public init() {}
208+
public convenience init(foo: Int) {
209+
self.init()
210+
print(foo)
211+
}
212+
}

test/api-digester/Inputs/cake_current/cake.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,13 @@ public class SwiftObjcClass {
208208
@objc(NewObjCFool:NewObjCA:NewObjCB:)
209209
public func foo(a:Int, b:Int, c: Int) {}
210210
}
211+
212+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
213+
open class AddingNewDesignatedInit {
214+
public init(_ b: Bool) {}
215+
public init() {}
216+
public convenience init(foo: Int) {
217+
self.init()
218+
print(foo)
219+
}
220+
}

test/api-digester/Outputs/Cake-abi.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,4 @@ cake: Var RequiementChanges.addedVar has been added as a protocol requirement
115115
cake: Class C4 has changed its super class from APINotesTest.OldType to APINotesTest.NewType
116116
cake: Class SubGenericClass has changed its super class from cake.GenericClass<cake.P1> to cake.GenericClass<cake.P2>
117117
cake: Class SuperClassRemoval has removed its super class cake.C3
118+
cake: Constructor AddingNewDesignatedInit.init(_:) has been added as a designated initializer to an open class

test/api-digester/Outputs/Cake.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ cake: Accessor ClassWithOpenMember.property.Get() is no longer open for subclass
6464
cake: Class C4 has changed its super class from APINotesTest.OldType to APINotesTest.NewType
6565
cake: Class SubGenericClass has changed its super class from cake.GenericClass<cake.P1> to cake.GenericClass<cake.P2>
6666
cake: Class SuperClassRemoval has removed its super class cake.C3
67+
cake: Constructor AddingNewDesignatedInit.init(_:) has been added as a designated initializer to an open class
6768
cake: Func ClassWithOpenMember.bar() is no longer open for subclassing
6869
cake: Func ClassWithOpenMember.foo() is no longer open for subclassing
6970
cake: Var ClassWithOpenMember.property is no longer open for subclassing

test/api-digester/Outputs/cake-abi.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,8 @@
564564
"implicit": true,
565565
"declAttributes": [
566566
"Inlinable"
567-
]
567+
],
568+
"init_kind": "Designated"
568569
},
569570
{
570571
"kind": "Var",
@@ -1823,5 +1824,5 @@
18231824
]
18241825
}
18251826
],
1826-
"json_format_version": 5
1827+
"json_format_version": 6
18271828
}

test/api-digester/Outputs/cake.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,8 @@
584584
"implicit": true,
585585
"declAttributes": [
586586
"Inlinable"
587-
]
587+
],
588+
"init_kind": "Designated"
588589
},
589590
{
590591
"kind": "Var",
@@ -1674,5 +1675,5 @@
16741675
]
16751676
}
16761677
],
1677-
"json_format_version": 5
1678+
"json_format_version": 6
16781679
}

test/api-digester/Outputs/clang-module-dump.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@
105105
"Override",
106106
"ObjC",
107107
"Dynamic"
108-
]
108+
],
109+
"init_kind": "Designated"
109110
}
110111
],
111112
"declKind": "Class",
@@ -183,5 +184,5 @@
183184
]
184185
}
185186
],
186-
"json_format_version": 5
187+
"json_format_version": 6
187188
}

test/api-digester/Outputs/empty-baseline.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"kind": "Root",
33
"name": "TopLevel",
44
"printedName": "TopLevel",
5-
"json_format_version": 5
5+
"json_format_version": 6
66
}

tools/swift-api-digester/ModuleAnalyzerNodes.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ SDKNodeDeclFunction::SDKNodeDeclFunction(SDKNodeInitInfo Info):
132132
FuncSelfKind(Info.FuncSelfKind) {}
133133

134134
SDKNodeDeclConstructor::SDKNodeDeclConstructor(SDKNodeInitInfo Info):
135-
SDKNodeDeclAbstractFunc(Info, SDKNodeKind::DeclConstructor) {}
135+
SDKNodeDeclAbstractFunc(Info, SDKNodeKind::DeclConstructor), InitKind(Info.InitKind) {}
136136

137137
SDKNodeDeclAccessor::SDKNodeDeclAccessor(SDKNodeInitInfo Info):
138138
SDKNodeDeclAbstractFunc(Info, SDKNodeKind::DeclAccessor),
@@ -1271,6 +1271,30 @@ static std::vector<DeclAttrKind> collectDeclAttributes(Decl *D) {
12711271
return Results;
12721272
}
12731273

1274+
CtorInitializerKind SDKNodeDeclConstructor::getInitKind() const {
1275+
#define CASE(KIND) if (InitKind == #KIND) return CtorInitializerKind::KIND;
1276+
CASE(Designated)
1277+
CASE(Convenience)
1278+
CASE(ConvenienceFactory)
1279+
CASE(Factory)
1280+
#undef CASE
1281+
llvm_unreachable("unhandled init kind");
1282+
}
1283+
1284+
StringRef SDKContext::getInitKind(Decl *D) {
1285+
if (auto *CD = dyn_cast<ConstructorDecl>(D)) {
1286+
switch(CD->getInitKind()) {
1287+
#define CASE(KIND) case CtorInitializerKind::KIND: return #KIND;
1288+
CASE(Designated)
1289+
CASE(Convenience)
1290+
CASE(ConvenienceFactory)
1291+
CASE(Factory)
1292+
#undef CASE
1293+
}
1294+
}
1295+
return StringRef();
1296+
}
1297+
12741298
SDKNodeInitInfo::SDKNodeInitInfo(SDKContext &Ctx, Decl *D):
12751299
Ctx(Ctx), DKind(D->getKind()),
12761300
Location(calculateLocation(Ctx, D)),
@@ -1285,6 +1309,7 @@ SDKNodeInitInfo::SDKNodeInitInfo(SDKContext &Ctx, Decl *D):
12851309
IntrowatchOS(Ctx.getPlatformIntroVersion(D, PlatformKind::watchOS)),
12861310
Introswift(Ctx.getLanguageIntroVersion(D)),
12871311
ObjCName(Ctx.getObjcName(D)),
1312+
InitKind(Ctx.getInitKind(D)),
12881313
IsImplicit(D->isImplicit()),
12891314
IsDeprecated(D->getAttrs().getDeprecated(D->getASTContext())),
12901315
IsABIPlaceholder(isABIPlaceholderRecursive(D)),
@@ -1916,6 +1941,11 @@ void SDKNodeDeclFunction::jsonize(json::Output &out) {
19161941
output(out, KeyKind::KK_funcSelfKind, FuncSelfKind);
19171942
}
19181943

1944+
void SDKNodeDeclConstructor::jsonize(json::Output &out) {
1945+
SDKNodeDeclAbstractFunc::jsonize(out);
1946+
output(out, KeyKind::KK_init_kind, InitKind);
1947+
}
1948+
19191949
void SDKNodeDeclType::jsonize(json::Output &out) {
19201950
SDKNodeDecl::jsonize(out);
19211951
output(out, KeyKind::KK_superclassUsr, SuperclassUsr);

tools/swift-api-digester/ModuleAnalyzerNodes.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ namespace api {
6262
///
6363
/// When the json format changes in a way that requires version-specific handling, this number should be incremented.
6464
/// This ensures we could have backward compatibility so that version changes in the format won't stop the checker from working.
65-
const uint8_t DIGESTER_JSON_VERSION = 5; // Populate ObjC, Dynamic and Final to attribute list
65+
const uint8_t DIGESTER_JSON_VERSION = 6; // Add initkind for constructors
6666
const uint8_t DIGESTER_JSON_DEFAULT_VERSION = 0; // Use this version number for files before we have a version number in json.
6767

6868
class SDKNode;
@@ -218,6 +218,7 @@ class SDKContext {
218218
StringRef getPlatformIntroVersion(Decl *D, PlatformKind Kind);
219219
StringRef getLanguageIntroVersion(Decl *D);
220220
StringRef getObjcName(Decl *D);
221+
StringRef getInitKind(Decl *D);
221222
bool isEqual(const SDKNode &Left, const SDKNode &Right);
222223
bool checkingABI() const { return Opts.ABI; }
223224
AccessLevel getAccessLevel(const ValueDecl *VD) const;
@@ -677,9 +678,12 @@ class SDKNodeDeclFunction: public SDKNodeDeclAbstractFunc {
677678
};
678679

679680
class SDKNodeDeclConstructor: public SDKNodeDeclAbstractFunc {
681+
StringRef InitKind;
680682
public:
681683
SDKNodeDeclConstructor(SDKNodeInitInfo Info);
682684
static bool classof(const SDKNode *N);
685+
CtorInitializerKind getInitKind() const;
686+
void jsonize(json::Output &Out) override;
683687
};
684688

685689
class SDKNodeDeclAccessor: public SDKNodeDeclAbstractFunc {

tools/swift-api-digester/ModuleDiagsConsumer.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ static StringRef getCategoryName(uint32_t ID) {
7373
case LocalDiagID::super_class_removed:
7474
case LocalDiagID::super_class_changed:
7575
case LocalDiagID::no_longer_open:
76+
case LocalDiagID::desig_init_added:
7677
return "/* Class Inheritance Change */";
7778
default:
7879
return StringRef();

tools/swift-api-digester/swift-api-digester.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,18 @@ class PrunePass : public MatchedNodeListener, public SDKTreeDiffPass {
11441144
}
11451145
}
11461146
}
1147+
if (auto *CD = dyn_cast<SDKNodeDeclConstructor>(Right)) {
1148+
if (auto *TD = dyn_cast<SDKNodeDeclType>(Right->getParent())) {
1149+
if (TD->isOpen() && CD->getInitKind() == CtorInitializerKind::Designated) {
1150+
// If client's subclass provides an implementation of all of its superclass designated
1151+
// initializers, it automatically inherits all of the superclass convenience initializers.
1152+
// This means if a new designated init is added to the base class, the inherited
1153+
// convenience init may be missing and cause breakage.
1154+
CD->emitDiag(diag::desig_init_added);
1155+
}
1156+
}
1157+
}
1158+
11471159
return;
11481160
case NodeMatchReason::Removed:
11491161
assert(!Right);

0 commit comments

Comments
 (0)