Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-6875] Codable codegen blocked by subclass with designated initializer #49424

Open
mattneub opened this issue Jan 30, 2018 · 7 comments
Open
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. Codable Area → standard library: `Codable` and co. compiler The Swift compiler in itself

Comments

@mattneub
Copy link

Previous ID SR-6875
Radar None
Original Reporter @mattneub
Type Bug
Additional Detail from JIRA
Votes 2
Component/s Compiler
Labels Bug, Codable
Assignee None
Priority Medium

md5: 8cbf28e52969ee8f5d0e47bab2efe5df

relates to:

  • SR-5122 Codable conformance compilation error with required initializers

Issue Description:

This issue arose as a complaint from a beginner on Stack Overflow:

https://stackoverflow.com/questions/48491971/how-to-declare-a-subclass-designated-initializer-with-more-parameters-than-super

The OP has misunderstood the problem, which is easily expressed as follows:

class Superclass : Codable {}
class Subclass : Superclass {
    var prop : String?
    init(prop:String) {
        self.prop = prop
    }
}

This gives:

error: 'required' initializer 'init(from:)' must be provided by subclass of 'Superclass'

The trouble is that giving the subclass a designated initializer has cut off inheritance of the Codable injected init(from:). That's normal, but the trouble is that we are therefore forced to write init(from:) manually in the subclass. That's ugly, and many beginners can't handle it at all.

The injected init(from:) (performing codegen by way of a protocol extension) is a real time-saver, and opens Codable to the use of beginners. It seems a pity, therefore, that it should never be able to apply to a subclass of a Codable adopter. In effect, we're cutting off a huge set of object types from its benefits. I wonder whether the language could not be modified to make this work somehow.

@itaiferber
Copy link
Contributor

To loosely bring my comment from SR-5122 over — Codable just performs codegen; there's nothing it can do that you can't write yourself, and vice versa. Making this work for Codable specifically would require us to special-case this through several layers of the compiler, I think.

But, at the very least, I think that we can improve the diagnostic to make it clearer what's going on. Yes, you will still have to manually implement init(from🙂 (which you would have had to do anyway in order to encode/decode the additional properties you're adding), but it would be clearer why if we produced something like "note: subclass initializer 'init(prop: String)' prevented inheritance of 'required init()'; 'required init()' must be overridden".

@belkadan Any thoughts?

@itaiferber
Copy link
Contributor

FWIW, even if Subclass did inherit init(from🙂, I think it would still be pretty confusing as prop wouldn't ever get encoded or decoded (since the implementation is being inherited). This is a problem that we have today that we need to properly diagnose somehow.

@belkadan
Copy link
Contributor

I still think it's a trap that init(from:) can be inherited in the first place; I agree that we would at least want a warning when you did that. To me, that means special-casing this diagnostic makes sense as well.

I really wish we could ask for synthesis somehow.

@mattneub
Copy link
Author

Just to emphasize the point once again: it's not like you and I don't know how to write init(from:) by hand. The problem is that a beginner doesn't know. We've led everyone down this wonderful garden path where you just adopt Codable and everything just works automagically with no code, and then the path turns out to lead into a nest of thorns just because something is subclassed somewhere. That's what happened to the OP in the Stack Overflow question.

@mattneub
Copy link
Author

mattneub commented Feb 1, 2018

This could be a product of the same problem: https://stackoverflow.com/questions/48566443/implementing-codable-for-uicolor The OP expected to be able to make UIColor Codable but couldn't.

@itaiferber
Copy link
Contributor

@mattneub That's a different issue — you can't add required initializers in extensions to non-final classes:

import Foundation

extension NSString: Decodable {
    public required init(from decoder: Decoder) throws {
        self.init(string: "Hello!")
    }
}

produces

Untitled 2.swift:4:21: error: designated initializer cannot be declared in an extension of 'NSString'; did you mean this to be a convenience initializer?
    public required init(from decoder: Decoder) throws {
                    ^
                    convenience 
Untitled 2.swift:4:21: error: 'required' initializer must be declared directly in class 'NSString' (not in an extension)
    public required init(from decoder: Decoder) throws {
           ~~~~~~~~ ^
Untitled 2.swift:4:21: error: initializer requirement 'init(from:)' can only be satisfied by a `required` initializer in the definition of non-final class 'NSString'
    public required init(from decoder: Decoder) throws {
                    ^

in the same way that

import Foundation

extension NSString {
    public required init<T>(_ foo: T) {
        print("NSString.init<\(T.self)>(_:)")
        self.init(string: "Hello!")
    }
}

produces

Untitled 2.swift:4:21: error: designated initializer cannot be declared in an extension of 'NSString'; did you mean this to be a convenience initializer?
    public required init<T>(_ foo: T) {
                    ^
                    convenience 
Untitled 2.swift:4:21: error: 'required' initializer must be declared directly in class 'NSString' (not in an extension)
    public required init<T>(_ foo: T) {
           ~~~~~~~~ ^

The SO question just ommitted the relevant error there.

@swift-ci
Copy link
Collaborator

Comment by Aaron Von Gauss (JIRA)

I personally wouldn't describe this as a beginner issue, I think its more of a gap in the implementation. Logically, for any Type that is marked Decodable or Encodable either directly or indirectly through inheritance the compiler should have the potential to offer a default (codgen) implementation of CodingKeys, init(from🙂 and/or encode(to🙂 appropriately. The CodingKeys if present in the source or generated belong to the Type and should be private to that Type. Having the compiler evaluate and work with each individual Type I would think would produce the expected (desirable) behavior.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. Codable Area → standard library: `Codable` and co. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

4 participants