[swift-evolution] [Proposal] Revising access modifiers on extensions

Adrian Zubarev adrian.zubarev at devandartist.com
Mon Jun 27 03:38:06 CDT 2016


Hi, Adrian. Can you explain why you want to make this change? “public” on an extension doesn’t mean anything by itself because you can’t refer to an extension as an entity in and of itself. Access modifiers are disallowed on extensions with protocols because the conformance isn’t controlled by the access modifier and we didn’t want to give the impression that it would.
Now that you mention this, I did again some tests and I think I now understand why controlling conformance with access modifier would be fatal, but I think this edge-case also can be banned easily.

My first though was looking like this:

public protocol A { func foo() }

public struct B {}

// If we had the same access control like on structs/ classes
// We could suppress the visibility (imagine: `internal struct B : A`)
internal extension B : A {
     
    /* implicitly internal */ func foo(){}  
     
    // Some other custom members for this extension bag
    func boo() {}
}
Next step would be to import the module and extend B with A again, because it isn’t visible for the imported module. And that would lead to a huge problem.
BUT this is a very wrong thought, because if extensions would have the same access control as other types, this edge case cannot happen at all. The conformance itself is applied on the type B and moved to its own extension bag. But because the extended type B is public the compiler must raise an error (Fix-me?) for this particular extension that it cannot be internal when its members are coming from a public protocol and must retain public (iff the extend type is public as well).

The example from above will become this:

public protocol A { func foo() }

public struct B {}

public extension B : A {
     
    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 boo() {}
}
To sum this example up: the access modifier on extensions should only have control of its bag visibility in respect to the access level of the extended type. This would be consistent to structs, enums and classes.

In Swift we cannot suppress conformance visibility to lower visibility if the extended type is of higher or equal visibility as the protocol.

public protocol A { func foo() }

public struct B : A {
     
    // foo must retain public
    public func foo() {}
}

internal protocol C : A {
     
    // foo must retain internal
    // we cannot grant foo more visibility than the type its implemented in
    /* implicitly internal */ func foo() {}
}

// same for `fileprivate` and `private`
public > internal > fileprivate >= private (Iff private is allowed at file scope.)

However we can grant visibility to members and still hide the conformance itself.

internal protocol A { func foo() }

public protocol B : A {
     
    // foo won't be visible when imported
    func foo() {}
}

public protocol C : A {
     
    // we can grant foo more visibility than it originally had
    // foo will be visible when imported, but the  
    // conformance to `A` will not be visible
    public func foo() {}
}
That said we could do the same with extensions as well.

internal protocol A { func foo() }

public struct B {}

public extension B : A {
     
    // we can grant foo visibility but still hide conformance to A
    // and move everything from `A` 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 boo() {}
}
This whole idea is to gain more consistent control of visibility and sort out the strange access control rules extensions currently have.

If I’m correct this also would allow us to nest extension (but this needs an other thread in the future) if there is any desire to do so:

internal protocol A {}

public struct B {
     
    public struct C {}
     
    /* implicitly internal */ extension C : A {}
}

// Nested extension would remove this
internal extension B.C : A {}
One other thing I’d like to mention is this from the imported stdlib:

extension ErrorProtocol {
}
How on earth is this possible?

public protocol A {}

public extension A {
     
    internal func foo()
}
The imported module would look like this, and there is no empty extension:

public protocol A {}
Lets examine the impact on default protocol implementations:

Currently we have this behavior:

public protocol A {
    func foo()
}

extension A {
    func foo() { /* implement */ }  
}
The imported version would look like this.

public protocol A {
    public func foo()
}
As the module user you have no clue that there might be a default implementation, but you sill will be able to use it, because when conforming to A you don’t have to implement foo. This implicitly signals you that there is indeed a default implemenation

struct B : A {} // This will be enough

A().foo() // this is fine
One could signal the module user that there is a default implementation by making the extension explicit public as well.

// explicitly marked as public to grant visibility to  
// the default implementation extension bag
public extension A {
     
    /// will do something cool
    func foo() { /* implement */ }  
}
The result of the imported module would change and look like this:

public protocol A {
    public func foo()
}

extension A {
     
    /// will do something cool
    public func foo()
}
With the proposed change all default implementations will become visible by default and I think this is great step as well.

This will also allow us to hide default implementation for the public usage but still use it internally, which is kinda cool.

There’s really no such thing as an “implicitly public extension”. An extension is just a bag of additional members and conformances. An access modifier on the extension sets the default access level of members in the extension as a convenience.
You had me at this point, I misunderstood the behavior completely. I’ll have to rewrite the proposal to the mentioned behavior from above.

Yet I have no idea how complicated this change might be to implement, but I feel like this is a reasonable change.



-- 
Adrian Zubarev
Sent with Airmail

Am 27. Juni 2016 um 02:41:28, Jordan Rose (jordan_rose at apple.com) schrieb:

Hi, Adrian. Can you explain why you want to make this change? “public” on an extension doesn’t mean anything by itself because you can’t refer to an extension as an entity in and of itself. Access modifiers are disallowed on extensions with protocols because the conformance isn’t controlled by the access modifier and we didn’t want to give the impression that it would.

There’s really no such thing as an “implicitly public extension”. An extension is just a bag of additional members and conformances. An access modifier on the extension sets the default access level of members in the extension as a convenience.

Jordan
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160627/25b7a2b6/attachment.html>


More information about the swift-evolution mailing list