[Draft] A Consistent Foundation For Access Control: Scope-Bounded Capabilities


(Matthew Johnson) #1

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#grammar>Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#playground>Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.playground.zip>.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#future-possibilities>Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#source-compatibility>Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-var>public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-enum>public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-protocol>public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-abi-stability>Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#alternatives-considered>Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.


Constrain protocol usage outside the module
(Jaden Geller) #2

Hi Matthew,

Overall, I think this is a great simplification of the access control mechanism! I really appreciate the focus on both “simple made easy” and safe defaults.

I have a few comments and questions:

(1) It seems odd to use `scope` as both an access control modifier alone and as a function-like attribute. Perhaps something like `access(set, scope)` would be better than `scope(set)`. As it is, we're conflating scope with meaning visibility and lexical scope—I found that very confusing.

(2) I think that the attribute should be spelled `@access` like other attributes in Swift, `@availability` for example. This seems more consistent.

(3) Speaking of `@availability`, how do you suppose access control ought to interact with that? Can I somehow mark that the setter was public in a previous version but is now private?

(4) The function-like access control attribute as you proposed it has very odd semantics, especially compared to Swift functions. For example, both `scope(get, file)` and `scope(file)` are valid spellings, as if `get` was a default argument, but Swift does not allow default arguments in that position without labels. Obviously this isn’t a parsing problem since we’re using keywords, but I find it very confusing. Either swap the order of the arguments or, I would prefer, add labels. With my `@access` spelling, we might have `@access(get, in: file)` and `@access(in: file)`.

(5) It seems odd that `call` is the name of the default visibility for functions. I agree with the semantics, but the naming seems to imply that I could still refer to the value `let foo = myFunction` but not call it. For Types and Protocols, `use` seems like a very generic word to, well uh, use. Perhaps `visible` would make more sense (though it is not a verb like the others…)? Perhaps `constrain`? I think the name `switch` for enums is also not particularly great, but this probably isn’t the time to bikeshed naming.

(6) Do closed protocols fit into this model? The `conform` capability allows API users to add additional conformances, but there’s no capability to promise that the API maintainer itself won’t add additional conformances. Obviously adding this features is out of the scope of this proposal, but I want to know how it might be spelled in this scheme.

(7) It isn’t immediately clear to me the “hierarchy” of additional capabilities. Obviously `set` is a refinement of `get`, but is `override` a refinement on `set` or `get`? Or are they entirely separate? Can you use both? It would be useful to formalize what happens when the access level is specified for multiple additional capabilities.

(8) It may be useful to clarify the hierarchy of scopes. Each name is mentioned, but it isn’t intuitively obvious how it works. Does `TypeName` essentially allow me to make friend classes, via `@access(set, in: Foo)`, a la C++. If so, that’s pretty cool! I’m assuming `extension` gives visibility in extensions to this type? What about subclasses—is there a sort of `protected` scope?

Thanks for the great proposal, Matthew!

Cheers,
Jaden Geller

···

On Mar 2, 2017, at 11:58 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#grammar>Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#playground>Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.playground.zip>.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#future-possibilities>Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#source-compatibility>Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-var>public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-enum>public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-protocol>public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-abi-stability>Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#alternatives-considered>Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(BJ Homer) #3

I want to read this through a couple more times to evaluate all the implications, but my initial impression is quite favorable. I especially like how the separation of concerns between visibility and capability opens clear pathways for future evolution.

A few notes on where the proposal could use some clarification.

- In the “aliases” section, you have multiple definitions for “final” and “open”. It’s not clear why there are multiple definitions—do they apply in different contexts?

- At the beginning of the “Proposed Solution” section, you said that “internal” should be the default availability for all capabilities. However, “capabilities” have not yet been concretely defined at this point, which makes it easy to gloss over that essential point. I recommend repeating information about “internal” default for capabilities later, in the “Capabilities” section. When I first read the proposal, I thought that *all* properties would become read-only unless specifically annotated with “scope(set)”. On further reading, I think that’s not correct, but it may bear clarification in the Capabilities section, where it’s most relevant.

- How would this affect a declaration like this:
      public private(set) var x: Int = 0

  Is source compatibility preserved in that case? How would that be spelled under this proposal? Would it be something like this?
      scope(set) public var x: Int = 0

  If so, is this also legal? Are multiple scope declarations allowed on a single property?
      scope(set) scope(get, public) var x: Int = 0

-BJ

···

On Mar 2, 2017, at 12:58 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#grammar>Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#playground>Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.playground.zip>.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#future-possibilities>Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#source-compatibility>Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-var>public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-enum>public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-protocol>public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-abi-stability>Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#alternatives-considered>Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


#4

This is an intriguing proposal. There are parts I like, and parts that I
will have to think about more before drawing an opinion.

Personally, I would like to see public classes be inheritable, public
protocols be conformable, and public enums be switchable, unless specified
otherwise. However I will defer to those who have more experience writing
libraries.

On a first read-through, a few things stand out to me:

1. There are still five basic access levels: scope, private, internal,
public, and open. I think this is too many, and would prefer to just have
private, internal, and public.

2. The default is listed as “submodule”. I think the default should always
be “module”, and restriction to a submodule should be explicit.

3. There is still a file-specific access level. I think this should be
removed in favor of the submodule level. If submodules can be nested then
there is no loss of expressivity, because a file could be made its own
sub-submodule.

4. The proposal keeps a scope-based access level, which I think is
unnecessary.

All that said, there is a lot to like about this model, and I will let the
ideas percolate.

Nevin


(Charles Srstka) #5

1) Can I declare something like this, and have it overridable but not otherwise accessible?

scope scope(inherit, everywhere) func cannotCallButCanOverride()

2) Will the above also work for protocols?

If the answer to these questions is “yes”, +1

Charles

···

On Mar 2, 2017, at 1:58 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#grammar>Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#playground>Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.playground.zip>.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#future-possibilities>Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#source-compatibility>Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-var>public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-enum>public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-protocol>public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-abi-stability>Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#alternatives-considered>Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #6

Hi Matthew,

Overall, I think this is a great simplification of the access control mechanism! I really appreciate the focus on both “simple made easy” and safe defaults.

Thanks Jaden! I appreciate your thoughtful feedback!

I have a few comments and questions:

(1) It seems odd to use `scope` as both an access control modifier alone and as a function-like attribute. Perhaps something like `access(set, scope)` would be better than `scope(set)`. As it is, we're conflating scope with meaning visibility and lexical scope—I found that very confusing.

This is good feedback. The reason I designed it this way is because it is intended to work like the existing access modifiers which can be parameterized or stand alone. The idea is that it is analogous to a function with default arguments and we treat the unbound function as if it had been called with no explicit arguments (using the defaults). There is an example of this in the playground prototype I included.

That said, there is a difference - the existing modifiers don't accept a scope argument. I can see how this could make it more confusing. In the current design `scope(set, lexical)` is already valid and is similar to the `access(set, scope)` idea you suggested.

One thing I want to be very careful about is making the syntax read well if possible. In the playground I even used argument labels `scope(of: set, is: lexical)`. I think that would be too verbose in real code but I think it's important that this read like a sentence in our minds at least.

One option would be to simply remove the default scope argument and require it to be provided explicitly when lexical scope is intended. I'm not sure how I feel about that. I'm going to spend some time thinking about this further. I definitely don't want the shorthand (without arguments) to be a source of confusion.

(2) I think that the attribute should be spelled `@access` like other attributes in Swift, `@availability` for example. This seems more consistent.

If it were an attribute I would agree. But it's not, it's an access modifier. Those don't have the `@`.

(3) Speaking of `@availability`, how do you suppose access control ought to interact with that? Can I somehow mark that the setter was public in a previous version but is now private?

This is a good question. I am not familiar enough with `@availability` to answer off the top of my head. Do you think this is something that needs to be addressed by this proposal?

(4) The function-like access control attribute as you proposed it has very odd semantics, especially compared to Swift functions. For example, both `scope(get, file)` and `scope(file)` are valid spellings, as if `get` was a default argument, but Swift does not allow default arguments in that position without labels. Obviously this isn’t a parsing problem since we’re using keywords, but I find it very confusing. Either swap the order of the arguments or, I would prefer, add labels. With my `@access` spelling, we might have `@access(get, in: file)` and `@access(in: file)`.

I actually used labels in the playground and thought about including them in the proposal. I decided against it on the grounds that access modifiers are pervasive and the labels could quickly be seen as noise. If others feel they are important I have no objection.

(5) It seems odd that `call` is the name of the default visibility for functions. I agree with the semantics, but the naming seems to imply that I could still refer to the value `let foo = myFunction` but not call it.

Some of these names were pretty tough to decide on. I wholeheartedly embrace bikeshedding here. I wouldn't be surprised if community input can improve on the names I have used. :slight_smile:

I chose `call` because if you can see a function you can call it. It isn't possible to see it, bind it to a name, etc without being able to call it and it probably shouldn't ever be.

For Types and Protocols, `use` seems like a very generic word to, well uh, use. Perhaps `visible` would make more sense (though it is not a verb like the others…)?

I agree. This was the most difficult name to decide on. The notion I tried to capture is that if you can see a type you can work with values of of that type (as well as access it's static members). I would be very happy if we could identify a better name.

Perhaps `constrain`?

This is also something you can always do when you can see a protocol, but you can also use it as an existential (thinking ahead to generalized existentials) so you can also work with values of the protocol existential type.

I think the name `switch` for enums is also not particularly great, but this probably isn’t the time to bikeshed naming.

I used `switchExhaustively` in an earlier draft but thought it was too verbose. Maybe the verbosity is necessary?

(6) Do closed protocols fit into this model? The `conform` capability allows API users to add additional conformances, but there’s no capability to promise that the API maintainer itself won’t add additional conformances. Obviously adding this features is out of the scope of this proposal, but I want to know how it might be spelled in this scheme.

Yes. I think this would use the same capability name as enums that support exhaustive switch.

One interesting point is that this would introduce the notion of mutually exclusive capabilities. You can't have both `public(conform)` and `public(switch)`.

(7) It isn’t immediately clear to me the “hierarchy” of additional capabilities. Obviously `set` is a refinement of `get`, but is `override` a refinement on `set` or `get`? Or are they entirely separate? Can you use both? It would be useful to formalize what happens when the access level is specified for multiple additional capabilities.

Great question. I'll add clarification on this point in the next draft. The short answer is that the bounds of additional capabilities are usually independent of each other.

That said, as noted above, in some cases they could be mutually exclusive. It's also possible that they could have other kinds of relationships. This really depends on the semantics of the declaration providing the capabilities.

(8) It may be useful to clarify the hierarchy of scopes. Each name is mentioned, but it isn’t intuitively obvious how it works. Does `TypeName` essentially allow me to make friend classes, via `@access(set, in: Foo)`, a la C++. If so, that’s pretty cool! I’m assuming `extension` gives visibility in extensions to this type? What about subclasses—is there a sort of `protected` scope?

Great question. I'll clarify this further in an updated draft.

In the meantime, I'm afraid I'm going to have to let you down a bit on this point. Scopes form a strictly nested hierarchy. This means that you can only reference an ancestor scope.

For types, this simply follows the lexical nesting of types and member declarations. A declaration may reference the scope of a type that contains it by using the name of the type. The capability is available to any declarations within the lexical scope of the type declaration, including other types. But it is intentionally *not* like `friend` in C++.

`extension` is simply a way to reference the lexical scope created by an extension that contains a declaration. It does not provide access to extensions of the type containing the declaration.

The purpose of access control is compiler verified encapsulation. Since anybody can add an extension anywhere the compiler can't really offer any useful verification of encapsulation. I don't think it would be a good idea for access control to have a feature like this. The same is true of protected and any other type-based access control scheme.

Swift's access control system has always followed the idea of strictly hierarchical scopes, it just hasn't allowed us to name most of them. I think this was a really good principle that we should continue to embrace.

That said, I do have some thoughts about ways to address the kinds of use cases people have in mind for "extensions or subclasses of the type" visibility. The idea I have been exploring is what I have been calling "symbol groups". I have been thinking about this in the context of imported symbols and haven't given any thought as to how it might apply outside that context. The way it looks with an import is this:

import UIKit including UIGestureRecognizerSubclasses

The idea here is that the symbols reside in the same (sub)module as the other symbols they are related to but are not imported unless the symbol group name is explicitly stated. This solves the problem of unintentional or inappropriate symbol availability and accidental misuse without offering promises of encapsulation that can't really be followed through on (as is the case with `protected`).

This is obviously well outside the scope of this proposal but is something I think would be worth pursuing at some point.

Thanks for the great proposal, Matthew!

Thanks Jaden! I'm really happy that you like it. I was pretty uncertain about how it would be received. It's encouraging to see that others like the idea. :slight_smile:

···

Sent from my iPad

On Mar 2, 2017, at 3:35 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

Cheers,
Jaden Geller

On Mar 2, 2017, at 11:58 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities
Proposal: SE-NNNN
Authors: Matthew Johnson
Review Manager: TBD
Status: Awaiting review
Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

Problems with Swift's access control system

Swift's current access control system can has several problems.

Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
Functions and initializers

call (the basic capability)
override (for class methods only)
Types

use (the basic capability)
inherit (for classes)
switch (for enums)
Protocols

use (the basic capability)
conform
Extensions and typealiases

use (the basic capability)
Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

Detailed design

Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here.

Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #7

(1) It seems odd to use scope as both an access control modifier alone and as a function-like attribute. Perhaps something like access(set, scope) would be better than scope(set). As it is, we’re conflating scope with meaning visibility and lexical scope—I found that very confusing.
I like the idea of calling it access instead of scope, but I’d prefer keeping it as an access modifier instead of an attribute.

(2) I think that the attribute should be spelled @access like other attributes in Swift, @availability for example. This seems more consistent.
I must disagree here, because this proposal aims to replace all current access modifiers with a whole new model, but for simplicity keep aliases intact. That means scope(…) can be used as a standalone modifier if you’re someone who don’t want to use public, open etc. The implication of an access modifier is that it doesn’t need the @ attribute prefix.

My personal question is, what happens to static in classes? I cannot remember where I read it before, but I remember that static is an alias for final class. Does it mean that public static in classes will become an alias for scope(call, everywhere) scope(override, nowhere) class?

@Matthew: I’d really appreciate if you could add a few simple examples using scope(…) in real code to understand it’s behavior (I’m not asking for some code that makes sense, just something that explains the behavior of the access modifier). I’m kinda having hard times to follow the idea of overloading the typealiases in some corners.

···

--
Adrian Zubarev
Sent with Airmail


(Rien) #8

Just scanned it, but my first impression is: LOVE IT!

IMO this is the way to go.

Though some of the details will no doubt be improved. I will read it in more depth coming WE.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl

···

On 02 Mar 2017, at 20:58, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

  • Proposal: SE-NNNN
  • Authors: Matthew Johnson
  • Review Manager: TBD
  • Status: Awaiting review
Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

  • Complex: entangled, intertwined, interleaved, braided together
  • Simple: one strand, single focus, disentangled
  • Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

  • open vs public access modifiers classes and methods
  • Access modifier parameterization for setter availability: private(set)
  • The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

Problems with Swift's access control system

Swift's current access control system can has several problems.

Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

  • public var allows access to the setter
  • public enum allows exhaustive switch
  • public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo
{
    
private var bar = 42

}

An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

  • private(capability) = scope(capability, file)
  • internal(capability) = scope(capability, submodule)
  • public(capability) = scope(capability, everywhere)
  • open = scope(inherit, everywhere)
  • open = scope(override, everywhere)
  • final = scope(inherit, nowhere)
  • final = scope(override, nowhere)
  • total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

Scopes

The hierarchy of scopes is as follows:

  • nowhere
  • lexical
  • TypeName
  • extension
  • file
  • submodule
  • SubmoduleName
  • module
  • everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

Properties and subscripts

  • get (the basic capability)
  • set (for readwrite properties only)
  • override (for class properties only)
Functions and initializers

  • call (the basic capability)
  • override (for class methods only)
Types

  • use (the basic capability)
  • inherit (for classes)
  • switch (for enums)
Protocols

  • use (the basic capability)
  • conform
Extensions and typealiases

  • use (the basic capability)
Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

Detailed design

Rules

The rules which make up the essential complexity in Swift's access control system are:

  • The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
  • The scope of a capability may be modified by an explicit access modifier.
  • The scope of an additional capability is implicitly bounded by the scope of the basic capability.
  • The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
  • If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
  • The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
  • The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

  • The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
  • The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch

Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here.

Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Daniel Leping) #9

General impression is positive in case shortcuts (private, bulblic) still
remain for the most common use cases.

I’ve been continuing to think about how to provide clear and consistent
semantics for access control in Swift. This draft represents what I think
is the best way to accomplish that. It eliminates the current
inconsistencies and establishes a principled basis for the features we have
today as well as the enhancements we may need in the future. It does this
with minimal breaking changes.

The draft is included below and can also be found here:
https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md
.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

   - Proposal: SE-NNNN
   <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
   - Authors: Matthew Johnson <https://github.com/anandabits>
   - Review Manager: TBD
   - Status: Awaiting review

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>
Introduction

This proposal introduces a consistent foundation for all access control in
Swift: scope-bounded capabilities. The existing access control features are
generalized with a single mechanism that unifies their semantics. This
unified mechanism eliminates the inessential complexity and inconsistency
of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal
<https://lists.swift.org/pipermail/swift-evolution/>

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>
Motivation

The new access control features in Swift 3 have proven to be extremely
controversial. The most common refrain is that we need a more simple
system. In order to accomplish this we need to do more than tweak the
system we already have. We need to revisit the foundation of the system
itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple
Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(
https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich
explores the etymology and relationship of the words "simple", "complex",
and "easy". The meanings he explores are:

   - Complex: entangled, intertwined, interleaved, braided together
   - Simple: one strand, single focus, disentangled
   - Easy: familiar, nearby, readily at hand

The central point Rich makes in this talk is that when a design entangles
two orthogonal concepts complexity is the result. He coins the term
"complect" to refer to this kind of inessential complexity. This complexity
can be removed by disentangling the concepts. Instead of "complecting"
independent concerns we can *compose* them.

The result is a simpler system. It is simpler because independent concepts
can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible
system. When orthogonal concepts are entangled it is more difficult to
extend the system to meet future needs. One concept cannot be extended
independently of the other. It is not possible to make independent
decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach
for an immediately "easy" solution. Unfortunately, the "easy" solution
often entangles concepts and are therefor actually complex. He suggests
that we *first*design a simple (i.e. disentangled) solution and then
layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two
orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts:
availability and capability. Availability is what immediately comes to mind
when one thinks of access control: a symbol is either available or it is
not. Capability is more nuanced. It refers to what you can *do* with that
symbol.

Each declaration supports a *basic* capability which is always available
when the symbol itself is available. Many declarations also offer
*additional* capabiities (such as the ability to inherit, override, set a
property, etc). These additional capabilities may be *less available* than
the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does
not currently have a consistent way to talk about capabilities. Thus far we
have introduced new syntax every time we wish to distinguish the
availabiltiy of an *additional*capability from that of the symbol itself:

   - open vs public access modifiers classes and methods
   - Access modifier parameterization for setter availability:
   private(set)
   - The @closed attribute which has been discussed as a way to specify
   non-resilient enums in Swift 4*

It is clear that we need to be able to talk about not just basic
availability, but also *capabilities*. It would be very nice if we had one consistent
way to do this. This can be accomplished by composing the concepts of
availability and capability into the notion of a *scope-bounded
capability*.

*@closed would lie outside the access control system proper. It is
included for the sake of completeness. It is also included to demonstrate
how the language currently lacks a clear and obvious way to specify new
capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems
with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>
Inconsistency

As noted above, the ways *additional* capabilities are bounded is
inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal
default

The Swift evolution community has adopted the principle that nothing
should be available outside a module without an explicit declaration of
intent by a library author. This is an excellent default which protects
library authors against making an error of omission that would require a
breaking change to correct. Unfortunately this principle has not been
consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>
public

In *most* cases public only provides access to the *basic* capability a
declaration offers. This is true by definition for declarations do not
offer *additional* capabilities but it is *also* true for classes (with
respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to
*additional* capabilities:

   - public var allows access to the setter
   - public enum allows exhaustive switch
   - public protocol allows new conformances to be introduced

It is not currently possible to declare resilient enums or closed
protocols but both have received significant discussion. Further, resilient
enums need to be supported before ABI stability is declared. A consistent
access control system would treat these as independent capabilities that
are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>
private and fileprivate

The most heavily debated aspect of the changes to the access control
system in Swift 3 is without question the change in meaning of private to
be the current lexical scope and the renaming of the file-level scope to
fileprivate. This change was made with the idea that a lexically scoped
private would prove to be a good "soft default" for a less-than-module
availability bound. While many users appreciate the semantics of a
scope-based access modifier it has not proven to be a good "soft default"
and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>
Extensions

In languages without extensions lexically scoped availability is
equivalent to type-based availability for members of a type. In such a
language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an
extremely common Swift idiom. This idiom is not well supported by a "soft
default" of scope-based availability. The tension between a pervasive idiom
and the "soft default" leads to confusion about when scope-based a
availability is appropriate, and often an overuse of the feature. It also
leads to the need to use fileprivate much more frequently than is
desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types
and members

A "soft default" should not have subtle behavior that has the potential to
confuse beginners. Most beginners would expect Foo and bar in the
following example to have the same visibility. This was true in Swift 2 but
it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An
advanced feature

Lexically scoped availability has important uses such as preserving
invariants. All access to invariant-related state can be routed through
basis methods which access the state carefully without violating
invariants, even when that access happens in an extension in the same file.
We should not abandon this tool but it should not be the "soft default". It
is best reserved for specific use cases where the guarantee it offers is
important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential
and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are
all forms of *inessential* complexity. This makes Swift's access control
system more difficult to understand and use than it needs to be and causes
confusion.

At the same time the *essential* complexity of capabilities that are
bounded independent of basic symbol availability is not explicitly
acknowledged and embraced. This also makes the access control system more
difficult to understand and use than it should be. Users are not taught to
think in terms of independently bounded capabilities. This is a concept
that *could* be learned once and applied generally if it was more visible
in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed
solution

The proposed solution is to establish a semantic foundation for access
control that is *simple* in the sense of composing rather than
interleaving independent concerns. The solution is made *easy* by
defining familiar names in terms of this foundation while preserving the
semantics Swift users expect them to have. It is *consistent* in its use
of a single mechanism for bounding capabilities and its default of
internal for *all* capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded
capabilities

All access control is defined in terms of a parameterized access modifier
that allows the user to specify a capability and a scope that bounds that
capability.

// The scope of the setter is the current file.scope(set, file) var foo = 42

Here I suggest a bit different API to make it very distinct on which

capability gets which access. Instead of ',' use ':'

scope(set: file) vat foo = 42

Each parameter has a default argument. The default argument for the

···

On Thu, 2 Mar 2017 at 21:58 Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:

capability is simply the *basic* capability the declaration provides. For
a variable this is the getter, for a method it is the ability to call the
method, for a type it is the ability to use the type and so on. The default
argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.// This is equivalent to `private var foo = 42` in Swift 3.scope var foo = 42
// The scope of the getter is the current file.scope(file) var bar = 42
// The scope of the setter is the current lexical scope.scope(set) var baz = 42

The scope of the basic capability implicitly bounds additional
capabilities: if basic use of a symbol is not available it is not possible
to anything with that symbol. This is similar to the existing rule that a
type implicitly bounds the availability of all symbols declared within its
scope: a public property of an internal type is not available outside the
module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>
Aliases

This modifier is simple (in the sense defined above), general and
powerful. However it is also unfamiliar, often slightly verbose, and offers
a very wide configuration space. Familiar aliases are provided as "soft
defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand
scopes, capabilities and how they compose to produce scope-bounded
capabilities the user also has the ability to understand *all* aliases we
introduce. Tools could even make the definition of the alias available to
remind the user of its underlying meaning (similarly to they way Xcode
allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

   - private(capability) = scope(capability, file)
   - internal(capability) = scope(capability, submodule)
   - public(capability) = scope(capability, everywhere)
   - open = scope(inherit, everywhere)
   - open = scope(override, everywhere)
   - final = scope(inherit, nowhere)
   - final = scope(override, nowhere)
   - total = scope(switch, everywhere)

private reverts to the Swift 2 semantics. scope with no parameters has
semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope
until submodules are introduced. It is specified this way to indicate the
intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides
semantics equivalent to public enum. public enum receives the semantics
of resilient enums. If a suitable shorthand is not identified the slightly
longer public(switch) enum can be used to specify an enum which supports
exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration
they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>
Scopes

The hierarchy of scopes is as follows:

   - nowhere
   - lexical
   - TypeName
   - extension
   - file
   - submodule
   - SubmoduleName
   - module
   - everywhere

The name of any ancestor type or submodule of a declaration, including the
immediately containing type or submodule, form the set of valid
user-defined scope references.

Including nowhere allows us to define final in terms of this system. It
also allows us to model all properties and functions with the same set of
capabilities: the setter of a read only property is automatically bounded
to nowhere and the override capability of a function that is not a class
method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced
users a degree of control that is not possible in the current access
control system. If submodules are introduced into Swift this additional
control will be especially useful as a means to facilitate bounded
collaboration between peer submodules allowing them to communicate in ways
not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>
Capabilities

The capabilities available depend on the kind of declaration an access
modifier is applied to. All declarations offer a *basic*capability that
is always available when the declaration itself is available. The basic
capability is specified by default when the scope modifier or a
parameterized alias is used without an explicit capability argument. Some
declarations also offer *additional* capabilities which may have an
independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties
and subscripts

   - get (the basic capability)
   - set (for readwrite properties only)
   - override (for class properties only)

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions
and initializers

   - call (the basic capability)
   - override (for class methods only)

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>
Types

   - use (the basic capability)
   - inherit (for classes)
   - switch (for enums)

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>
Protocols

   - use (the basic capability)
   - conform

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions
and typealiases

   - use (the basic capability)

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable
in the future

As the language grows the mechanism of scope-bounded capabilities can be
extended in an obvious way to meet the needs of future declarations and
capabilities. Users are only required to learn about the new declaration or
capability that was introduced. Their existing knowledge of the
scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed
design
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>
Rules

The rules which make up the *essential* complexity in Swift's access
control system are:

   - The default scope for all capabilites a declaration offers is
   module-wide (or submodule-wide in the future).
   - The scope of a capability may be modified by an explicit access
   modifier.
   - The scope of an *additional* capability is implicitly bounded by the
   scope of the *basic* capability.
   - The scope of an *additional* capability may not be explicitly
   specified as greater than that of the *basic* capability.
   - If no scope is explicitly provided for the *basic* capability and an
   *additional* capability is specified to be available outside the
   (sub)module the *basic* capability is also given the same availability.
   - The scope of a declaration (including all capabilities) may be
   bounded by the declaration of ancestor.
   - The scope of a declaration may not be greater than the scope of the
   capabilities necessary to use that declaration: if you can't see a
   parameter type you can't call the function.

Most of these rules already exist in Swift's access control system. There
is one change and one addition:

   - The first rule changes


(Matthew Johnson) #10

I have updated the proposal to reflect initial feedback. The primary change is to modify the syntax to make the name of the fundamental modifier `access` and introduce a `scoped` alias for lexical scope. Several clarifications were added as well.

The new draft can be found on Github: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

The diff is also available for those who read the first draft: https://github.com/anandabits/swift-evolution/commit/321dc65fc5a81b601ce45212af4e99c6894e6c33.

I’m looking forward to your feedback on the new draft!

Thanks,
Matthew

···

On Mar 2, 2017, at 1:58 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#grammar>Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#playground>Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.playground.zip>.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#future-possibilities>Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#source-compatibility>Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-var>public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-enum>public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-protocol>public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-abi-stability>Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#alternatives-considered>Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #11

I have updated this proposal with a few more refinements based on yesterday’s discussion. You can find the final proposal on Github:

https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

Matthew

···

On Mar 2, 2017, at 1:58 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#grammar>Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#playground>Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.playground.zip>.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#future-possibilities>Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#source-compatibility>Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-var>public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-enum>public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-protocol>public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-abi-stability>Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#alternatives-considered>Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jaden Geller) #12

1) Can I declare something like this, and have it overridable but not otherwise accessible?

scope scope(inherit, everywhere) func cannotCallButCanOverride()

I just want to use this code sample ready quick to emphasize a point I made in my email.

I initially thought that Charles made a typo here repeating scope. I wouldn’t have been confused had this been written

@access(inherit, everywhere)
scope func cannotCallButCanOverride()

···

On Mar 2, 2017, at 3:31 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

2) Will the above also work for protocols?

If the answer to these questions is “yes”, +1

Charles

On Mar 2, 2017, at 1:58 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#grammar>Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#playground>Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.playground.zip>.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#future-possibilities>Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#source-compatibility>Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-var>public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-enum>public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-protocol>public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-abi-stability>Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#alternatives-considered>Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jaden Geller) #13

Sent from my iPad

Hi Matthew,

Overall, I think this is a great simplification of the access control mechanism! I really appreciate the focus on both “simple made easy” and safe defaults.

Thanks Jaden! I appreciate your thoughtful feedback!

I have a few comments and questions:

(1) It seems odd to use `scope` as both an access control modifier alone and as a function-like attribute. Perhaps something like `access(set, scope)` would be better than `scope(set)`. As it is, we're conflating scope with meaning visibility and lexical scope—I found that very confusing.

This is good feedback. The reason I designed it this way is because it is intended to work like the existing access modifiers which can be parameterized or stand alone. The idea is that it is analogous to a function with default arguments and we treat the unbound function as if it had been called with no explicit arguments (using the defaults). There is an example of this in the playground prototype I included.

That said, there is a difference - the existing modifiers don't accept a scope argument. I can see how this could make it more confusing. In the current design `scope(set, lexical)` is already valid and is similar to the `access(set, scope)` idea you suggested.

One thing I want to be very careful about is making the syntax read well if possible. In the playground I even used argument labels `scope(of: set, is: lexical)`. I think that would be too verbose in real code but I think it's important that this read like a sentence in our minds at least.

One option would be to simply remove the default scope argument and require it to be provided explicitly when lexical scope is intended. I'm not sure how I feel about that. I'm going to spend some time thinking about this further. I definitely don't want the shorthand (without arguments) to be a source of confusion.

I think this would be an improvement.

As a sidenote, if there were to be an alias for lexical access control, think the pseudo-adjective `scoped` aligns better with the adjective `public`, `internal`, and `private`. The noun `scope` seems out of place.

(2) I think that the attribute should be spelled `@access` like other attributes in Swift, `@availability` for example. This seems more consistent.

If it were an attribute I would agree. But it's not, it's an access modifier. Those don't have the `@`.

I guess I’m suggesting that it might be better expressed as an attribute. The attribute `@available` is already used to specify symbol visibility (based on platform, version, etc.), so it seem best to align access control by using an attribute as well. I agree that the aliases like `private` shouldn’t require an `@` for concision…

(Does Swift have a hard-and-fast rule for which modifiers should use attributes and which should use keywords? Or even a more hand-wavey rule? It seems almost arbitrary to me…)

Another benefit is that there’s stylistic precedent for attribute declarations to be placed on the line before the declaration. This works well with wordier restrictions.

(3) Speaking of `@availability`, how do you suppose access control ought to interact with that? Can I somehow mark that the setter was public in a previous version but is now private?

This is a good question. I am not familiar enough with `@availability` to answer off the top of my head. Do you think this is something that needs to be addressed by this proposal?

I’m not entirely sure. It _probably_ doesn’t need to be addressed just yet. I don’t think `@availability` currently has such features, so it would be additive.

(4) The function-like access control attribute as you proposed it has very odd semantics, especially compared to Swift functions. For example, both `scope(get, file)` and `scope(file)` are valid spellings, as if `get` was a default argument, but Swift does not allow default arguments in that position without labels. Obviously this isn’t a parsing problem since we’re using keywords, but I find it very confusing. Either swap the order of the arguments or, I would prefer, add labels. With my `@access` spelling, we might have `@access(get, in: file)` and `@access(in: file)`.

I actually used labels in the playground and thought about including them in the proposal. I decided against it on the grounds that access modifiers are pervasive and the labels could quickly be seen as noise. If others feel they are important I have no objection.

I like labels, but I can understand the preference for concision. It just confuses my mental model when the semantics cannot be implemented in Swift. In your playground, try removing your labels—the function becomes impossible to call without specifying the capability argument!

(Sidenote: Why doesn’t Swift warn on function declarations that provide unusable default arguments??)

For the record, `@available` does not use labels. It even omits commas in places I might expect them… `@available(swift 3.0.2)`.

(5) It seems odd that `call` is the name of the default visibility for functions. I agree with the semantics, but the naming seems to imply that I could still refer to the value `let foo = myFunction` but not call it.

Some of these names were pretty tough to decide on. I wholeheartedly embrace bikeshedding here. I wouldn't be surprised if community input can improve on the names I have used. :slight_smile:

I agree that we should eventually bikeshed, but I’m trying to focus on the semantics first :slight_smile:

I chose `call` because if you can see a function you can call it. It isn't possible to see it, bind it to a name, etc without being able to call it and it probably shouldn't ever be.

For Types and Protocols, `use` seems like a very generic word to, well uh, use. Perhaps `visible` would make more sense (though it is not a verb like the others…)?

I agree. This was the most difficult name to decide on. The notion I tried to capture is that if you can see a type you can work with values of of that type (as well as access it's static members). I would be very happy if we could identify a better name.

Perhaps `constrain`?

This is also something you can always do when you can see a protocol, but you can also use it as an existential (thinking ahead to generalized existentials) so you can also work with values of the protocol existential type.

I was potentially recommending constrain for types too. `let x: Int` constrains `x` to be type `Int`, and `let y: Hashable` constraints `y` to be the (existential) type `Hashable`, or at least will once that’s possible. I’m not sure this is a good spelling, just throwing it out there…

I think the name `switch` for enums is also not particularly great, but this probably isn’t the time to bikeshed naming.

I used `switchExhaustively` in an earlier draft but thought it was too verbose. Maybe the verbosity is necessary?

Well, I think that the name `switch` is misleading, but I don’t love this more verbose name either. I was thinking `closed` maybe? …since it guarantees that no more cases can be added. Not sure.

(6) Do closed protocols fit into this model? The `conform` capability allows API users to add additional conformances, but there’s no capability to promise that the API maintainer itself won’t add additional conformances. Obviously adding this features is out of the scope of this proposal, but I want to know how it might be spelled in this scheme.

Yes. I think this would use the same capability name as enums that support exhaustive switch.

One interesting point is that this would introduce the notion of mutually exclusive capabilities. You can't have both `public(conform)` and `public(switch)`.

(7) It isn’t immediately clear to me the “hierarchy” of additional capabilities. Obviously `set` is a refinement of `get`, but is `override` a refinement on `set` or `get`? Or are they entirely separate? Can you use both? It would be useful to formalize what happens when the access level is specified for multiple additional capabilities.

Great question. I'll add clarification on this point in the next draft. The short answer is that the bounds of additional capabilities are usually independent of each other.

That said, as noted above, in some cases they could be mutually exclusive. It's also possible that they could have other kinds of relationships. This really depends on the semantics of the declaration providing the capabilities.

I think we really ought to formalize these relationships. It’s difficult to reason about otherwise.

(8) It may be useful to clarify the hierarchy of scopes. Each name is mentioned, but it isn’t intuitively obvious how it works. Does `TypeName` essentially allow me to make friend classes, via `@access(set, in: Foo)`, a la C++. If so, that’s pretty cool! I’m assuming `extension` gives visibility in extensions to this type? What about subclasses—is there a sort of `protected` scope?

Great question. I'll clarify this further in an updated draft.

In the meantime, I'm afraid I'm going to have to let you down a bit on this point. Scopes form a strictly nested hierarchy. This means that you can only reference an ancestor scope.

For types, this simply follows the lexical nesting of types and member declarations. A declaration may reference the scope of a type that contains it by using the name of the type. The capability is available to any declarations within the lexical scope of the type declaration, including other types. But it is intentionally *not* like `friend` in C++.

Unfortunate. If the community found this desirable, would it be additive to add this in the future? I don’t necessary want friend types, but I like that this syntax would make them much, much, much more intuitive than in C++. You wouldn’t even have to read the docs to understand the feature—just read the declaration!

`extension` is simply a way to reference the lexical scope created by an extension that contains a declaration. It does not provide access to extensions of the type containing the declaration.

The purpose of access control is compiler verified encapsulation. Since anybody can add an extension anywhere the compiler can't really offer any useful verification of encapsulation. I don't think it would be a good idea for access control to have a feature like this. The same is true of protected and any other type-based access control scheme.

I’m not really understanding what you’re saying here. I think we might be saying the same thing. My understanding was

struct Foo {
  scope(extension) var x: Int
}

means that I can write

extension Foo {
  func bar() {
    x += 1
  }
}

unlike if I had chose Swift 3.0 private.

Swift's access control system has always followed the idea of strictly hierarchical scopes, it just hasn't allowed us to name most of them. I think this was a really good principle that we should continue to embrace.

That said, I do have some thoughts about ways to address the kinds of use cases people have in mind for "extensions or subclasses of the type" visibility. The idea I have been exploring is what I have been calling "symbol groups". I have been thinking about this in the context of imported symbols and haven't given any thought as to how it might apply outside that context. The way it looks with an import is this:

import UIKit including UIGestureRecognizerSubclasses

The idea here is that the symbols reside in the same (sub)module as the other symbols they are related to but are not imported unless the symbol group name is explicitly stated. This solves the problem of unintentional or inappropriate symbol availability and accidental misuse without offering promises of encapsulation that can't really be followed through on (as is the case with `protected`).

This is obviously well outside the scope of this proposal but is something I think would be worth pursuing at some point.

Thanks for the great proposal, Matthew!

Thanks Jaden! I'm really happy that you like it. I was pretty uncertain about how it would be received. It's encouraging to see that others like the idea. :slight_smile:

Definitely! I appreciate your hard work! I realize how time-consuming it is to write these proposals, not to mention thinking through them!

···

On Mar 2, 2017, at 7:51 PM, Matthew Johnson <matthew@anandabits.com> wrote:
On Mar 2, 2017, at 3:35 PM, Jaden Geller <jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>> wrote:

Cheers,
Jaden Geller

On Mar 2, 2017, at 11:58 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#grammar>Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#playground>Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.playground.zip>.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#future-possibilities>Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#source-compatibility>Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-var>public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-enum>public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public-protocol>public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-abi-stability>Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#effect-on-api-resilience>Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#alternatives-considered>Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #14

I want to read this through a couple more times to evaluate all the implications, but my initial impression is quite favorable. I especially like how the separation of concerns between visibility and capability opens clear pathways for future evolution.

Thanks! I'm looking forward to any further thoughts you have after think it through further.

A few notes on where the proposal could use some clarification.

- In the “aliases” section, you have multiple definitions for “final” and “open”. It’s not clear why there are multiple definitions—do they apply in different contexts?

Yes. Classes and methods. I think alluded to this but it can probably use clarification. Thanks for pointing this out.

- At the beginning of the “Proposed Solution” section, you said that “internal” should be the default availability for all capabilities. However, “capabilities” have not yet been concretely defined at this point, which makes it easy to gloss over that essential point. I recommend repeating information about “internal” default for capabilities later, in the “Capabilities” section. When I first read the proposal, I thought that *all* properties would become read-only unless specifically annotated with “scope(set)”. On further reading, I think that’s not correct, but it may bear clarification in the Capabilities section, where it’s most relevant.

This is good feedback, thanks. The setter defaults to internal just like everything else.

- How would this affect a declaration like this:
      public private(set) var x: Int = 0

  Is source compatibility preserved in that case? How would that be spelled under this proposal? Would it be something like this?
      scope(set) public var x: Int = 0

You would just spell it the same way we already do. The version you have here would bound the setter to the lexical scope (the default scope).

  If so, is this also legal? Are multiple scope declarations allowed on a single property?
      scope(set) scope(get, public) var x: Int = 0

This would be equivalent to the previous version where the setter is bound to the lexical scope.

One minor point: I distinguished all of the scope names from the aliases in the proposal so you would actually say `scope(get, everywhere)`. I didn't elaborate on the rationale for that decision in the proposal and maybe should in an update. One reason was to clearly distinguish scope names from the aliases to avoid confusion. Another reason is that I like the idea of scopes named `module` and `submodule` with `internal` as an alias. It didn't feel right to use `public` as a scope name if `internal` isn't also a scope name. Finally, the symmetry of nowhere and everywhere is appealing and nowhere is actually a very useful scope on this system (it allows us to bring `final` into the fold, for example).

In real code you would always use an alias if it's available. An argument could be made to ban the fully written out version when an equivalent alias is available. I think that would be excessive but I wouldn't argue against it if a consensus formed around it. A fix-it would definitely be a good idea.

···

Sent from my iPad

On Mar 2, 2017, at 3:12 PM, BJ Homer <bjhomer@gmail.com> wrote:

-BJ

On Mar 2, 2017, at 12:58 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities
Proposal: SE-NNNN
Authors: Matthew Johnson
Review Manager: TBD
Status: Awaiting review
Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

Problems with Swift's access control system

Swift's current access control system can has several problems.

Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
Functions and initializers

call (the basic capability)
override (for class methods only)
Types

use (the basic capability)
inherit (for classes)
switch (for enums)
Protocols

use (the basic capability)
conform
Extensions and typealiases

use (the basic capability)
Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

Detailed design

Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here.

Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #15

1) Can I declare something like this, and have it overridable but not otherwise accessible?

scope scope(inherit, everywhere) func cannotCallButCanOverride()

I think you meant to use `override` instead of inherit. I'm not allowing this in the current proposal because it is not currently supported by Swift and the proposal is large enough without adding features like this. That said, this use case was noted in the future possibilities section as one that could be supported by this model with minor changes to the rules.

2) Will the above also work for protocols?

Can you elaborate further? Protocol requirements don't have access modifiers independent of the protocol itself. Protocol extension methods cannot be overridden. So I'm not sure how the above could be made to work with protocols no matter what kind of access control system we have.

···

Sent from my iPad

On Mar 2, 2017, at 5:31 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:

If the answer to these questions is “yes”, +1

Charles

On Mar 2, 2017, at 1:58 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities
Proposal: SE-NNNN
Authors: Matthew Johnson
Review Manager: TBD
Status: Awaiting review
Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

Problems with Swift's access control system

Swift's current access control system can has several problems.

Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
Functions and initializers

call (the basic capability)
override (for class methods only)
Types

use (the basic capability)
inherit (for classes)
switch (for enums)
Protocols

use (the basic capability)
conform
Extensions and typealiases

use (the basic capability)
Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

Detailed design

Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here.

Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #16

1) Can I declare something like this, and have it overridable but not otherwise accessible?

scope scope(inherit, everywhere) func cannotCallButCanOverride()

I just want to use this code sample ready quick to emphasize a point I made in my email.

I initially thought that Charles made a typo here repeating scope. I wouldn’t have been confused had this been written

@access(inherit, everywhere)
scope func cannotCallButCanOverride()

Thanks Jaden, I'm definitely going to give some careful thought to this issue.

···

Sent from my iPad

On Mar 2, 2017, at 7:11 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

On Mar 2, 2017, at 3:31 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

2) Will the above also work for protocols?

If the answer to these questions is “yes”, +1

Charles

On Mar 2, 2017, at 1:58 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities
Proposal: SE-NNNN
Authors: Matthew Johnson
Review Manager: TBD
Status: Awaiting review
Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

Problems with Swift's access control system

Swift's current access control system can has several problems.

Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
Functions and initializers

call (the basic capability)
override (for class methods only)
Types

use (the basic capability)
inherit (for classes)
switch (for enums)
Protocols

use (the basic capability)
conform
Extensions and typealiases

use (the basic capability)
Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

Detailed design

Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch
Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here.

Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(BJ Homer) #17

I agree that “scope(set: file)” reads better. It does make it a little strange that “scope(set)" is also valid, though; it’s ambiguous whether it takes labeled or unlabeled arguments. Under this proposal, all of these are valid:

scope var == scope(get, lexical)
scope(set) var == scope(set, lexical)
private(set) var == scope(set, file)
internal(set) var == scope(set, submodule)
public(set) var == scope(set, everywhere)

For beginner users, the use of “scope” as both an access modifier and a capability modifier seems a bit confusing; we’re “complecting” access control and capability control. Perhaps we should avoid the concept of “default arguments” altogether by separating the “scope” access control keyword from the concept of capability control. What about something like this?

scoped var == access(get: lexical)
scoped(set) var == access(set: lexical)
private(set) var == access(set: file)
internal(set) var == access(set: submodule)
public(set) var == access(set: everywhere)

Under this proposal, “scoped” is just another alias, similar to “private", “internal", and “public”. It does not have a privileged position, and it’s written as an adjective just like the others. The downside here is that we’re introducing two new keywords instead of one, but it means that we don’t need to mentally insert any default arguments. (The name of “access” is a placeholder, subject to bikeshedding.)

-BJ

···

On Mar 3, 2017, at 4:23 AM, Daniel Leping via swift-evolution <swift-evolution@swift.org> wrote:

General impression is positive in case shortcuts (private, bulblic) still remain for the most common use cases.

On Thu, 2 Mar 2017 at 21:58 Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42

Here I suggest a bit different API to make it very distinct on which capability gets which access. Instead of ',' use ':'

scope(set: file) vat foo = 42

Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #18

(1) It seems odd to use scope as both an access control modifier alone and as a function-like attribute. Perhaps something like access(set, scope) would be better than scope(set). As it is, we’re conflating scope with meaning visibility and lexical scope—I found that very confusing.
I like the idea of calling it access instead of scope, but I’d prefer keeping it as an access modifier instead of an attribute.

I’m coming around to this view as well. I’m going to give it a little bit more thought but will likely be updating the proposal to reflect this soon.

(2) I think that the attribute should be spelled @access like other attributes in Swift, @availability for example. This seems more consistent.
I must disagree here, because this proposal aims to replace all current access modifiers with a whole new model, but for simplicity keep aliases intact. That means scope(…) can be used as a standalone modifier if you’re someone who don’t want to use public, open etc. The implication of an access modifier is that it doesn’t need the @ attribute prefix.

My personal question is, what happens to static in classes? I cannot remember where I read it before, but I remember that static is an alias for final class. Does it mean that public static in classes will become an alias for scope(call, everywhere) scope(override, nowhere) class?

It would be really helpful if you can find a reference to this somewhere. If it’s possible to define static in terms of this system also that would be pretty cool.

@Matthew: I’d really appreciate if you could add a few simple examples using scope(…) in real code to understand it’s behavior (I’m not asking for some code that makes sense, just something that explains the behavior of the access modifier). I’m kinda having hard times to follow the idea of overloading the typealiases in some corners.

I'm not sure I follow what you mean by “overloading the typealiases”. Can you elaborate? If you can provide examples of things that you’re having trouble understanding that would be helpful. I will be happy to include additional examples where they can add clarity.

···

On Mar 3, 2017, at 3:19 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #19

Just scanned it, but my first impression is: LOVE IT!

IMO this is the way to go.

Though some of the details will no doubt be improved. I will read it in more depth coming WE.

Awesome, thanks! I have already received a lot of great feedback (thanks Jaden!) and will be posting a second draft that incorporates it as soon as I have a chance to make the updates.

···

On Mar 3, 2017, at 4:05 AM, Rien <Rien@Balancingrock.nl> wrote:

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl

On 02 Mar 2017, at 20:58, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

  • Proposal: SE-NNNN
  • Authors: Matthew Johnson
  • Review Manager: TBD
  • Status: Awaiting review
Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

  • Complex: entangled, intertwined, interleaved, braided together
  • Simple: one strand, single focus, disentangled
  • Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

  • open vs public access modifiers classes and methods
  • Access modifier parameterization for setter availability: private(set)
  • The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

Problems with Swift's access control system

Swift's current access control system can has several problems.

Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

  • public var allows access to the setter
  • public enum allows exhaustive switch
  • public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo
{

private var bar = 42

}

An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42
Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

  • private(capability) = scope(capability, file)
  • internal(capability) = scope(capability, submodule)
  • public(capability) = scope(capability, everywhere)
  • open = scope(inherit, everywhere)
  • open = scope(override, everywhere)
  • final = scope(inherit, nowhere)
  • final = scope(override, nowhere)
  • total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

Scopes

The hierarchy of scopes is as follows:

  • nowhere
  • lexical
  • TypeName
  • extension
  • file
  • submodule
  • SubmoduleName
  • module
  • everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

Properties and subscripts

  • get (the basic capability)
  • set (for readwrite properties only)
  • override (for class properties only)
Functions and initializers

  • call (the basic capability)
  • override (for class methods only)
Types

  • use (the basic capability)
  • inherit (for classes)
  • switch (for enums)
Protocols

  • use (the basic capability)
  • conform
Extensions and typealiases

  • use (the basic capability)
Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

Detailed design

Rules

The rules which make up the essential complexity in Swift's access control system are:

  • The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
  • The scope of a capability may be modified by an explicit access modifier.
  • The scope of an additional capability is implicitly bounded by the scope of the basic capability.
  • The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
  • If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
  • The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
  • The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

  • The first rule changes the availability of the additional capability of public readwrite properties, protocols and enums.
  • The fifth rule affords shorthand for implicitly making the basic capability public when an additional capability is also made public.
Grammar

The changes to the access modifier grammar are as follows:

access-level-modifier → scope­ | scope­(­ capability-specifier ­)­ | scope­(­ scope-specifier ­)­ | scope­( capability-specifier ­, scope-specifier ­)­
access-level-modifier → private­ | private­(­ capability-specifier ­)­
access-level-modifier → internal­ | internal­(­ capability-specifier ­)­
access-level-modifier → public­ | public­(­ capability-specifier ­)­
access-level-modifier → open­
access-level-modifier → final
access-level-modifier → total

scope-specifier → nowhere | extension | file | submodule | module | everywhere | identifier
capability-specifier → set | inherit | override | conform | switch

Playground

A Swift playground that includes a prototype of the basic scope-bounded capability access modifier as well as the aliases is availabe here.

Future possibilities

Scope-bounded capabilities are able to express set-only properties and override-only methods with a minor change to the rules of the system. These features have been requested on the list in the past. In the case of override-only methods there are known examples in Apple's frameworks. Allowing support for these would add some complexity to the model and is not essential to establishing a consistent basis for the existing access control feature.

Source compatibility

This proposal will not cause any Swift 3 source to fail to compile but will produce different behavior in three cases. In all cases a mechanical migration is possible.

public var

This proposal removes the availability of the setter of a public var outside the module, requiring public(set) var to expose the setter. This requires a migration of existing code. We could ease this transition with a deprecation warning in one release and then introduce the semantic change in the following release.

public enum

This proposal removes the availability of exhaustive switch from public enums. Non-resilient enums will need to be declared as a total enum (or the equivalent keyword chose after bikeshedding) or public(switch) enum if we choose not to introduce an alias for this semantic. As with public var, a deprecation warning and deferred semantic change could be used to ease the transition.

public protocol

This proposal requires protocols conformable outside the module to use the open protocol alias rather than public protocol. Visible but not conformable protocols are out of scope for Swift 4. This means that in Swift 4 open protocoland public protocol could share the same semantics with a deprecation warning on public protocol telling users to use open and that the semantics of public protocol will be different in the future. We could remove support for public protocol in a point release, reserving it until we introduce the ability for a protocol to be visible but not conformable.

Effect on ABI stability

If this proposal impacts ABI stability it would be in the area of runtime metadata or introspection. Input on this topic is welcome.

Effect on API resilience

This proposal does not impact API reslience. The proposed solution recasts some existing features but does so in a way that should be backwards compatible. No existing semantics are changed, only how those semantics are stated syntactically.

Alternatives considered

The primary alternative is to continue using the ad-hoc approach to meeting our access control needs. There have been many different ideas about how we might be able to simplify the current system by removing or slightly tweaking some of the existing features. The author believes this approach will not be able to meet future needs well and will continue to be burdened by the accumulation of inessential complexity. This situation will get worse, not better, as new features are added to the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #20

General impression is positive in case shortcuts (private, bulblic) still remain for the most common use cases.

The shorthand isn’t going anywhere. It is essential to making the system practical and familiar.

···

On Mar 3, 2017, at 5:23 AM, Daniel Leping <daniel@crossroadlabs.xyz> wrote:

On Thu, 2 Mar 2017 at 21:58 Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I’ve been continuing to think about how to provide clear and consistent semantics for access control in Swift. This draft represents what I think is the best way to accomplish that. It eliminates the current inconsistencies and establishes a principled basis for the features we have today as well as the enhancements we may need in the future. It does this with minimal breaking changes.

The draft is included below and can also be found here: https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md.

I’m looking forward to everyone’s feedback.

Matthew

A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Proposal: SE-NNNN <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
Authors: Matthew Johnson <https://github.com/anandabits>
Review Manager: TBD
Status: Awaiting review
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>Introduction

This proposal introduces a consistent foundation for all access control in Swift: scope-bounded capabilities. The existing access control features are generalized with a single mechanism that unifies their semantics. This unified mechanism eliminates the inessential complexity and inconsistency of the current system while expanding its utility.

Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>Motivation

The new access control features in Swift 3 have proven to be extremely controversial. The most common refrain is that we need a more simple system. In order to accomplish this we need to do more than tweak the system we already have. We need to revisit the foundation of the system itself.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple Made Easy

Rich Hickey gave a fantastic talk called [Simple Made Easy])(https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich explores the etymology and relationship of the words "simple", "complex", and "easy". The meanings he explores are:

Complex: entangled, intertwined, interleaved, braided together
Simple: one strand, single focus, disentangled
Easy: familiar, nearby, readily at hand
The central point Rich makes in this talk is that when a design entangles two orthogonal concepts complexity is the result. He coins the term "complect" to refer to this kind of inessential complexity. This complexity can be removed by disentangling the concepts. Instead of "complecting" independent concerns we can compose them.

The result is a simpler system. It is simpler because independent concepts can be considered and understood independently of each other.

The composition of independent concerns also results in a more flexible system. When orthogonal concepts are entangled it is more difficult to extend the system to meet future needs. One concept cannot be extended independently of the other. It is not possible to make independent decisions about what should be orthogonal aspects of the design.

Rich believes that the programming community is often too quick to reach for an immediately "easy" solution. Unfortunately, the "easy" solution often entangles concepts and are therefor actually complex. He suggests that we firstdesign a simple (i.e. disentangled) solution and then layer ease of use and familiarity on top, thus the title "Simple Made Easy".

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two orthogonal concepts

The access control system in Swift 3 incorporates two orthogonal concepts: availability and capability. Availability is what immediately comes to mind when one thinks of access control: a symbol is either available or it is not. Capability is more nuanced. It refers to what you can do with that symbol.

Each declaration supports a basic capability which is always available when the symbol itself is available. Many declarations also offer additional capabiities (such as the ability to inherit, override, set a property, etc). These additional capabilities may be less available than the symbol itself.

In Swift, availability is always specified in terms of a scope. Swift does not currently have a consistent way to talk about capabilities. Thus far we have introduced new syntax every time we wish to distinguish the availabiltiy of an additionalcapability from that of the symbol itself:

open vs public access modifiers classes and methods
Access modifier parameterization for setter availability: private(set)
The @closed attribute which has been discussed as a way to specify non-resilient enums in Swift 4*
It is clear that we need to be able to talk about not just basic availability, but also capabilities. It would be very nice if we had one consistent way to do this. This can be accomplished by composing the concepts of availability and capability into the notion of a scope-bounded capability.

*@closed would lie outside the access control system proper. It is included for the sake of completeness. It is also included to demonstrate how the language currently lacks a clear and obvious way to specify new capability bounds when they are arise.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems with Swift's access control system

Swift's current access control system can has several problems.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>Inconsistency

As noted above, the ways additional capabilities are bounded is inconsistent. The semantics of public are also inconsistent.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal default

The Swift evolution community has adopted the principle that nothing should be available outside a module without an explicit declaration of intent by a library author. This is an excellent default which protects library authors against making an error of omission that would require a breaking change to correct. Unfortunately this principle has not been consistently applied.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>public

In most cases public only provides access to the basic capability a declaration offers. This is true by definition for declarations do not offer additional capabilities but it is also true for classes (with respect to inheritance) and class methods (with respect to overrides).

However, there are three cases where public currently provides access to additional capabilities:

public var allows access to the setter
public enum allows exhaustive switch
public protocol allows new conformances to be introduced
It is not currently possible to declare resilient enums or closed protocols but both have received significant discussion. Further, resilient enums need to be supported before ABI stability is declared. A consistent access control system would treat these as independent capabilities that are not made available with a simple public declaration.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>private and fileprivate

The most heavily debated aspect of the changes to the access control system in Swift 3 is without question the change in meaning of private to be the current lexical scope and the renaming of the file-level scope to fileprivate. This change was made with the idea that a lexically scoped private would prove to be a good "soft default" for a less-than-module availability bound. While many users appreciate the semantics of a scope-based access modifier it has not proven to be a good "soft default" and therefore does not deserve the name private.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>Extensions

In languages without extensions lexically scoped availability is equivalent to type-based availability for members of a type. In such a language it could make a reasonable default. Swift is not such a language.

Using several extensions on the same type within the same file is an extremely common Swift idiom. This idiom is not well supported by a "soft default" of scope-based availability. The tension between a pervasive idiom and the "soft default" leads to confusion about when scope-based a availability is appropriate, and often an overuse of the feature. It also leads to the need to use fileprivate much more frequently than is desirable for such an awkward keyword.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types and members

A "soft default" should not have subtle behavior that has the potential to confuse beginners. Most beginners would expect Foo and bar in the following example to have the same visibility. This was true in Swift 2 but it is not true in Swift 3.

private struct Foo {
    private var bar = 42
}
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An advanced feature

Lexically scoped availability has important uses such as preserving invariants. All access to invariant-related state can be routed through basis methods which access the state carefully without violating invariants, even when that access happens in an extension in the same file. We should not abandon this tool but it should not be the "soft default". It is best reserved for specific use cases where the guarantee it offers is important to correctess of the software.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential and inessential complexity

The inconsistencies noted above and a bad "soft default" of private are all forms of inessential complexity. This makes Swift's access control system more difficult to understand and use than it needs to be and causes confusion.

At the same time the essential complexity of capabilities that are bounded independent of basic symbol availability is not explicitly acknowledged and embraced. This also makes the access control system more difficult to understand and use than it should be. Users are not taught to think in terms of independently bounded capabilities. This is a concept that could be learned once and applied generally if it was more visible in the language.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed solution

The proposed solution is to establish a semantic foundation for access control that is simple in the sense of composing rather than interleaving independent concerns. The solution is made easy by defining familiar names in terms of this foundation while preserving the semantics Swift users expect them to have. It is consistent in its use of a single mechanism for bounding capabilities and its default of internal for all capabilities.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded capabilities

All access control is defined in terms of a parameterized access modifier that allows the user to specify a capability and a scope that bounds that capability.

// The scope of the setter is the current file.
scope(set, file) var foo = 42

Here I suggest a bit different API to make it very distinct on which capability gets which access. Instead of ',' use ':'

scope(set: file) vat foo = 42

Each parameter has a default argument. The default argument for the capability is simply the basic capability the declaration provides. For a variable this is the getter, for a method it is the ability to call the method, for a type it is the ability to use the type and so on. The default argument for the scope is the current lexical scope.

// The scope of the getter is the current lexical scope.
// This is equivalent to `private var foo = 42` in Swift 3.
scope var foo = 42

// The scope of the getter is the current file.
scope(file) var bar = 42

// The scope of the setter is the current lexical scope.
scope(set) var baz = 42
The scope of the basic capability implicitly bounds additional capabilities: if basic use of a symbol is not available it is not possible to anything with that symbol. This is similar to the existing rule that a type implicitly bounds the availability of all symbols declared within its scope: a public property of an internal type is not available outside the module because the type itself is not available.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>Aliases

This modifier is simple (in the sense defined above), general and powerful. However it is also unfamiliar, often slightly verbose, and offers a very wide configuration space. Familiar aliases are provided as "soft defaults" which are recommended for common use cases.

These aliases introduce no additional semantics. Once a user understand scopes, capabilities and how they compose to produce scope-bounded capabilities the user also has the ability to understand all aliases we introduce. Tools could even make the definition of the alias available to remind the user of its underlying meaning (similarly to they way Xcode allows a user to command-click to see a symbol definition).

These aliases are defined in terms of the parameterized scope modifier:

private(capability) = scope(capability, file)
internal(capability) = scope(capability, submodule)
public(capability) = scope(capability, everywhere)
open = scope(inherit, everywhere)
open = scope(override, everywhere)
final = scope(inherit, nowhere)
final = scope(override, nowhere)
total = scope(switch, everywhere)
private reverts to the Swift 2 semantics. scope with no parameters has semantics equivalent to that of private in Swift 3.

internal is specified in terms of submodule and is equivalent to module scope until submodules are introduced. It is specified this way to indicate the intent of the author should submodules be added.

total is a placholder subject to bikeshedding. total enum provides semantics equivalent to public enum. public enum receives the semantics of resilient enums. If a suitable shorthand is not identified the slightly longer public(switch) enum can be used to specify an enum which supports exhaustive switch outside the module.

open, final and closed are overloaded based on the kind of declaration they are applied to.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>Scopes

The hierarchy of scopes is as follows:

nowhere
lexical
TypeName
extension
file
submodule
SubmoduleName
module
everywhere
The name of any ancestor type or submodule of a declaration, including the immediately containing type or submodule, form the set of valid user-defined scope references.

Including nowhere allows us to define final in terms of this system. It also allows us to model all properties and functions with the same set of capabilities: the setter of a read only property is automatically bounded to nowhere and the override capability of a function that is not a class method is automatically bounded to nowhere.

Allowing users to reference any ancestor scope introduces affords advanced users a degree of control that is not possible in the current access control system. If submodules are introduced into Swift this additional control will be especially useful as a means to facilitate bounded collaboration between peer submodules allowing them to communicate in ways not available to the rest of the module.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>Capabilities

The capabilities available depend on the kind of declaration an access modifier is applied to. All declarations offer a basiccapability that is always available when the declaration itself is available. The basic capability is specified by default when the scope modifier or a parameterized alias is used without an explicit capability argument. Some declarations also offer additional capabilities which may have an independent bound applied to them.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties and subscripts

get (the basic capability)
set (for readwrite properties only)
override (for class properties only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions and initializers

call (the basic capability)
override (for class methods only)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>Types

use (the basic capability)
inherit (for classes)
switch (for enums)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>Protocols

use (the basic capability)
conform
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions and typealiases

use (the basic capability)
<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable in the future

As the language grows the mechanism of scope-bounded capabilities can be extended in an obvious way to meet the needs of future declarations and capabilities. Users are only required to learn about the new declaration or capability that was introduced. Their existing knowledge of the scope-bounded capability access control system is immediately applicable.

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed design

<https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>Rules

The rules which make up the essential complexity in Swift's access control system are:

The default scope for all capabilites a declaration offers is module-wide (or submodule-wide in the future).
The scope of a capability may be modified by an explicit access modifier.
The scope of an additional capability is implicitly bounded by the scope of the basic capability.
The scope of an additional capability may not be explicitly specified as greater than that of the basic capability.
If no scope is explicitly provided for the basic capability and an additional capability is specified to be available outside the (sub)module the basic capability is also given the same availability.
The scope of a declaration (including all capabilities) may be bounded by the declaration of ancestor.
The scope of a declaration may not be greater than the scope of the capabilities necessary to use that declaration: if you can't see a parameter type you can't call the function.
Most of these rules already exist in Swift's access control system. There is one change and one addition:

The first rule changes