Why does subclassing a Codable class produce "class has no initializers"?

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:

  1. 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:

    • The type directly implements the requirement (e.g., implements a method or associated type required by the protocol)
    • The type is class-bound and has a superclass which implements the requirement (such that it could be inherited by this type)
    • The requirement can be implemented for the type via code synthesis

    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)

  2. 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.

1 Like

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... :slight_smile:

I'll definitely do just that if no one else beats me to it! I've never written a pitch before

1 Like
Terms of Service

Privacy Policy

Cookie Policy