Example code:
class Thing: Codable {
let id: Int
}
class Person: Thing { // Class 'Person' has no initializers
let name: String
}
Shouldn't the proper initializers be synthesized for both classes in the hierarchy if possible?
Example code:
class Thing: Codable {
let id: Int
}
class Person: Thing { // Class 'Person' has no initializers
let name: String
}
Shouldn't the proper initializers be synthesized for both classes in the hierarchy if possible?
I believe the answer is that Codable
auto-synthesis only works for the declaring type, and subclasses (which add properties) can't inherit a superclass's implementation.
Does anyone know why that is though? And if that behavior is documented anywhere? (I couldn't find any)
cc @itaiferber
Yes, please see the now pretty old SR-4772.
As Avi notes correctly, the issue here is that Codable
synthesis happens only for base classes, and subclasses inherit conformance; in this case, since Person
has added storage, the inheritance can't happen.
The why of this is two-fold:
At least partially, implementation detail (which is not a justifiable long-term answer for this behavior). Code synthesis like this happens during protocol conformance checking: when the type checker checks whether a type conforms to a protocol, it looks at all of the protocol requirements and confirms whether the type satisfies the requirement.
There are a few things which can satisfy such a requirement:
There is some more detail to this, of course, but the above checks are performed in order. This means that right now, when the compiler checks "does Person
implement Codable
?", the answer is "yes" because Thing
has implementations for Codable
protocol requirements, and Person
could inherit them, if valid. (Whether or not they are valid to inherit does not actually weigh in here for the purposes of this check, but this does block synthesis)
The implementation here can be changed over time (though not without risk), but it does reflect the current state of code synthesis in Swift overall: there is currently no precedent for what it means to force synthesis of code over preferring inheritance. Code synthesis is already pretty rare, and Codable
synthesis is one of the few things which can be synthesized for classes. (Classes don't, for instance, get Equatable
or Hashable
synthesis, at least partially because of the ambiguity here)
There is no way at the moment, via syntax or otherwise, to specify to the compiler whether a class should inherit conformance, or force synthesis. If Person
didn't add storage, a synthesized implementation would actually produce different output than if it inherited Thing
's implementation (the whole type would be wrapped in an additional { "super": { ... } }
layer) — would this be intentional, or not? If the compiler always forced synthesis over inheritance, there would similarly be no way to signal that you prefer to inherit in such a case.
Although this can be surprising, it is consistent with general inheritance principles in Swift, and is a more conservative solution which gives us a way forward for iteration. For instance, one possible solution is to allow "redundant" redeclaration of protocol conformance to be signal to the compiler that you do indeed want synthesis; right now,
class Thing: Codable {}
class Person: Thing, Codable {}
produces
error: redundant conformance of 'Person' to protocol 'Decodable'
note: 'Person' inherits conformance to protocol 'Decodable' from superclass here
error: redundant conformance of 'Person' to protocol 'Encodable'
note: 'Person' inherits conformance to protocol 'Encodable' from superclass here
It is possible to evolve this to be the spelling for "please force synthesis over implicitly inheriting conformance" for those protocols for which this is relevant (Codable
, and possibly Hashable
).
Happy to describe things with further specificity if it would help.
I think that about answers all my questions about why this doesn't work, thank you for the detailed answer!
Do you know if a way to do exactly that is in the works? Because there totally should be a way to indicate that you want synthesis in this scenario.
Indeed, I think there should be. There is currently nothing planned - a change like that would require going through Swift Evolution and will likely need to be thoroughly discussed. If you're interested, writing a pitch could always get the conversation going...
I'll definitely do just that if no one else beats me to it! I've never written a pitch before
机器能完成的工作就不应该让人去完成,这个情况只会让程序员写更多无用的代码,这不是一个好现象,希望改进语言,让子类自动实现 Codable
I really have a hard time understanding how they released Codable with this limitation. It makes serialization of an OOP graph require a ton of boilerplate in every class.
Do you know if a third party swift serialization library that avoids/fixes thus - maybe using reflection?
I haven't tried it but here is a list of open source macros: GitHub - krzysztofzablocki/Swift-Macros: A curated list of awesome Swift Macros
Which contains this one that might be helpful for you: GitHub - SwiftyLab/MetaCodable: Supercharge Swift's Codable implementations with macros meta-programming.
Thanks. Looks like it will be much better - only problem is my main development machine has an older swift toolchain 5.7 - and the package wants 5.9... struggling to get xcode to use the downloaded toolchain.
Swift 5.7 is Xcode 14-14.2, you need Xcode 15+ to submit to the app store: Xcode 15 - Upcoming Requirements - Apple Developer
So you will probably have to update your macOS and then your Xcode.
Yes, I have another machine that runs the latest OS and xcode I can use for a final build and release. I just don't understand how XCode shows it is uses the 5.9 toolchain, but I still get that error.
This is an OSX app btw, so I don't think the upload requirements hold - but I did see that you can only upload using the toolchain provided with the XCode.