<div dir="ltr">On Sat, Sep 30, 2017 at 11:58 AM,  <span dir="ltr">&lt;<a href="mailto:swift-dev-request@swift.org" target="_blank">swift-dev-request@swift.org</a>&gt;</span> wrote:<br><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Message: 2<br>
Date: Fri, 29 Sep 2017 18:21:44 -0700<br>
From: Jordan Rose &lt;<a href="mailto:jordan_rose@apple.com">jordan_rose@apple.com</a>&gt;<br>
To: swift-dev &lt;<a href="mailto:swift-dev@swift.org">swift-dev@swift.org</a>&gt;<br>
Subject: [swift-dev] What can you change in a non-exhaustive enum?<br>
Message-ID: &lt;<a href="mailto:31DF689E-1AD3-47CB-9FCE-6CBC7E34BC43@apple.com">31DF689E-1AD3-47CB-9FCE-<wbr>6CBC7E34BC43@apple.com</a>&gt;<br>
Content-Type: text/plain; charset=&quot;utf-8&quot;<br>
<br>
Hello again, swift-dev! This is a sort of follow-up to &quot;What can you change in a fixed-contents struct&quot; 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&#39;d want to allow a developer to do anything and everything that doesn&#39;t break source compatibility.<br>
<br>
[This only affects libraries with binary compatibility concerns. Libraries distributed with an app can always allow the app to access the enum&#39;s representation directly. That makes this an Apple-centric problem in the near term.]<br>
<br>
So, what&#39;s the issue? We want to make it efficient to switch over a non-exhaustive enum, even from a client library that doesn&#39;t have access to the enum&#39;s guts. We do this by asking for the enum&#39;s tag separately from its payload (pseudocode):<br>
<br>
switch getMyOpaqueEnumTag(&amp;<wbr>myOpaqueEnum) {<br>
case 0:<br>
  var payload: Int<br>
  getMyOpaqueEnumPayload(&amp;<wbr>myOpaqueEnum, 0, &amp;payload)<br>
  doSomething(payload)<br>
case 1:<br>
  var payload: String<br>
  getMyOpaqueEnumPayload(&amp;<wbr>myOpaqueEnum, 1, &amp;payload)<br>
  doSomethingElse(payload)<br>
default:<br>
  print(&quot;unknown case&quot;)<br>
}<br>
<br>
The tricky part is those constant values &quot;0&quot; and &quot;1&quot;. We&#39;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.<br>
<br>
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&#39;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&#39;t great, because people like being able to move deprecated enum cases to the end of the list, where they&#39;re out of the way, but it&#39;s at least explainable, and consistent with the idea of enums some day having a &#39;cases&#39; property that includes all cases.<br>
<br>
Slava and I aren&#39;t happy with this, but we haven&#39;t thought of another solution yet. The rest of this email will describe our previous idea, which has a fatal flaw.<br>
<br>
<br>
Availability Ordering<br>
<br>
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:<br>
<br>
@available(macOS 10.13, iOS 11, tvOS 11, watchOS 4, *)<br>
<br>
It&#39;s a model we only support for Apple platforms, but in theory it&#39;s extendable to arbitrary &quot;deployments&quot;. You ought to be able to say `@available(MagicKit 5)` and have the compiler actually check that.<br>
<br>
Let&#39;s say we had this model, and we were using it like this:<br>
<br>
public enum SpellKind {<br>
  case hex<br>
  case charm<br>
  case curse<br>
  @available(MagicKit 5)<br>
  case blight<br>
  @available(MagicKit 5.1)<br>
  case jinx<br>
}<br>
<br>
&quot;Availability ordering&quot; says that we can derive a canonical ordering from the names of cases (which are API) combined with their versions. Since we &quot;know&quot; 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].<br>
<br>
<br>
The Fatal Flaw<br>
<br>
It&#39;s time for MagicKit 6 to come out, and we&#39;re going to add a new SpellKind:<br>
<br>
@available(MagicKit 6)<br>
case summoning<br>
// [charm, curse, hex, blight, jinx, summoning]<br>
<br>
We ship out a beta to our biggest clients, but realize we forgot a vital feature. Beta 2 comes with another new SpellKind:<br>
<br>
@available(MagicKit 6)<br>
case banishing<br>
// [charm, curse, hex, blight, jinx, banishing, summoning]<br>
<br>
And now we&#39;re in trouble: anything built against the first beta expects &#39;summoning&#39; to have tag 5, not 6. Our clients have to recompile everything before they can even try out the new version of the library.<br>
<br>
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 &quot;release&quot;, so that you can be sure to use a higher number than the previous &quot;release&quot;. 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&#39;re not going to stop and recompile all of /System/Library/Frameworks just to try out their change.<br>
<br>
So, availability ordering is great when you have easily divisible &quot;releases&quot;, but falls down when you want to make a change &quot;during a release&quot;.<br>
<br>
<br>
Salvaging Availability Ordering?<br>
<br>
- We could still sort by availability, so that you can reorder the sections but not the individual cases in them. That doesn&#39;t seem very useful, though.<br>
<br>
- We could say &quot;this is probably rare&quot;, and state that anything added &quot;in the same release&quot; needs to get an explicit annotation for ordering purposes. (This is equivalent to the `@abi(2)` Dave Zarzycki mentioned in the previous thread—it&#39;s not the default but it&#39;s there if you need it.)<br>
<br>
- We could actually require libraries to annotate all of their &quot;releases&quot;, but in order to apply that within Apple we&#39;d need some translation from library versions (like &quot;Foundation 1258&quot;) to OS versions (&quot;macOS 10.11.4&quot;), and then we&#39;d still need to figure out what to do about betas. (And there&#39;s a twist, at least at Apple, where a release&#39;s version number isn&#39;t decided until the new source code is submitted.)<br>
<br>
- There might be something clever that I haven&#39;t thought of yet.<br>
<br>
<br>
This kind of known ordering isn&#39;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&#39;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!<br>
<br>
Jordan</blockquote><div><br></div><div>Kind of a hybrid idea, but hopefully one that circumvents the internal issues that you outline here and simplifies the mental model. I&#39;ll introduce it stepwise:</div><div><br></div><div>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):</div><div><br></div><div>@_versioned(abi: 2)</div><div>public enum SpellKind {</div><div>  case hex, charm, curse</div><div>  @available(abi: SpellKind 1) case blight</div><div>  @available(abi: SpellKind 1.1) case jinx</div><div>}</div><div><br></div><div>Now, clearly, the value of &quot;abi&quot; is arbitrary inside the @_versioned annotation; it&#39;s not really necessary for our limited purposes, and if there&#39;s a typo and it&#39;s lower than the highest ABI version referenced in an @available annotation, things get wonky. So, drop it:</div><div><br></div><div>public enum SpellKind {</div><div>  case hex, charm, curse</div><div>  @available(abi: 1) case blight</div><div>  @available(abi: 1.1) case jinx</div><div>}</div><div><br></div><div>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 &quot;version&quot; 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&#39;t have to think about memory layout; Swift takes care of the rest.</div><div><br></div><div>This can be simplified further; these ABI version numbers are entirely arbitrary. Suppose we instead extended the &quot;@available(introduced:)&quot; syntax to allow dates or timestamps:</div><div><br></div><div>public enum SpellKind {</div><div>  case hex, charm, curse</div><div>  @available(*, introduced: 2017-09-30): case blight</div><div>  @available(*, introduced: 2017-10-12): case jinx</div><div>}</div><div><br></div><div>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.</div><div><br></div><div><br></div></div></div></div>