On Mon, Nov 6, 2017 at 6:54 PM, Jacob Bandes-Storch via swift-evolution < swift-evolut...@swift.org> wrote:
> 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."* > > This proposal would be very useful in reducing boiler plate code in many cases! With "Enums imported from C/Obj-C headers", it would be nice to automatically bridge it by using an appropriately namespaced (and dynamically loaded) ObjC equivalent values(s), if one is available. I think this addresses Doug's concerns. For example: // // Foundation NSObjCRuntime.h // #define NS_VALUE_ENUMERABLE(__TYPE__) \ FOUNDATION_EXPORT NSInteger const __TYPE__ ##ValueCount; \ FOUNDATION_EXPORT __TYPE__ __TYPE__##ValueAtIndex(NSInteger index); // // UITableViewCellStyle.h // ... NS_VALUE_ENUMERABLE(UITableViewCellStyle); // // UITableViewCellStyle.m // ... NSInteger const UITableViewCellStyleValueCount = 4; UITableViewCellStyle UITableViewCellStyleValueAtIndex(NSInteger index) { // Can be a switch statement in more complicated cases return (UITableViewCellStyle) index; } // // UITableViewCellStyle-Generated.swift // extension UITableViewCellStyle: ValueEnumerable { typealias AllValuesCollection = _NSValueEnumerableCollection< UITableViewCellStyle> static var allValues: AllValuesCollection { return AllValuesCollection( count: UITableViewCellStyleValueCount, valueAtIndex: UITableViewCellStyleValueAtIndex) } } // // Swift Standard Library // public struct _NSValueEnumerableCollection<T>: Collection { private let valueAtIndex: (Int) -> T public let count: Int public var startIndex: Int { return 0 } public var endIndex: Int { return count } public init(count: Int, valueAtIndex: @escaping (Int) -> T) { self.count = count self.valueAtIndex = valueAtIndex } public subscript(index: Int) -> T { // Potentially valueAtIndex could be @convention(c) returning // an integer type. T could be RawRepresentable, and conversion // errors could be handled here. return self.valueAtIndex(index) } public func index(after i: Int) -> Int { return i + 1 } } Thanks, Andrew Bennett 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 > > _______________________________________________ > swift-evolution mailing list > swift-evolut...@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution > >
_______________________________________________ swift-dev mailing list swift-dev@swift.org https://lists.swift.org/mailman/listinfo/swift-dev