Skip to content

Commit 5f34931

Browse files
authored
Merge pull request #9758 from itaiferber/codable-derivation-improvements
Improve experience of derivation of Codable types
2 parents 2e04e24 + 5eb58ef commit 5f34931

15 files changed

+568
-267
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,6 +2008,9 @@ NOTE(enum_raw_value_incrementing_from_zero,none,
20082008

20092009
// Derived conformances
20102010

2011+
ERROR(cannot_synthesize_in_extension,none,
2012+
"implementation of %0 cannot be automatically synthesized in an extension yet", (Type))
2013+
20112014
ERROR(broken_raw_representable_requirement,none,
20122015
"RawRepresentable protocol is broken: unexpected requirement", ())
20132016
ERROR(broken_equatable_requirement,none,
@@ -2029,6 +2032,17 @@ ERROR(broken_encodable_requirement,none,
20292032
ERROR(broken_decodable_requirement,none,
20302033
"Decodable protocol is broken: unexpected requirement", ())
20312034

2035+
NOTE(codable_extraneous_codingkey_case_here,none,
2036+
"CodingKey case %0 does match any stored properties", (Identifier))
2037+
NOTE(codable_non_conforming_property_here,none,
2038+
"cannot automatically synthesize %0 because %1 does not conform to %0", (Type, Identifier))
2039+
NOTE(codable_non_decoded_property_here,none,
2040+
"cannot automatically synthesize %0 because %1 does not have a matching CodingKey and does not have a default value", (Type, Identifier))
2041+
NOTE(codable_codingkeys_type_is_not_an_enum_here,none,
2042+
"cannot automatically synthesize %0 because 'CodingKeys' is not an enum", (Type))
2043+
NOTE(codable_codingkeys_type_does_not_conform_here,none,
2044+
"cannot automatically synthesize %0 because 'CodingKeys' does not conform to CodingKey", (Type))
2045+
20322046
// Dynamic Self
20332047
ERROR(dynamic_self_non_method,none,
20342048
"%select{global|local}0 function cannot return 'Self'", (bool))

lib/Sema/DerivedConformanceCodable.cpp

Lines changed: 247 additions & 189 deletions
Large diffs are not rendered by default.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
// Simple classes with all Codable properties whose CodingKeys come from a
4+
// typealias should get derived conformance to Codable.
5+
class SimpleClass : Codable {
6+
var x: Int = 1
7+
var y: Double = .pi
8+
static var z: String = "foo"
9+
10+
private typealias CodingKeys = A // expected-note {{'CodingKeys' declared here}}
11+
private typealias A = B
12+
private typealias B = C
13+
private typealias C = MyRealCodingKeys
14+
15+
private enum MyRealCodingKeys : String, CodingKey {
16+
case x
17+
case y
18+
}
19+
}
20+
21+
// They should receive synthesized init(from:) and an encode(to:).
22+
let _ = SimpleClass.init(from:)
23+
let _ = SimpleClass.encode(to:)
24+
25+
// The synthesized CodingKeys type should not be accessible from outside the
26+
// class.
27+
let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,10 @@ class ClassWithComputedMembers : Codable {
2727
}
2828
}
2929

30-
// These are wrapped in a dummy function to avoid binding a global variable.
31-
func foo() {
32-
// They should receive synthesized init(from:) and an encode(to:).
33-
let _ = ClassWithComputedMembers.init(from:)
34-
let _ = ClassWithComputedMembers.encode(to:)
30+
// They should receive synthesized init(from:) and an encode(to:).
31+
let _ = ClassWithComputedMembers.init(from:)
32+
let _ = ClassWithComputedMembers.encode(to:)
3533

36-
// The synthesized CodingKeys type should not be accessible from outside the
37-
// class.
38-
let _ = ClassWithComputedMembers.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
39-
}
34+
// The synthesized CodingKeys type should not be accessible from outside the
35+
// class.
36+
let _ = ClassWithComputedMembers.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
struct NonCodable {}
4+
5+
// Classes which have a default property that is not Codable, but which has a
6+
// default value and is skipped in its CodingKeys should still derive
7+
// conformance.
8+
class SimpleClass : Codable {
9+
var w: NonCodable = NonCodable()
10+
var x: Int = 1
11+
var y: Double = .pi
12+
static var z: String = "foo"
13+
14+
private enum CodingKeys : String, CodingKey { // expected-note {{'CodingKeys' declared here}}
15+
case x
16+
case y
17+
}
18+
}
19+
20+
// They should receive synthesized init(from:) and an encode(to:).
21+
let _ = SimpleClass.init(from:)
22+
let _ = SimpleClass.encode(to:)
23+
24+
// The synthesized CodingKeys type should not be accessible from outside the
25+
// class.
26+
let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,10 @@ class SimpleChildClass : SimpleClass {
4343
}
4444
}
4545

46-
// These are wrapped in a dummy function to avoid binding a global variable.
47-
func foo() {
48-
// They should receive synthesized init(from:) and an encode(to:).
49-
let _ = SimpleChildClass.init(from:)
50-
let _ = SimpleChildClass.encode(to:)
51-
52-
// The synthesized CodingKeys type should not be accessible from outside the
53-
// class.
54-
let _ = SimpleChildClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
55-
}
46+
// They should receive synthesized init(from:) and an encode(to:).
47+
let _ = SimpleChildClass.init(from:)
48+
let _ = SimpleChildClass.encode(to:)
49+
50+
// The synthesized CodingKeys type should not be accessible from outside the
51+
// class.
52+
let _ = SimpleChildClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
// Classes with a CodingKeys entity which is not a type should not derive
4+
// conformance.
5+
class InvalidCodingKeys1 : Codable { // expected-error {{type 'InvalidCodingKeys1' does not conform to protocol 'Decodable'}}
6+
// expected-error@-1 {{type 'InvalidCodingKeys1' does not conform to protocol 'Encodable'}}
7+
let CodingKeys = 5 // expected-note {{cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum}}
8+
// expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum}}
9+
}
10+
11+
// Classes with a CodingKeys entity which does not conform to CodingKey should
12+
// not derive conformance.
13+
class InvalidCodingKeys2 : Codable { // expected-error {{type 'InvalidCodingKeys2' does not conform to protocol 'Decodable'}}
14+
// expected-error@-1 {{type 'InvalidCodingKeys2' does not conform to protocol 'Encodable'}}
15+
enum CodingKeys {} // expected-note {{cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey}}
16+
// expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey}}
17+
}
18+
19+
// Classes with a CodingKeys entity which is not an enum should not derive
20+
// conformance.
21+
class InvalidCodingKeys3 : Codable { // expected-error {{type 'InvalidCodingKeys3' does not conform to protocol 'Decodable'}}
22+
// expected-error@-1 {{type 'InvalidCodingKeys3' does not conform to protocol 'Encodable'}}
23+
struct CodingKeys : CodingKey { // expected-note {{cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum}}
24+
// expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum}}
25+
var stringValue: String
26+
init?(stringValue: String) {
27+
self.stringValue = stringValue
28+
self.intValue = nil
29+
}
30+
31+
var intValue: Int?
32+
init?(intValue: Int) {
33+
self.stringValue = "\(intValue)"
34+
self.intValue = intValue
35+
}
36+
}
37+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
struct NonCodable {}
4+
5+
// Classes whose properties are not all Codable should fail to synthesize
6+
// conformance.
7+
class NonConformingClass : Codable { // expected-error {{type 'NonConformingClass' does not conform to protocol 'Decodable'}}
8+
// expected-error@-1 {{type 'NonConformingClass' does not conform to protocol 'Decodable'}}
9+
// expected-error@-2 {{type 'NonConformingClass' does not conform to protocol 'Encodable'}}
10+
// expected-error@-3 {{type 'NonConformingClass' does not conform to protocol 'Encodable'}}
11+
// expected-note@-4 {{did you mean 'init'?}}
12+
var w: NonCodable = NonCodable() // expected-note {{cannot automatically synthesize 'Decodable' because 'w' does not conform to 'Decodable'}}
13+
// expected-note@-1 {{cannot automatically synthesize 'Decodable' because 'w' does not conform to 'Decodable'}}
14+
// expected-note@-2 {{cannot automatically synthesize 'Encodable' because 'w' does not conform to 'Encodable'}}
15+
// expected-note@-3 {{cannot automatically synthesize 'Encodable' because 'w' does not conform to 'Encodable'}}
16+
var x: Int = 1
17+
var y: Double = .pi
18+
static var z: String = "foo"
19+
20+
// These lines have to be within the NonConformingClass type because
21+
// CodingKeys should be private.
22+
func foo() {
23+
// They should not get a CodingKeys type.
24+
let _ = NonConformingClass.CodingKeys.self // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
25+
let _ = NonConformingClass.CodingKeys.x // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
26+
let _ = NonConformingClass.CodingKeys.y // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
27+
let _ = NonConformingClass.CodingKeys.z // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}
28+
}
29+
}
30+
31+
// They should not receive Codable methods.
32+
let _ = NonConformingClass.init(from:) // expected-error {{type 'NonConformingClass' has no member 'init(from:)'}}
33+
let _ = NonConformingClass.encode(to:) // expected-error {{type 'NonConformingClass' has no member 'encode(to:)'}}
34+
35+
// They should not get a CodingKeys type.
36+
let _ = NonConformingClass.CodingKeys.self // expected-error {{type 'NonConformingClass' has no member 'CodingKeys'}}

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,10 @@ class SimpleClass : Codable {
2222
}
2323
}
2424

25-
// These are wrapped in a dummy function to avoid binding a global variable.
26-
func foo() {
27-
// They should receive synthesized init(from:) and an encode(to:).
28-
let _ = SimpleClass.init(from:)
29-
let _ = SimpleClass.encode(to:)
25+
// They should receive synthesized init(from:) and an encode(to:).
26+
let _ = SimpleClass.init(from:)
27+
let _ = SimpleClass.encode(to:)
3028

31-
// The synthesized CodingKeys type should not be accessible from outside the
32-
// class.
33-
let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
34-
}
29+
// The synthesized CodingKeys type should not be accessible from outside the
30+
// class.
31+
let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,29 @@
1-
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
1+
// RUN: %target-typecheck-verify-swift
22

3-
// Simple classes where Codable conformance is added in extensions should still
4-
// derive conformance.
5-
class SimpleClass {
3+
// Simple classes where Codable conformance is added in extensions should not
4+
// derive conformance yet.
5+
class SimpleClass { // expected-note {{did you mean 'init'?}}
66
var x: Int = 1
77
var y: Double = .pi
88
static var z: String = "foo"
99

10-
// These lines have to be within the SimpleClass type because CodingKeys
11-
// should be private.
1210
func foo() {
13-
// They should receive a synthesized CodingKeys enum.
14-
let _ = SimpleClass.CodingKeys.self
15-
16-
// The enum should have a case for each of the vars.
17-
let _ = SimpleClass.CodingKeys.x
18-
let _ = SimpleClass.CodingKeys.y
19-
20-
// Static vars should not be part of the CodingKeys enum.
21-
let _ = SimpleClass.CodingKeys.z // expected-error {{type 'SimpleClass.CodingKeys' has no member 'z'}}
11+
// They should not get a CodingKeys type.
12+
let _ = SimpleClass.CodingKeys.self // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
13+
let _ = SimpleClass.CodingKeys.x // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
14+
let _ = SimpleClass.CodingKeys.y // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
15+
let _ = SimpleClass.CodingKeys.z // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
2216
}
2317
}
2418

25-
extension SimpleClass : Codable {}
19+
extension SimpleClass : Codable {} // expected-error {{implementation of 'Decodable' cannot be automatically synthesized in an extension}}
20+
// expected-error@-1 {{implementation of 'Decodable' cannot be automatically synthesized in an extension}}
21+
// expected-error@-2 {{implementation of 'Encodable' cannot be automatically synthesized in an extension}}
22+
// expected-error@-3 {{implementation of 'Encodable' cannot be automatically synthesized in an extension}}
2623

27-
// These are wrapped in a dummy function to avoid binding a global variable.
28-
func foo() {
29-
// They should receive synthesized init(from:) and an encode(to:).
30-
let _ = SimpleClass.init(from:)
31-
let _ = SimpleClass.encode(to:)
24+
// They should not receive Codable methods.
25+
let _ = SimpleClass.init(from:) // expected-error {{type 'SimpleClass' has no member 'init(from:)'}}
26+
let _ = SimpleClass.encode(to:) // expected-error {{type 'SimpleClass' has no member 'encode(to:)'}}
3227

33-
// The synthesized CodingKeys type should not be accessible from outside the
34-
// class.
35-
let _ = SimpleClass.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
36-
}
28+
// They should not get a CodingKeys type.
29+
let _ = SimpleClass.CodingKeys.self // expected-error {{type 'SimpleClass' has no member 'CodingKeys'}}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
// Simple structs with all Codable properties whose CodingKeys come from a
4+
// typealias should get derived conformance to Codable.
5+
struct SimpleStruct : Codable {
6+
var x: Int = 1
7+
var y: Double = .pi
8+
static var z: String = "foo"
9+
10+
private typealias CodingKeys = A // expected-note {{'CodingKeys' declared here}}
11+
private typealias A = B
12+
private typealias B = C
13+
private typealias C = MyRealCodingKeys
14+
15+
private enum MyRealCodingKeys : String, CodingKey {
16+
case x
17+
case y
18+
}
19+
}
20+
21+
// They should receive synthesized init(from:) and an encode(to:).
22+
let _ = SimpleStruct.init(from:)
23+
let _ = SimpleStruct.encode(to:)
24+
25+
// The synthesized CodingKeys type should not be accessible from outside the
26+
// struct.
27+
let _ = SimpleStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
struct NonCodable {}
4+
5+
// Structs which have a default property that is not Codable, but which has a
6+
// default value and is skipped in its CodingKeys should still derive
7+
// conformance.
8+
struct SimpleStruct : Codable {
9+
var w: NonCodable = NonCodable()
10+
var x: Int
11+
var y: Double
12+
static var z: String = "foo"
13+
14+
private enum CodingKeys : String, CodingKey { // expected-note {{'CodingKeys' declared here}}
15+
case x
16+
case y
17+
}
18+
}
19+
20+
// They should receive synthesized init(from:) and an encode(to:).
21+
let _ = SimpleStruct.init(from:)
22+
let _ = SimpleStruct.encode(to:)
23+
24+
// The synthesized CodingKeys type should not be accessible from outside the
25+
// struct.
26+
let _ = SimpleStruct.CodingKeys.self // expected-error {{'CodingKeys' is inaccessible due to 'private' protection level}}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
// Structs with a CodingKeys entity which is not a type should not derive
4+
// conformance.
5+
struct InvalidCodingKeys1 : Codable { // expected-error {{type 'InvalidCodingKeys1' does not conform to protocol 'Decodable'}}
6+
// expected-error@-1 {{type 'InvalidCodingKeys1' does not conform to protocol 'Encodable'}}
7+
let CodingKeys = 5 // expected-note {{cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum}}
8+
// expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum}}
9+
}
10+
11+
// Structs with a CodingKeys entity which does not conform to CodingKey should
12+
// not derive conformance.
13+
struct InvalidCodingKeys2 : Codable { // expected-error {{type 'InvalidCodingKeys2' does not conform to protocol 'Decodable'}}
14+
// expected-error@-1 {{type 'InvalidCodingKeys2' does not conform to protocol 'Encodable'}}
15+
enum CodingKeys {} // expected-note {{cannot automatically synthesize 'Decodable' because 'CodingKeys' does not conform to CodingKey}}
16+
// expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' does not conform to CodingKey}}
17+
}
18+
19+
// Structs with a CodingKeys entity which is not an enum should not derive
20+
// conformance.
21+
struct InvalidCodingKeys3 : Codable { // expected-error {{type 'InvalidCodingKeys3' does not conform to protocol 'Decodable'}}
22+
// expected-error@-1 {{type 'InvalidCodingKeys3' does not conform to protocol 'Encodable'}}
23+
struct CodingKeys : CodingKey { // expected-note {{cannot automatically synthesize 'Decodable' because 'CodingKeys' is not an enum}}
24+
// expected-note@-1 {{cannot automatically synthesize 'Encodable' because 'CodingKeys' is not an enum}}
25+
var stringValue: String
26+
init?(stringValue: String) {
27+
self.stringValue = stringValue
28+
self.intValue = nil
29+
}
30+
31+
var intValue: Int?
32+
init?(intValue: Int) {
33+
self.stringValue = "\(intValue)"
34+
self.intValue = intValue
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)