- Proposal: SE-0119
- Author: Adrian Zubarev
- Status: Active Review July 12...19
- Review manager: Chris Lattner
One great goal for Swift 3 is to sort out any source breaking language changes. This proposal aims to fix access modifier inconsistency on extensions compared to other scope declarations types.
Swift-evolution thread: [Proposal] Revising access modifiers on extensions
The access control of classes, enums and structs in Swift is very easy to learn and memorize. It also disallows to suppress the access modifier of implemented conformance members to lower access modifier if the host type has an access modifier of higher or equal level.
`public` > `internal` > `fileprivate` >= `private`public class A {
public func foo() {}
}
public class B : A {
// `foo` must retain `public`
override public func foo() {}
}
However in Swift it is possible to grant more visibility to a member but still hide the conformance to a protocol.
internal protocol C {
func foo()
}
public struct D : C {
// `foo` can be either `internal` or `public`
public func foo() {}
}
The imported module will look like this:
public struct D {
public func foo()
}
This simple access control model also allows us to nest types inside each other to create a really nice type hierarchy.
Extensions however behave differently when it comes to their access control:
-
The access modifier of an extension sets the default modifier of its members which do not have their own localy defined modifier.
public struct D {} // The extension itself has also `private` visibility private extension D { // `foo` is implicitly `private` func foo() {} }
-
Any type members added in an extension have the same default access level as type members declared in the original type being extended. If you extend a public or internal type, any new type members you add will have a default access level of internal. If you extend a private type, any new type members you add will have a default access level of private.
Source: The Swift Programming Language
```swift
private struct E {}
extension E {
// `foo` is implicitly `private`
func foo() {}
}
```
-
The access modifier can be overridden by the member with a lower access modifier.
public struct F {} internal extension F { // `foo` can be `internal`, `fileprivate` or `private` private func foo() {} }
Furthermore in Swift 2.2 it is not allowed to apply an access modifier on extensions when a type inheritance clause is present:
public protocol SomeProtocol {}
// 'public' modifier cannot be used with
// extensions that declare protocol conformances
public extension A : SomeProtocol {}
Extensions are also used for protocol default implementations in respect to the mentioned rules. That means that if someone would want to provide a public default implementation for a specific protocol there are three different ways to achive this goal:
public protocol G {
func foo()
}
-
First way:
extension G { public func foo() { /* implement */ } }
-
Second way:
public extension G { func foo() { /* implement */ } }
-
Third way:
public extension G { public func foo() { /* implement */ } }
Any version will currently be imported as:
public protocol G {
func foo()
}
extension G {
public func foo()
}
I propose to revise the access control on extensions by removing access modifiers from extensions.
That way, access for members follows the same defaults as in the original type.
-
It would be possible to conform types to a protocol using an extension which has an explicit access modifier. The access modifier respects the modifier of the extended type and the protocol to which it should be conformed.
internal protocol H { func foo() } public protocol I { func boo() } public struct J {} public extension J : H { // We can grant `foo` visibility but still hide conformance to `H` // and move everything from `H` to an extra extension bag public func foo() {} // Access modifier on members won't be overridden by the extension access modifier anymore // And they will respect the access level boundary set by the extension func moo() {} } // The extension of `J` conforming to `I` must retain `public` public extension J : I { // `boo` must retain `public` public func boo() {} }
The above extension can be simplified to:
// The extension must retain `public` because `J` and `I` are marked as `public` public extension J : H, I { // `foo` can be either `public` or `internal` public func foo() {} // Implicitly `internal` func moo() {} // `boo` must retain `public` public func boo() {} }
-
The right and only one version for public protocol default implementations will look like this:
public extension G { public func foo() { /* implement */ } }
-
Removing this behavior would imply less need to learn different behaviors for access control in general.
-
From a future perspective one could allow Swift to have nested extensions (which is neither part nor a strong argument of this proposal).
internal protocol K {} public struct L { public struct M {} /* implicitly internal */ extension M : K {} } // Nested extension would remove this: /* internal */ extension L.M : K {}
-
The ability of setting the default modifier could be reintroduces in its own typeless scope design, which might look like this:
fileprivate extension Int { // Not visible outside this extension bag private func doSomething() -> Int { ... } fileprivate group { // Every group memebr is `fileprivate` func member1() {} func member2() {} func member3() {} func member4() {} func member5() {} } }
Such a mechanism could also be used outside extensions! This idea has its own discussion thread.
-
Remove access modifier from extensions to stop being able to set the default access modifier.
-
Allow access modifier when type-inheritance-clause is present.
-
Access modifier on extensions should respect the modifier of the extended type and the protocol to which it should conform.
-
Public protocol:
public type
+public protocol
=public extension
internal type
+public protocol
=internal extension
private type
+public protocol
=private extension
-
Internal protocol:
public type
+internal protocol
=public extension
orinternal extension
internal type
+internal protocol
=internal extension
private type
+internal protocol
=private extension
-
Private protocol:
public type
+private protocol
=public extension
orinternal extension
orprivate extension
internal type
+private protocol
=internal extension
orprivate extension
private type
+private protocol
=private extension
-
Multiple protocol conformance is decided analogously by using the highest access modifier from all protocols + the access level of the extended type.
-
extension-declaration → access-level-modifieropt extension type-identifier type-inheritance-clauseopt extension-body
extension-declaration → access-level-modifieropt extension type-identifier requirement-clause extension-body
extension-body → { declarationsopt }
Iff the access-level-modifier is not present, the access modifier on extensions should always be implicitly internal.
- extension SomeType : SomeProtocol {
+ public extension SomeType : SomeProtocol {
public func someMemeber()
}
This is a source-breaking change that can be automated by a migrator.
-
Extensions without an explicit access modifier:
//===-----------------------------===// //===-------- public type --------===// //===-----------------------------===// public struct AA {} - extension AA { + public extension AA { func member1() {} public func member2() {} private func member3() {} } //===-----------------------------===// //===------- internal type -------===// //===-----------------------------===// internal struct BB {} // No impact at all because it is already // implicitly `internal` extension BB { func member1() {} private func member2() {} } //===-----------------------------===// //===------- private type --------===// //===-----------------------------===// private struct CC {} - extension CC { + private extension CC { // Implicitly private func member1() {} private func member2() {} }
-
Extensions with an explicit access modifier:
//===-----------------------------===// //===-------- public type --------===// //===-----------------------------===// public struct DD {} public extension DD { - func member1() {} + public func member1() {} public func member2() {} private func member3() {} internal func member4() {} } internal extension DD { func member5() {} private func member6() {} internal func member7() {} } private extension DD { // Implicitly private func member8() {} private func member9() {} } //===-----------------------------===// //===------- internal type -------===// //===-----------------------------===// internal struct EE {} internal extension EE { func member1() {} private func member2() {} internal func member3() {} } private extension EE { // Implicitly private func member4() {} private func member5() {} } //===-----------------------------===// //===------- private type --------===// //===-----------------------------===// private struct FF {} private extension FF { // Implicitly private func member1() {} private func member2() {} }
-
Extensions without an explicit access modifier and protocol conformance:
public protocol Foo { func foo() } internal protocol Boo { func boo() } private protocol Zoo { func zoo() } //===-----------------------------===// //===-------- public type --------===// //===-----------------------------===// public struct GG {} - extension GG : Foo, Boo, Zoo { + public extension GG : Foo, Boo, Zoo { func member1() {} public func member2() {} private func member3() {} // Access modifier for `foo`, `boo` and `zoo` // won't have an impact } //===-----------------------------===// //===------- internal type -------===// //===-----------------------------===// internal struct HH {} // No impact at all because it is already // implicitly `internal` extension BB : Foo, Boo, Zoo { func member1() {} private func member2() {} // Access modifier for `foo`, `boo` and `zoo` // won't have an impact } //===-----------------------------===// //===------- private type --------===// //===-----------------------------===// private struct II {} - extension CC : Foo, Boo, Zoo { + private extension CC : Foo, Boo, Zoo { // Implicitly private func member1() {} private func member2() {} // Access modifier for `foo`, `boo` and `zoo` // won't have an impact (all are private) }
- Allow access modifier when type-inheritance-clause is present and use the rules presented in Proposed solution.
On [Date], the core team decided to (TBD) this proposal. When the core team makes a decision regarding this proposal, their rationale for the decision will be written here.