> On 4. Oct 2017, at 01:37, Karl Wagner <razie...@gmail.com> wrote:
>
>
>
>> On 30. Sep 2017, at 20:23, Xiaodi Wu via swift-dev <swift-dev@swift.org
>> <mailto:swift-dev@swift.org>> wrote:
>>
>> On Sat, Sep 30, 2017 at 11:58 AM, <swift-dev-requ...@swift.org
>> <mailto:swift-dev-requ...@swift.org>> wrote:
>> Message: 2
>> Date: Fri, 29 Sep 2017 18:21:44 -0700
>> From: Jordan Rose <jordan_r...@apple.com <mailto:jordan_r...@apple.com>>
>> To: swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>>
>> Subject: [swift-dev] What can you change in a non-exhaustive enum?
>> Message-ID: <31df689e-1ad3-47cb-9fce-6cbc7e34b...@apple.com
>> <mailto:31df689e-1ad3-47cb-9fce-6cbc7e34b...@apple.com>>
>> Content-Type: text/plain; charset="utf-8"
>>
>> Hello again, swift-dev! This is a sort of follow-up to "What can you change
>> in a fixed-contents struct" from a few weeks ago, but this time concerning
>> enums. Worryingly, this seems to be an important consideration even for
>> non-exhaustive enums, which are normally the ones where we'd want to allow a
>> developer to do anything and everything that doesn't break source
>> compatibility.
>>
>> [This only affects libraries with binary compatibility concerns. Libraries
>> distributed with an app can always allow the app to access the enum's
>> representation directly. That makes this an Apple-centric problem in the
>> near term.]
>>
>> So, what's the issue? We want to make it efficient to switch over a
>> non-exhaustive enum, even from a client library that doesn't have access to
>> the enum's guts. We do this by asking for the enum's tag separately from its
>> payload (pseudocode):
>>
>> switch getMyOpaqueEnumTag(&myOpaqueEnum) {
>> case 0:
>> var payload: Int
>> getMyOpaqueEnumPayload(&myOpaqueEnum, 0, &payload)
>> doSomething(payload)
>> case 1:
>> var payload: String
>> getMyOpaqueEnumPayload(&myOpaqueEnum, 1, &payload)
>> doSomethingElse(payload)
>> default:
>> print("unknown case")
>> }
>>
>> The tricky part is those constant values "0" and "1". We'd really like them
>> to be constants so that the calling code can actually emit a jump table
>> rather than a series of chained conditionals, but that means case tags are
>> part of the ABI, even for non-exhaustive enums.
>>
>> Like with struct layout, this means we need a stable ordering for cases.
>> Since non-exhaustive enums can have new cases added at any time, we can't do
>> a simple alphabetical sort, nor can we do some kind of ordering on the
>> payload types. The naive answer, then, is that enum cases cannot be
>> reordered, even in non-exhaustive enums. This isn't great, because people
>> like being able to move deprecated enum cases to the end of the list, where
>> they're out of the way, but it's at least explainable, and consistent with
>> the idea of enums some day having a 'cases' property that includes all cases.
>>
>> Slava and I aren't happy with this, but we haven't thought of another
>> solution yet. The rest of this email will describe our previous idea, which
>> has a fatal flaw.
>>
>>
>> Availability Ordering
>>
>> In a library with binary compatibility concerns, any new API that gets added
>> should always be explicitly annotated with an availability attribute. Today
>> that looks like this:
>>
>> @available(macOS 10.13, iOS 11, tvOS 11, watchOS 4, *)
>>
>> It's a model we only support for Apple platforms, but in theory it's
>> extendable to arbitrary "deployments". You ought to be able to say
>> `@available(MagicKit 5)` and have the compiler actually check that.
>>
>> Let's say we had this model, and we were using it like this:
>>
>> public enum SpellKind {
>> case hex
>> case charm
>> case curse
>> @available(MagicKit 5)
>> case blight
>> @available(MagicKit 5.1)
>> case jinx
>> }
>>
>> "Availability ordering" says that we can derive a canonical ordering from
>> the names of cases (which are API) combined with their versions. Since we
>> "know" that newly-added cases will always have a newer version than existing
>> cases, we can just put the older cases first. In this case, that would give
>> us a canonical ordering of [charm, curse, hex, blight, jinx].
>>
>>
>> The Fatal Flaw
>>
>> It's time for MagicKit 6 to come out, and we're going to add a new SpellKind:
>>
>> @available(MagicKit 6)
>> case summoning
>> // [charm, curse, hex, blight, jinx, summoning]
>>
>> We ship out a beta to our biggest clients, but realize we forgot a vital
>> feature. Beta 2 comes with another new SpellKind:
>>
>> @available(MagicKit 6)
>> case banishing
>> // [charm, curse, hex, blight, jinx, banishing, summoning]
>>
>> And now we're in trouble: anything built against the first beta expects
>> 'summoning' to have tag 5, not 6. Our clients have to recompile everything
>> before they can even try out the new version of the library.
>>
>> Can this be fixed? Sure. We could add support for beta versions to
>> `@available`, or fake it somehow with the existing version syntax. But in
>> both of these cases, it means you have to know what constitutes a "release",
>> so that you can be sure to use a higher number than the previous "release".
>> This could be made to work for a single library, but falls down for an
>> entire Apple OS. If the Foundation team wants to add a second new enum case
>> while macOS is still in beta, they're not going to stop and recompile all of
>> /System/Library/Frameworks just to try out their change.
>>
>> So, availability ordering is great when you have easily divisible
>> "releases", but falls down when you want to make a change "during a release".
>>
>>
>> Salvaging Availability Ordering?
>>
>> - We could still sort by availability, so that you can reorder the sections
>> but not the individual cases in them. That doesn't seem very useful, though.
>>
>> - We could say "this is probably rare", and state that anything added "in
>> the same release" needs to get an explicit annotation for ordering purposes.
>> (This is equivalent to the `@abi(2)` Dave Zarzycki mentioned in the previous
>> thread—it's not the default but it's there if you need it.)
>>
>> - We could actually require libraries to annotate all of their "releases",
>> but in order to apply that within Apple we'd need some translation from
>> library versions (like "Foundation 1258") to OS versions ("macOS 10.11.4"),
>> and then we'd still need to figure out what to do about betas. (And there's
>> a twist, at least at Apple, where a release's version number isn't decided
>> until the new source code is submitted.)
>>
>> - There might be something clever that I haven't thought of yet.
>>
>>
>> This kind of known ordering isn't just good for enum cases; it could also be
>> applied to protocol witnesses, so that those could be directly dispatched
>> like C++ vtables. (I don't think we want to restrict reordering of protocol
>> requirements, as much as it would make our lives easier.) So if anyone has
>> any brilliant ideas, Slava and I would love to hear them!
>>
>> Jordan
>>
>> Kind of a hybrid idea, but hopefully one that circumvents the internal
>> issues that you outline here and simplifies the mental model. I'll introduce
>> it stepwise:
>>
>> Currently, the @available annotation is supported for platforms and for
>> Swift versions; you propose extending support to arbitrary deployments
>> (e.g., MagicKit). Instead, suppose you extended support to arbitrary
>> versioned types (open, public, and @_versioned internal):
>>
>> @_versioned(abi: 2)
>> public enum SpellKind {
>> case hex, charm, curse
>> @available(abi: SpellKind 1) case blight
>> @available(abi: SpellKind 1.1) case jinx
>> }
>>
>> Now, clearly, the value of "abi" is arbitrary inside the @_versioned
>> annotation; it's not really necessary for our limited purposes, and if
>> there's a typo and it's lower than the highest ABI version referenced in an
>> @available annotation, things get wonky. So, drop it:
>>
>> public enum SpellKind {
>> case hex, charm, curse
>> @available(abi: 1) case blight
>> @available(abi: 1.1) case jinx
>> }
>>
>> This is looking like the original @abi(2) proposal that Dave Zarzycki
>> brought up. However, a key difference here: multiple cases can have the same
>> ABI "version" and would be ordered relative to each other by name; that is,
>> a user would only be annotating when new cases are added and doesn't have to
>> think about memory layout; Swift takes care of the rest.
>>
>> This can be simplified further; these ABI version numbers are entirely
>> arbitrary. Suppose we instead extended the "@available(introduced:)" syntax
>> to allow dates or timestamps:
>>
>> public enum SpellKind {
>> case hex, charm, curse
>> @available(*, introduced: 2017-09-30): case blight
>> @available(*, introduced: 2017-10-12): case jinx
>> }
>>
>> I suspect there are wrinkles to this scheme, but the overall idea here is to
>> salvage availability ordering but have some way to version a type in a
>> low-mental-overhead way instead of resorting to a syntax for manually
>> ordering cases.
>>
>
> Pretty much. If you want a stable order, you need to manually annotate each
> change to the enum’s layout with some incrementing identifier which can be
> sorted. Ideally, we would use the module’s version number, but as Jordan
> points out, during development layout can change even between versions.
>
> So the simplest answer to me is to give each enum it’s own mini version
> number. You could write them out, as in your examples. Another approach might
> be to introduce a version-break statement in the case-list, with the
> condition that those version-breaks cannot be reordered, but everything
> inside of them can be:
>
> enum SpellKind {
> case hex, charm, curse
> @new-version case blight
> @new-version
> case jinx
> case summoning
> case ...
> }
>
> A library developer could add as many of those as they need for internal
> testing, and selectively remove them as appropriate for each Beta/GM release
> (or they might keep them, if they decide to promise binary compatibility
> between beta and GM).
>
> - Karl
>
>>
>> _______________________________________________
>> swift-dev mailing list
>> swift-dev@swift.org <mailto:swift-dev@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-dev
>> <https://lists.swift.org/mailman/listinfo/swift-dev>
Ah I suppose this wouldn’t really work with @available since you can’t group
cases in to a single availability context to provide a stable ordering within.
So you will have to write the number out.
Still, I don’t think there is any way to do it other than an arbitrary,
enum-scoped build/release number. It doesn’t mean anything other than to
provide a level of ABI versioning below the module’s version number, like
@abi(2). I don’t agree with supporting arbitrary tags or dates: they need to
obviously increment, with minimal opportunities to get it wrong.
enum SpellKind {
case hex
case charm
case curse
@available(MagicKit 5) case blight
@available(MagicKit 5.1) case jinx
@available(MagicKit 6) case summoning
@available(MagicKit 6, build: 2) case banishing
}
- Karl
_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev