Skip to content

Commit 4c366d8

Browse files
authored
Merge pull request #11045 from itaiferber/codable-class-fixes
Codable Class Fixes
2 parents ddea636 + 071caf2 commit 4c366d8

10 files changed

+565
-136
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2072,6 +2072,20 @@ NOTE(codable_codingkeys_type_is_not_an_enum_here,none,
20722072
"cannot automatically synthesize %0 because 'CodingKeys' is not an enum", (Type))
20732073
NOTE(codable_codingkeys_type_does_not_conform_here,none,
20742074
"cannot automatically synthesize %0 because 'CodingKeys' does not conform to CodingKey", (Type))
2075+
NOTE(decodable_no_super_init_here,none,
2076+
"cannot automatically synthesize %0 because superclass does not have a callable %1", (DeclName, DeclName))
2077+
NOTE(decodable_super_init_not_designated_here,none,
2078+
"cannot automatically synthesize %0 because implementation would need to call %1, which is not designated", (DeclName, DeclName))
2079+
NOTE(decodable_inaccessible_super_init_here,none,
2080+
"cannot automatically synthesize %0 because implementation would need to call %1, which is inaccessible due to "
2081+
"'%select{private|fileprivate|internal|%error|%error}2' protection level",
2082+
(DeclName, DeclName, Accessibility))
2083+
NOTE(decodable_super_init_is_failable_here,none,
2084+
"cannot automatically synthesize %0 because implementation would need to call %1, which is failable", (DeclName, DeclName))
2085+
NOTE(decodable_suggest_overriding_init_here,none,
2086+
"did you mean to override 'init(from:)'?", ())
2087+
NOTE(codable_suggest_overriding_init_here,none,
2088+
"did you mean to override 'init(from:)' and 'encode(to:)'?", ())
20752089

20762090
// Dynamic Self
20772091
ERROR(dynamic_self_non_method,none,

lib/Sema/DerivedConformanceCodable.cpp

Lines changed: 181 additions & 76 deletions
Large diffs are not rendered by default.

lib/Sema/DerivedConformanceCodingKey.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,9 @@ static bool canSynthesizeCodingKey(TypeChecker &tc, Decl *parentDecl,
407407
return false;
408408
}
409409

410-
if (!enumDecl->getInherited().empty() &&
411-
enumDecl->getInherited().front().isError())
410+
auto inherited = enumDecl->getInherited();
411+
if (!inherited.empty() && inherited.front().wasValidated() &&
412+
inherited.front().isError())
412413
return false;
413414

414415
// If it meets all of those requirements, we can synthesize CodingKey

lib/Sema/DerivedConformanceRawRepresentable.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,9 @@ static bool canSynthesizeRawRepresentable(TypeChecker &tc, Decl *parentDecl,
343343
auto parentDC = cast<DeclContext>(parentDecl);
344344
rawType = parentDC->mapTypeIntoContext(rawType);
345345

346-
if (!enumDecl->getInherited().empty() &&
347-
enumDecl->getInherited().front().isError())
346+
auto inherited = enumDecl->getInherited();
347+
if (!inherited.empty() && inherited.front().wasValidated() &&
348+
inherited.front().isError())
348349
return false;
349350

350351
// The raw type must be Equatable, so that we have a suitable ~= for

lib/Sema/TypeCheckDecl.cpp

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7944,6 +7944,67 @@ static void diagnoseClassWithoutInitializers(TypeChecker &tc,
79447944
tc.diagnose(classDecl, diag::class_without_init,
79457945
classDecl->getDeclaredType());
79467946

7947+
// HACK: We've got a special case to look out for and diagnose specifically to
7948+
// improve the experience of seeing this, and mitigate some confusion.
7949+
//
7950+
// For a class A which inherits from Decodable class B, class A may have
7951+
// additional members which prevent default initializer synthesis (and
7952+
// inheritance of other initializers). The user may have assumed that this
7953+
// case would synthesize Encodable/Decodable conformance for class A the same
7954+
// way it may have for class B, or other classes.
7955+
//
7956+
// It is helpful to suggest here that the user may have forgotten to override
7957+
// init(from:) (and encode(to:), if applicable) in a note, before we start
7958+
// listing the members that prevented initializer synthesis.
7959+
// TODO: Add a fixit along with this suggestion.
7960+
if (auto *superclassDecl = classDecl->getSuperclassDecl()) {
7961+
ASTContext &C = tc.Context;
7962+
auto *decodableProto = C.getProtocol(KnownProtocolKind::Decodable);
7963+
auto superclassType = superclassDecl->getDeclaredInterfaceType();
7964+
if (auto ref = tc.conformsToProtocol(superclassType, decodableProto,
7965+
superclassDecl,
7966+
ConformanceCheckOptions(),
7967+
SourceLoc())) {
7968+
// super conforms to Decodable, so we've failed to inherit init(from:).
7969+
// Let's suggest overriding it here.
7970+
//
7971+
// We're going to diagnose on the concrete init(from:) decl if it exists
7972+
// and isn't implicit; otherwise, on the subclass itself.
7973+
ValueDecl *diagDest = classDecl;
7974+
auto initFrom = DeclName(C, C.Id_init, C.Id_from);
7975+
auto result = tc.lookupMember(superclassDecl, superclassType, initFrom,
7976+
NameLookupFlags::ProtocolMembers |
7977+
NameLookupFlags::IgnoreAccessibility);
7978+
7979+
if (!result.empty() && !result.front().getValueDecl()->isImplicit())
7980+
diagDest = result.front().getValueDecl();
7981+
7982+
auto diagName = diag::decodable_suggest_overriding_init_here;
7983+
7984+
// This is also a bit of a hack, but the best place we've got at the
7985+
// moment to suggest this.
7986+
//
7987+
// If the superclass also conforms to Encodable, it's quite
7988+
// likely that the user forgot to override its encode(to:). In this case,
7989+
// we can produce a slightly different diagnostic to suggest doing so.
7990+
auto *encodableProto = C.getProtocol(KnownProtocolKind::Encodable);
7991+
if ((ref = tc.conformsToProtocol(superclassType, encodableProto,
7992+
superclassDecl,
7993+
ConformanceCheckOptions(),
7994+
SourceLoc()))) {
7995+
// We only want to produce this version of the diagnostic if the
7996+
// subclass doesn't directly implement encode(to:).
7997+
// The direct lookup here won't see an encode(to:) if it is inherited
7998+
// from the superclass.
7999+
auto encodeTo = DeclName(C, C.Id_encode, C.Id_to);
8000+
if (classDecl->lookupDirect(encodeTo).empty())
8001+
diagName = diag::codable_suggest_overriding_init_here;
8002+
}
8003+
8004+
tc.diagnose(diagDest, diagName);
8005+
}
8006+
}
8007+
79478008
for (auto member : classDecl->getMembers()) {
79488009
auto pbd = dyn_cast<PatternBindingDecl>(member);
79498010
if (!pbd)
@@ -8131,14 +8192,49 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) {
81318192
bool FoundMemberwiseInitializedProperty = false;
81328193
bool SuppressDefaultInitializer = false;
81338194
bool SuppressMemberwiseInitializer = false;
8195+
bool FoundSynthesizedInit = false;
81348196
bool FoundDesignatedInit = false;
81358197
decl->setAddedImplicitInitializers();
8198+
8199+
// Before we look for constructors, we need to make sure that all synthesized
8200+
// initializers are properly synthesized.
8201+
//
8202+
// NOTE: Lookups of synthesized initializers MUST come after
8203+
// decl->setAddedImplicitInitializers() in case synthesis requires
8204+
// protocol conformance checking, which might be recursive here.
8205+
// FIXME: Disable this code and prevent _any_ implicit constructors from doing
8206+
// this. Investigate why this hasn't worked otherwise.
8207+
DeclName synthesizedInitializers[1] = {
8208+
// init(from:) is synthesized by derived conformance to Decodable.
8209+
DeclName(Context, DeclBaseName(Context.Id_init), Context.Id_from)
8210+
};
8211+
8212+
auto initializerIsSynthesized = [=](ConstructorDecl *initializer) {
8213+
if (!initializer->isImplicit())
8214+
return false;
8215+
8216+
for (auto &name : synthesizedInitializers)
8217+
if (initializer->getFullName() == name)
8218+
return true;
8219+
8220+
return false;
8221+
};
8222+
8223+
for (auto &name : synthesizedInitializers) {
8224+
synthesizeMemberForLookup(decl, name);
8225+
}
8226+
81368227
SmallPtrSet<CanType, 4> initializerParamTypes;
81378228
llvm::SmallPtrSet<ConstructorDecl *, 4> overriddenInits;
81388229
for (auto member : decl->getMembers()) {
81398230
if (auto ctor = dyn_cast<ConstructorDecl>(member)) {
8140-
if (ctor->isDesignatedInit())
8231+
// Synthesized initializers others than the default initializer should
8232+
// not prevent default initializer synthesis.
8233+
if (initializerIsSynthesized(ctor)) {
8234+
FoundSynthesizedInit = true;
8235+
} else if (ctor->isDesignatedInit()) {
81418236
FoundDesignatedInit = true;
8237+
}
81428238

81438239
if (!ctor->isInvalid())
81448240
initializerParamTypes.insert(getInitializerParamType(ctor));
@@ -8233,7 +8329,8 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) {
82338329

82348330
// We can't define these overrides if we have any uninitialized
82358331
// stored properties.
8236-
if (SuppressDefaultInitializer && !FoundDesignatedInit) {
8332+
if (SuppressDefaultInitializer && !FoundDesignatedInit
8333+
&& !FoundSynthesizedInit) {
82378334
diagnoseClassWithoutInitializers(*this, classDecl);
82388335
return;
82398336
}
@@ -8325,7 +8422,9 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) {
83258422

83268423
// ... unless there are uninitialized stored properties.
83278424
if (SuppressDefaultInitializer) {
8328-
diagnoseClassWithoutInitializers(*this, classDecl);
8425+
if (!FoundSynthesizedInit)
8426+
diagnoseClassWithoutInitializers(*this, classDecl);
8427+
83298428
return;
83308429
}
83318430

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
// A class with no initializers (which has non-initialized properties so a
4+
// default constructor can be synthesized) should produce an error.
5+
class NoInitializers { // expected-error {{class 'NoInitializers' has no initializers}}
6+
// expected-note@-1 {{did you mean 'deinit'?}}
7+
var x: Double // expected-note {{stored property 'x' without initial value prevents synthesized initializers}}
8+
9+
func foo() {
10+
// The class should not receive a default constructor.
11+
let _ = NoInitializers.init() // expected-error {{type 'NoInitializers' has no member 'init'}}
12+
}
13+
}
14+
15+
// A similar class with Codable properties adopting Codable should get a
16+
// synthesized init(from:), and thus not warn.
17+
class CodableNoExplicitInitializers : Codable {
18+
var x: Double
19+
20+
func foo() {
21+
// The class should receive a synthesized init(from:) and encode(to:)
22+
let _ = CodableNoExplicitInitializers.init(from:)
23+
let _ = CodableNoExplicitInitializers.encode(to:)
24+
25+
// It should not, however, receive a default constructor.
26+
let _ = CodableNoExplicitInitializers.init() // expected-error {{missing argument for parameter 'from' in call}}
27+
}
28+
}
29+
30+
// A class with all initialized properties should receive a default constructor.
31+
class DefaultConstructed {
32+
var x: Double = .pi
33+
34+
func foo() {
35+
let _ = DefaultConstructed.init()
36+
}
37+
}
38+
39+
// A class with all initialized, Codable properties adopting Codable should get
40+
// the default constructor, along with a synthesized init(from:).
41+
class CodableDefaultConstructed : Codable {
42+
var x: Double = .pi
43+
44+
func foo() {
45+
let _ = CodableDefaultConstructed.init()
46+
let _ = CodableDefaultConstructed.init(from:)
47+
let _ = CodableDefaultConstructed.encode(to:)
48+
}
49+
}

test/decl/protocol/special/coding/class_codable_failure_diagnostics.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ class C1 : Codable {
88
var c: Nested = Nested()
99

1010
// CHECK: error: type 'C1' does not conform to protocol 'Decodable'
11-
// CHECK: note: cannot automatically synthesize 'Decodable' because 'C1.Nested' does not conform to 'Decodable'
1211
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
12+
// CHECK: note: cannot automatically synthesize 'Decodable' because 'C1.Nested' does not conform to 'Decodable'
1313

1414
// CHECK: error: type 'C1' does not conform to protocol 'Encodable'
15-
// CHECK: note: cannot automatically synthesize 'Encodable' because 'C1.Nested' does not conform to 'Encodable'
1615
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
16+
// CHECK: note: cannot automatically synthesize 'Encodable' because 'C1.Nested' does not conform to 'Encodable'
1717
}
1818

1919
// Codable class with non-enum CodingKeys.
@@ -30,12 +30,12 @@ class C2 : Codable {
3030
}
3131

3232
// CHECK: error: type 'C2' does not conform to protocol 'Decodable'
33-
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum
3433
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
34+
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum
3535

3636
// CHECK: error: type 'C2' does not conform to protocol 'Encodable'
37-
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum
3837
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
38+
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum
3939
}
4040

4141
// Codable class with CodingKeys not conforming to CodingKey.
@@ -51,12 +51,12 @@ class C3 : Codable {
5151
}
5252

5353
// CHECK: error: type 'C3' does not conform to protocol 'Decodable'
54-
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey
5554
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
55+
// CHECK: note: cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey
5656

5757
// CHECK: error: type 'C3' does not conform to protocol 'Encodable'
58-
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey
5958
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
59+
// CHECK: note: cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey
6060
}
6161

6262
// Codable class with extraneous CodingKeys
@@ -75,16 +75,16 @@ class C4 : Codable {
7575
}
7676

7777
// CHECK: error: type 'C4' does not conform to protocol 'Decodable'
78+
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
7879
// CHECK: note: CodingKey case 'a2' does not match any stored properties
7980
// CHECK: note: CodingKey case 'b2' does not match any stored properties
8081
// CHECK: note: CodingKey case 'c2' does not match any stored properties
81-
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
8282

8383
// CHECK: error: type 'C4' does not conform to protocol 'Encodable'
84+
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
8485
// CHECK: note: CodingKey case 'a2' does not match any stored properties
8586
// CHECK: note: CodingKey case 'b2' does not match any stored properties
8687
// CHECK: note: CodingKey case 'c2' does not match any stored properties
87-
// CHECK: note: protocol requires function 'encode(to:)' with type 'Encodable'
8888
}
8989

9090
// Codable class with non-decoded property (which has no default value).
@@ -98,12 +98,12 @@ class C5 : Codable {
9898
case c
9999
}
100100

101-
// CHECK: error: class 'C5' has no initializers
102-
// CHECK: note: stored property 'b' without initial value prevents synthesized initializers
103-
104101
// CHECK: error: type 'C5' does not conform to protocol 'Decodable'
105-
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
106102
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
103+
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
104+
105+
// CHECK: error: class 'C5' has no initializers
106+
// CHECK: note: stored property 'b' without initial value prevents synthesized initializers
107107
}
108108

109109
// Codable class with non-decoded property (which has no default value).
@@ -122,8 +122,8 @@ class C6 : Codable {
122122
}
123123

124124
// CHECK: error: type 'C6' does not conform to protocol 'Decodable'
125-
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
126125
// CHECK: note: protocol requires initializer 'init(from:)' with type 'Decodable'
126+
// CHECK: note: cannot automatically synthesize 'Decodable' because 'b' does not have a matching CodingKey and does not have a default value
127127
}
128128

129129
// Classes cannot yet synthesize Encodable or Decodable in extensions.

0 commit comments

Comments
 (0)