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-7897] Synthesized CodingKeys enum should conform to CaseIterable #50432

Open
moiseev mannequin opened this issue Jun 6, 2018 · 14 comments
Open

[SR-7897] Synthesized CodingKeys enum should conform to CaseIterable #50432

moiseev mannequin opened this issue Jun 6, 2018 · 14 comments
Labels
Codable Area → standard library: `Codable` and co. compiler The Swift compiler in itself improvement

Comments

@moiseev
Copy link
Mannequin

moiseev mannequin commented Jun 6, 2018

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

md5: 9bbce0d23efee7c88533a728306bd187

Issue Description:

These two features were added separately, but would actually be so much more useful when combined.

public struct S: Codable {
    var name: String = ""
    var age: Int8 = 42
}

There is no simple way of getting a list of all the fields in this struct, even though CodingKeys enum gets synthesized.

@moiseev
Copy link
Mannequin Author

moiseev mannequin commented Jun 6, 2018

/cc @CodaFi, @itaiferber

@itaiferber
Copy link
Contributor

If we synthesize enum CodingKeys we shouldn't have any problems making it CaseIterable; I wouldn't, however, necessarily want to automatically make all enum CodingKeys (including ones not synthesized) CaseIterable automatically as well without a developer requesting it.

Even so, though, I don't think that making synthesized CodingKeys CaseIterable would help you in this case as synthesized CodingKeys are purposefully private and are not accessible outside the type.


If we do do this, we should likely pitch this and go through the Swift Evo review process.

@belkadan
Copy link
Contributor

belkadan commented Jun 6, 2018

I'm with Itai on this one. CaseIterable generates extra code that can't be optimized away (because it's part of the conformance).

@swift-ci
Copy link
Collaborator

swift-ci commented Jun 7, 2018

Comment by Nick DiZazzo (JIRA)

It might make sense to be able to do something like:

struct MyType: Codable {
    var one: String = "a"
    var two: Int = 1
}

extension MyType.CodingKeys: CaseIterable { }

let a = MyType()
print(a.CodingKeys.allCases)

... but this won't work.

Even if you try to do something like: private typealias Foo = CodingKeys inside of MyStruct and then extend Foo.CodingKeys: CaseIterable, the compiler catches you red-handed.

I was chatting with @DougGregor about this, and it seems like this should work...

@moiseev
Copy link
Mannequin Author

moiseev mannequin commented Jun 7, 2018

My concrete poor wording aside: there should be a way to make these features work together. If automatic conformance for all synthesized {{Codable}}s is undesirable for various reasons, there should be an alternative way to request this manually. Right now there isn't.

@itaiferber
Copy link
Contributor

@moiseev That I agree with — this is one case of a more general limitation of not being able to extend a private you don't have access too; I'm trying to hunt for a recent JIRA where we were discussing exactly this type of extension in a different context. I'll see if I can pull it up.

It would be nice to be able to extend nested private types in the context where they're defined, or special-case synthesized types in this way if this isn't generalizable.

@DougGregor
Copy link
Member

@belkadan is there some reason why we can't extend a private type the way ndizazzo (JIRA User) and I tried? It seems to me like you should be able to extend a private type within the same file (under the Swift 4+ definition of private, of course).

@belkadan
Copy link
Contributor

The logic is that the extension itself is at top-level, which is outside the type. I don't think that's so important in this case, but we'd want to be careful about it. Consider code like this:

struct Outer {
  private struct Inner {}
  private typealias InnerProto = SomeProto
}
extension Outer.Inner: Outer.InnerProto {} // legal?

or this

struct Outer {
  private struct Inner {}
  struct Inner2 {
    private typealias InnerProto = SomeProto
  }
}
extension Outer.Inner: Outer.Inner2.InnerProto {} // legal?

@DougGregor
Copy link
Member

Right, we need to decide whether the types following the colon are in the file's access scope (so one cannot refer to private members) or in the extension's access scope. The latter is more permissive, and I don't think it causes any problems, but I also don't have a compelling use case for it.

@itaiferber
Copy link
Contributor

I found the discussion I'd tried to drag up earlier, BTW — it wasn't in JIRA, but on the Swift forums: Extensions to private nested types

I don't know what would be a bigger departure here from the current state: permitting external extensions like Jordan brings up, or changing the grammar to allow for extensions of nested types within the scope that they're nested in, e.g.

struct Foo : Codable {
    private extension CodingKeys { /* ... */ }
}

We currently only allow extensions at file scope, but we could skirt around the permissions issue by allowing extensions in nested scopes only for nested types (in a way that matches their existing visibility).

@belkadan
Copy link
Contributor

That would be a pretty drastic change because extension binding happens before most of the rest of type-checking. That relies on being able to find all the extensions.

@itaiferber
Copy link
Contributor

Hmm. Would constraining this to require extensions on types to be at most as nested as the type itself be helpful in that regard? For instance, I don't think we should allow any of the following extensions:

struct Foo {
    struct Bar {
        extension Foo { /* ... */ }
    }

    extension Foo { /* ... */ }
}

struct Baz {
    extension Foo { /* ... */ }
}

Conversely, you are allowed to extend a nested type outside of its nesting if visibility applies, as today.

Or are you saying that we are going the other way around — we first look for all extensions, bind them, then go through type checking? (if so, then yes, this seems like a big departure no matter how we handle it. e.g. look through all file-scope extensions first, then start type checking, then apply nested extensions; or, search through all scopes everywhere for extensions, then do type-checking as usual; or, change the model to look at types and find all extensions in context; etc.)

@swift-ci
Copy link
Collaborator

Comment by Alexander Zaytsev (JIRA)

Might not be the right forum for this, so I apologize if so, but this bug is one of the top hits for `CodingKeys CaseIterable` on Google.

What i found is that at least at runtime to iterate over the cases in the `private` CodingKeys we can use Mirror API, like so

let mirror = Mirror(reflecting: self)

 for child in mirror.children {
  if let lbl = child.label {
     print(Me.CodingKeys(stringValue: lbl))
  }
 }

@swift-ci
Copy link
Collaborator

Comment by Adam Rocska (JIRA)

zaitsman (JIRA User) 🙂 CaseITerable provides a static variable, what you describe is a runtime instance level thing. You can't reflect the enum cases on a Type level either, so that's also shot in the foot.

Though of-course it's a nice construct and could have solved your issue, it still doesn't address the main issue.

@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
Codable Area → standard library: `Codable` and co. compiler The Swift compiler in itself improvement
Projects
None yet
Development

No branches or pull requests

4 participants