<html><head><style>
body {
        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
        padding:1em;
        margin:auto;
        background:#fefefe;
}
h1, h2, h3, h4, h5, h6 {
        font-weight: bold;
}
h1 {
        color: #000000;
        font-size: 28pt;
}
h2 {
        border-bottom: 1px solid #CCCCCC;
        color: #000000;
        font-size: 24px;
}
h3 {
        font-size: 18px;
}
h4 {
        font-size: 16px;
}
h5 {
        font-size: 14px;
}
h6 {
        color: #777777;
        background-color: inherit;
        font-size: 14px;
}
hr {
        height: 0.2em;
        border: 0;
        color: #CCCCCC;
        background-color: #CCCCCC;
display: inherit;
}
p, blockquote, ul, ol, dl, li, table, pre {
        margin: 15px 0;
}
a, a:visited {
        color: #4183C4;
        background-color: inherit;
        text-decoration: none;
}
#message {
        border-radius: 6px;
        border: 1px solid #ccc;
        display:block;
        width:100%;
        height:60px;
        margin:6px 0px;
}
button, #ws {
        font-size: 12 pt;
        padding: 4px 6px;
        border-radius: 5px;
        border: 1px solid #bbb;
        background-color: #eee;
}
code, pre, #ws, #message {
        font-family: Monaco;
        font-size: 10pt;
        border-radius: 3px;
        background-color: #F8F8F8;
        color: inherit;
}
code {
        border: 1px solid #EAEAEA;
        margin: 0 2px;
        padding: 0 5px;
}
pre {
        border: 1px solid #CCCCCC;
        overflow: auto;
        padding: 4px 8px;
}
pre > code {
        border: 0;
        margin: 0;
        padding: 0;
}
#ws { background-color: #f8f8f8; }
.bloop_markdown table {
border-collapse: collapse;
font-family: Helvetica, arial, freesans, clean, sans-serif;
color: rgb(51, 51, 51);
font-size: 15px; line-height: 25px;
padding: 0; }
.bloop_markdown table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
.bloop_markdown table tr:nth-child(2n) {
background-color: #f8f8f8; }
.bloop_markdown table tr th {
font-weight: bold;
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
.bloop_markdown table tr td {
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
.bloop_markdown table tr th :first-child, table tr td :first-child {
margin-top: 0; }
.bloop_markdown table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
.bloop_markdown blockquote{
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }
code, pre, #ws, #message {
word-break: normal;
word-wrap: normal;
}
hr {
display: inherit;
}
.bloop_markdown :first-child {
-webkit-margin-before: 0;
}
code, pre, #ws, #message {
font-family: Menlo, Consolas, Liberation Mono, Courier, monospace;
}
.send { color:#77bb77; }
.server { color:#7799bb; }
.error { color:#AA0000; }</style></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;"><div class="bloop_markdown"><p>I’m probably better describing things with some bikeshedding code, but feel free to criticize it as much as you’d like.</p>
<pre><code class="swift">//===========--------- Module A ---------===========//
@closed public enum A {
case a
}
extension A {
case aa // error, because enum is closed
}
public func foo(a: A) {
switch a {
case .a:
print("done")
}
}
public enum B {
case b
}
extension B {
case bb // fine, because not-closed enums are extensible
}
public func bar(b: B) {
switch b {
case .b:
print("b")
default: // always needed
print("some other case")
}
}
// Sub-enum relationships
// Possible even the enum A is closed, because `@closed` only
// closes the extensibility of an enum
enum SubA : A {
case aa
}
// The following enum can have a sub-enum in the clients module
open enum C {
case c
}
public func cool(c: C) {
switch c {
case .c:
print("c")
default: // always needed
print("some other case")
}
}
@closed open enum D {
case d
}
public func doo(d: D) {
switch b {
case .b:
print("b")
}
}
// The enum case is always known at any point, no matter
// where the instance comes from, right?
let subA = SubA.aa
let otherSubA = SubA.a // Inherited case
let a: A = subA // error, downgrade the sub-enum to A first
let a: A = otherSubA // okay
foo(a: subA) // error, downgrade the sub-enum to A first
foo(a: otherSubA) // okay
//===========--------- Module B ---------===========//
// Totally fine
switch A.a {
case .a:
print("done")
}
extension A {
case aa // not allowed because the enum is closed
}
extension B {
case bbb
}
switch B.b {
case .b:
print("b")
default:
print("somethine else")
}
bar(b: B.bbb) // fine, because the switch statement on enums without
// `@closed` has always`default`
// Allowed because `C` is open, and open allows sub-typing, conforming
// and overriding to the client
enum SubC : C {
case cc
}
let subC = SubC.cc
cool(c: subC) // okay
enum SubD : D {
case dd
}
doo(d: D.dd)// error, downgrade sub-enum to D first
</code></pre>
<p>My point here is, that we should not think of (possible) <code>open enums</code> as enums that the client is allowed to extend. That way we’re only creating another inconsistent case for the <code>open</code> access modifier. As far as I can tell, <code>open</code> as for today means “the client is allowed to subclass/override things from a different module”. And I already said it hundred of times that we should extend this to make <code>open</code> a true <em>access modifier</em> in Swift. That said the meaning of <code>open</code> should become:</p>
<ul>
<li>The client is allowed to sub-type (currently only classes are supported).</li>
<li>The client is allowed to conform to open protocols</li>
<li>The client is allowed to override open type members</li>
</ul>
<p>This also means that extensibility is still allowed to <code>public</code> types. Public-but-not-open classes are still extensible today, which is the correct behavior. Extending an enum which is not closed <em>could</em> or probably should be made possible through extensions, because <em>I</em> cannot think of anther elegant way for the client to do so. That will leave us the possibility to think of sub-typing enums in the future (I sketched it out a little above). If I’m not mistaken, every enum case is known at compile time, which means to me that we can safely check the case before allowing to assign or pass an instance of a sub-enum to some of its super-enum. (Downgrading an enum case means that you will have to write some code that either mutates your current instance or creates a new one which matches one of the super-enum cases.) Furthermore that allows a clear distinction of what <code>open</code> access modifier does and how <code>@closed</code> behaves.</p>
<p>To summarize:</p>
<ul>
<li><code>@closed enum</code> - you’re not allowed to add new cases to the enum in your lib + (you’re allowed to create sub-enums)</li>
<li><code>@closed public enum</code> - you and the client are not allowed to add new cases (+ the client is not allowed to create sub-enums)</li>
<li><code>@closed open enum</code> - you and the client are not allowed to add new cases (+ the client might create new sub-enums)</li>
<li><code>enum</code> - you’re allowed to add new cases (<code>default</code> is needed in switch statements) (+ you can create new sub-enums)</li>
<li><code>public enum</code> - you and the client are allowed to add new cases (+ only you are allowed to create new sub-enums)</li>
<li><code>open enum</code> - you and the client are allowed to add new cases (everyone can create new sub-enums)</li>
</ul>
<p>This is a lot of bike shedding of mine, and the idea might not even see any light in Swift at all, but I’d like to share my ideas with the community. Feel free to criticize them or flesh something out into something real. :)</p>
<p>P.S.: If we had something like this:</p>
<pre><code class="swift">@closed enum X {
case x, y
func foo() {
switch self {
case .x, .y:
print("swift")
}
}
enum Z : X {
case z, zz
override func foo() {
// Iff `self` is `z` or `zz` then calling super will result in an error.
// Possible solution: always tell the client to downgrade explicitly the
// case first if there is an attempt to call super (if mutating),
// or handle all cases
switch self {
case .z, .zz:
print("custom work")
default: // or all super-enum cases
super.foo()
}
}
}
</code></pre>
<p></p></div><div class="bloop_original_html"><style>body{font-family:Helvetica,Arial;font-size:13px}</style><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div> <br> <div id="bloop_sign_1486806388447838976" class="bloop_sign"><div style="font-family:helvetica,arial;font-size:13px">-- <br>Adrian Zubarev<br>Sent with Airmail</div></div> <br><p class="airmail_on">Am 11. Februar 2017 um 04:49:11, Xiaodi Wu via swift-evolution (<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>) schrieb:</p> <blockquote type="cite" class="clean_bq"><span><div><div></div><div>
<title></title>
<div dir="ltr">On Wed, Feb 8, 2017 at 5:05 PM, Matthew Johnson via
swift-evolution <span dir="ltr"><<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>></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">
<div style="word-wrap:break-word">
<div>I’ve been thinking a lot about our public access modifier
story lately in the context of both protocols and enums. I
believe we should move further in the direction we took when
introducing the `open` keyword. I have identified what I
think is a promising direction and am interested in feedback from
the community. If community feedback is positive I will flesh
this out into a more complete proposal draft.</div>
<div><br></div>
<div><br></div>
<div>Background and Motivation:</div>
<div><br></div>
In Swift 3 we had an extended debate regarding whether or not to
allow inheritance of public classes by default or to require an
annotation for classes that could be subclassed outside the
module. The decision we reached was to avoid having a default
at all, and instead make `open` an access modifier. The
result is library authors are required to consider the behavior
they wish for each class. Both behaviors are equally
convenient (neither is penalized by requiring an additional
boilerplate-y annotation).
<div><br></div>
<div>A recent thread (<a href="https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031566.html" target="_blank">https://lists.swift.org/piper<wbr>mail/swift-evolution/Week-of-<wbr>Mon-20170206/031566.html</a>)
discussed a similar tradeoff regarding whether public enums should
commit to a fixed set of cases by default or not. The current
behavior is that they *do* commit to a fixed set of cases and there
is no option (afaik) to modify that behavior. The Library
Evolution document (<a href="https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums" target="_blank">https://github.com/apple/swif<wbr>t/blob/master/docs/LibraryEvol<wbr>ution.rst#enums</a>)
suggests a desire to change this before locking down ABI such that
public enums *do not* make this commitment by default, and are
required to opt-in to this behavior using an `@closed`
annotation.</div>
<div><br></div>
<div>In the previous discussion I stated a strong preference that
closed enums *not* be penalized with an additional
annotation. This is because I feel pretty strongly that it is
a design smell to: 1) expose cases publicly if consumers of the API
are not expected to switch on them and 2) require users to handle
unknown future cases if they are likely to switch over the cases in
correct use of the API.</div>
<div><br></div>
<div>The conclusion I came to in that thread is that we should
adopt the same strategy as we did with classes: there should not be
a default.</div>
<div><br></div>
<div>There have also been several discussions both on the list and
via Twitter regarding whether or not we should allow closed
protocols. In a recent Twitter discussion Joe Groff suggested
that we don’t need them because we should use an enum when there is
a fixed set of conforming types. There are at least two
reasons why I still think we *should* add support for closed
protocols.</div>
<div><br></div>
<div>As noted above (and in the previous thread in more detail), if
the set of types (cases) isn’t intended to be fixed (i.e. the
library may add new types in the future) an enum is likely not a
good choice. Using a closed protocol discourages the user
from switching and prevents the user from adding conformances that
are not desired.</div>
<div><br></div>
<div>Another use case supported by closed protocols is a design
where users are not allowed to conform directly to a protocol, but
instead are required to conform to one of several protocols which
refine the closed protocol. Enums are not a substitute for
this use case. The only option is to resort to documentation
and runtime checks.</div>
<div><br></div>
<div><br></div>
<div>Proposal:</div>
<div><br></div>
<div>This proposal introduces the new access modifier `closed` as
well as clarifying the meaning of `public` and expanding the use of
`open`. This provides consistent capabilities and semantics
across enums, classes and protocols.</div>
<div><br></div>
<div>`open` is the most permissive modifier. The symbol is
visible outside the module and both users and future versions of
the library are allowed to add new cases, subclasses or
conformances. (Note: this proposal does not introduce
user-extensible `open` enums, but provides the syntax that would be
used if they are added to the language)</div>
<div><br></div>
<div>`public` makes the symbol visible without allowing the user to
add new cases, subclasses or conformances. The library
reserves the right to add new cases, subclasses or conformances in
a future version.</div>
<div><br></div>
<div>`closed` is the most restrictive modifier. The symbol is
visible publicly with the commitment that future versions of the
library are *also* prohibited from adding new cases, subclasses or
conformances. Additionally, all cases, subclasses or
conformances must be visible outside the module.</div>
<div><br></div>
<div>Note: the `closed` modifier only applies to *direct*
subclasses or conformances. A subclass of a `closed` class
need not be `closed`, in fact it may be `open` if the design of the
library requires that. A class that conforms to a `closed`
protocol also need not be `closed`. It may also be
`open`. Finally, a protocol that refines a `closed` protocol
need not be `closed`. It may also be `open`.</div>
<div><br></div>
<div>This proposal is consistent with the principle that libraries
should opt-in to all public API contracts without taking a position
on what that contract should be. It does this in a way that
offers semantically consistent choices for API contract across
classes, enums and protocols. The result is that the language
allows us to choose the best tool for the job without restricting
the designs we might consider because some kinds of types are
limited with respect to the `open`, `public` and `closed` semantics
a design might require.</div>
<div><br></div>
<div><br></div>
<div>Source compatibility:</div>
<div><br></div>
<div>This proposal affects both public enums and public
protocols. The current behavior of enums is equivalent to a
`closed` enum under this proposal and the current behavior of
protocols is equivalent to an `open` protocol under this
proposal. Both changes allow for a simple mechanical
migration, but that may not be sufficient given the source
compatibility promise made for Swift 4. We may need to
identify a multi-release strategy for adopting this proposal.</div>
<div><br></div>
<div>Brent Royal-Gordon suggested such a strategy in a discussion
regarding closed protocols on Twitter:</div>
<div><br></div>
<div>* In Swift 4: all unannotated public protocols receive a
warning, possibly with a fix-it to change the annotation to
`open`.</div>
<div>* Also in Swift 4: an annotation is introduced to opt-in to
the new `public` behavior. Brent suggested `@closed`, but as
this proposal distinguishes `public` and `closed` we would need to
identify something else. I will use `@annotation` as a
placeholder.</div>
<div>* Also In Swift 4: the `closed` modifier is introduced.</div>
<div><br></div>
<div>* In Swift 5 the warning becomes a compiler error.
`public protocol` is not allowed. Users must use
`@annotation public protocol`.</div>
<div>* In Swift 6 `public protocol` is allowed again, now with the
new semantics. `@annotation public protocol` is also allowed,
now with a warning and a fix-it to remove the warning.</div>
<div>* In Swift 7 `@annotation public protocol` is no longer
allowed.</div>
<div><br></div>
<div>A similar mult-release strategy would work for migrating
public enums.</div>
</div>
</blockquote>
<div><br></div>
<div>A different line of feedback here:</div>
<div><br></div>
<div>As per previous reply, I now think if we clarify the mental
model of the access modifier hierarchy you're proposing and adopt
or reject with that clarity, we'll be fine whether we go with
`closed` or with `@closed`. But I don't think the source
compatibility strategy you list is the most simple or the most easy
to understand for end users.</div>
<div><br></div>
<div>- I'll leave aside closed protocols, which as per Jordan
Rose's feedback may or may not have sufficient
interestingness.</div>
<div>- With respect to enums, I don't think we need such a drastic
whiplash in terms of what will compile in future versions. Instead,
we could take a more pragmatic approach:</div>
<div><br></div>
<div>1. In Swift 4, remove the warning (or is it error?) about
`default` cases in switch statements over public enums.
Simultaneously, add `closed` or `@closed` (whatever is the approved
spelling) and start annotating standard library APIs. The
annotation will be purely future-proofing and have no functional
effect (i.e. the compiler will do nothing differently for a `closed
enum` or `@closed public enum` (as the case may be) versus a plain
`public enum`).<br></div>
<div>2. In Swift 4.1, _warn_ if switch statements over public enums
don't have a `default` statement: offer a fix-it to insert
`default: fatalError()` and, if the enum is in the same project,
offer a fix-it to insert `closed` or `@closed`.</div>
<div>3. In Swift 5, upgrade the warning to an error for
non-exhaustiveness if a switch statement over a public enum doesn't
have a `default` statement. Now, new syntax to extend an `open
enum` can be introduced and the compiler can treat closed and
public enums differently.</div>
<div><br></div>
</div>
</div>
</div>
_______________________________________________<br>swift-evolution mailing list<br>swift-evolution@swift.org<br>https://lists.swift.org/mailman/listinfo/swift-evolution<br></div></div></span></blockquote></div><div class="bloop_markdown"><p></p></div></body></html>