[swift-dev] Re-pitch: Deriving collections of enum cases

Jacob Bandes-Storch jtbandes at gmail.com
Mon Nov 6 01:54:10 CST 2017


Over a year ago, we discussed adding a magic "allValues"/"allCases" static
property on enums with a compiler-derived implementation. The original proposal
PR <https://github.com/apple/swift-evolution/pull/114> has been reopened
for Swift 5 after languishing for a while, and I'd like to revisit it and
make some changes before it goes up for formal review.

Prior discussion:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160411/015098.html
(good luck finding the rest of the thread if you weren't on the list at the
time...)

[cc'd swift-dev for importer/availability-related topics below.]

***Naming***

Given the complexity gap between a simple enumeration of cases and full
support for non-enum types and associated values (which we don't intend to
support with this proposal), I think it might be a good idea to adopt the
names *CaseEnumerable/allCases* instead of ValueEnumerable/allValues.

The original proposal didn't expose allValues as a requirement, for fear of
unduly restricting its type. However, if the protocol's scope is more
limited, *static var allCases* can be exposed as a requirement since the
implementations are not likely to be complex. Furthermore...


***Generics***

Since SE-0142
<https://github.com/apple/swift-evolution/blob/master/proposals/0142-associated-types-constraints.md>
was implemented in Swift 4, we now have more expressive options for the
protocol requirements:

  // 1 - array only
  protocol CaseEnumerable {
    static var allCases: [Self] { get }
  }

  // 2 - any sequence
  protocol CaseEnumerable {
    associatedtype *CaseSequence*: Sequence where CaseSequence.Element ==
Self
    static var *allCases*: CaseSequence { get }
  }

  // 3 - any collection
  protocol CaseEnumerable {
    associatedtype *CaseCollection*: Collection where
CaseCollection.Element == Self
    static var *allCases*: CaseCollection { get }
  }

This restricts the CaseEnumerable protocol to be used as a generic
constraint, but that'd be true even with a plain array because of the Self
type.

Personally I like the flexibility provided by the associatedtype, but I
also recognize it won't be incredibly useful for enums — more so if we
wanted to provide e.g. UInt8.allValues, whose ideal implementation might be
"return 0...UInt8.max". So I could see allowing allValues to be any
sequence or collection, but flexibility for allCases might be less
important. Others should weigh in here.


***Implementation strategy and edge cases***

Last year <https://twitter.com/CodaFi_/status/920132464001024001>, Robert
Widmann put together an implementation of CaseEnumerable:
https://github.com/apple/swift/compare/master...CodaFi:ace-attorney
I'd love to hear from anyone more familiar with the code whether there's
anything we'd want to change about this approach.

A few tricky situations have been brought to my attention:

- Enums *imported from C/Obj-C* headers. Doug Gregor writes: *"The
autogenerated allValues would only be able to list the enum cases it knows
about from the header it was compiled with. If the library changes to
add cases in the future (which, for example, Apple frameworks tend to do),
those wouldn’t be captured in allValues."*

My understanding of the runtime/importer is very shallow, but with the
current metadata-based strategy, I suspect imported enums couldn't be
supported at all, or if they could, the metadata would be generated at
import time rather than loaded dynamically from the library, which
naturally wouldn't behave the same way when you drop in an upgraded version
of the library. Is that correct?

(Nonetheless, if a user really wanted this auto-generation, it would be
nice to allow it somehow. Personally, I have had enums whose "source of
truth" was an Obj-C header file, but since it was compiled in with the rest
of the application, we didn't care at all about library upgrades. Maybe an
internal extension adding a conformance can be allowed to participate in
auto-generation?)

- Enums with *availability* annotations on some cases. Doug Gregor writes: *"if
I have a case that’s only available on macOS 10.12 and newer, it probably
shouldn’t show up if I use allValues when running on macOS 10.11."*

If we fetch cases from the enum metadata, does this "just work" since the
metadata will be coming from whichever version of the library is loaded at
runtime? If not, is it at least *possible* to extract availability info
from the metadata? Finally, if not, should we try to synthesize an
implementation that uses #available checks, or just refuse to synthesize
allCases?

- Should it be possible to add a CaseEnumerable conformance in an
*extension*? My thinking is: we want to make sure the metadata is coming
from the module that defines the enum, so we could restrict autogeneration
of allCases to that same module. (That is, it wouldn't be possible to
synthesize allCases for a CaseEnumerable extension on an enum from another
module.) Although, it may be that I am missing something and this
restriction isn't actually necessary. The question to answer is: in exactly
which circumstances can the implementation be synthesized?


Looking forward to hearing everyone's thoughts,
Jacob
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-dev/attachments/20171105/9b66f2ce/attachment.html>


More information about the swift-dev mailing list