[Discussion] access control modifier inconsistency


(Adrian Zubarev) #1

I’ve spotted some inconsistency on access control modifier I’d like to discuss in this thread (the behavior is not groundbreaking but inconsistent from readers and developers view perspective).

Why are extensions not marked as public in public apis when the module is imported?

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.

Alternatively, you can mark an extension with an explicit access level modifier (for example, private extension) to set a new default access level for all members defined within the extension. This new default can still be overridden within the extension for individual type members.

Source: Apple Inc. The Swift Programming Language (Swift 2.2).
This does not tell us how the access control modifier works on extensions. Here are three examples:
public struct A {}
     
/* implicitly internal */ extension A {}
     
// This extension won't be exported
as soon as at least one member modifier of an extension is public (if the extended type allows that and is also public), the extension itself becomes implicitly public:
public struct B {}
     
/* implicitly public */ extension B {
         
    public func foo() {}
         
    /* implicitly internal */ func boo() {}
}
     
// This extension will be exported as
     
extension B {
         
    public func foo()
}
This is inconsistent to other types, because you can not leave out the modifier for instance on classes, then add a public member and assume that your class is implicitly public! The compiler knows that and provides a correct warning that the mentioned class is internal.

public struct C {}
     
public extension C {
         
    public func foo() {}
         
    /* implicitly internal */ func boo() {}
}
     
// This extension will be exported as
     
/* public is missing here */ extension C {
         
    public func foo()
}
Extensions seem to behave differently and its behaviors is inconsistent and not documented.

protocols has explicit public modifier on its members when the module is imported. However, 'public' modifier cannot be used in protocols is how the compiler treat modifiers in protocols on declaration.

public protocol D {
         
    func doSomething()
}
Protocol D will be exported as:

public protocol D {
         
    public func doSomething()
}
Here is my suggestion:

Fix the behavior for extensions and document it correctly:

public struct B {}
     
/* implicitly public */ extension B {
         
    public func foo() {}
         
    /* implicitly internal */ func boo() {}
}
Such an extension should not be implicitly public and behave like other types and stay implicitly internal.
The compiler should provide a warning for the foo() function: Declaring a public function for an internal type
Extensions should have explicit public modifier like other types if the extension was marked as public and was imported into an other project/module.
This inconsistent behavior is source breaking and I’d suggest this change to happen in Swift 3!
Remove public modifier from imported protocols OR document this behavior correctly!

···

--
Adrian Zubarev
Sent with Airmail


(Adrian Zubarev) #2

One problem I found with the mentioned suggestion of mine is this:

public protocol A {}

public class B {}

// 'public' modifier cannot be used with
// extensions that declare protocol conformances
public extension B: A {}
Why is that like this?
Can this be fixed?
Implicit public extension feels odd and is inconsistent if you ask me.
Any statement to the pitched suggestion?

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Juni 2016 um 11:28:18, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

I’ve spotted some inconsistency on access control modifier I’d like to discuss in this thread (the behavior is not groundbreaking but inconsistent from readers and developers view perspective).

Why are extensions not marked as public in public apis when the module is imported?

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.

Alternatively, you can mark an extension with an explicit access level modifier (for example, private extension) to set a new default access level for all members defined within the extension. This new default can still be overridden within the extension for individual type members.

Source: Apple Inc. The Swift Programming Language (Swift 2.2).
This does not tell us how the access control modifier works on extensions. Here are three examples:
public struct A {}
      
/* implicitly internal */ extension A {}
      
// This extension won't be exported
as soon as at least one member modifier of an extension is public (if the extended type allows that and is also public), the extension itself becomes implicitly public:
public struct B {}
      
/* implicitly public */ extension B {
          
    public func foo() {}
          
    /* implicitly internal */ func boo() {}
}
      
// This extension will be exported as
      
extension B {
          
    public func foo()
}
This is inconsistent to other types, because you can not leave out the modifier for instance on classes, then add a public member and assume that your class is implicitly public! The compiler knows that and provides a correct warning that the mentioned class is internal.

public struct C {}
      
public extension C {
          
    public func foo() {}
          
    /* implicitly internal */ func boo() {}
}
      
// This extension will be exported as
      
/* public is missing here */ extension C {
          
    public func foo()
}
Extensions seem to behave differently and its behaviors is inconsistent and not documented.

protocols has explicit public modifier on its members when the module is imported. However, 'public' modifier cannot be used in protocols is how the compiler treat modifiers in protocols on declaration.

public protocol D {
          
    func doSomething()
}
Protocol D will be exported as:

public protocol D {
          
    public func doSomething()
}
Here is my suggestion:

Fix the behavior for extensions and document it correctly:

public struct B {}
      
/* implicitly public */ extension B {
          
    public func foo() {}
          
    /* implicitly internal */ func boo() {}
}
Such an extension should not be implicitly public and behave like other types and stay implicitly internal.
The compiler should provide a warning for the foo() function: Declaring a public function for an internal type
Extensions should have explicit public modifier like other types if the extension was marked as public and was imported into an other project/module.
This inconsistent behavior is source breaking and I’d suggest this change to happen in Swift 3!
Remove public modifier from imported protocols OR document this behavior correctly!

--
Adrian Zubarev
Sent with Airmail


(Adrian Zubarev) #3

Everyone does feel comfortable with implicit public extensions?

···

--
Adrian Zubarev
Sent with Airmail

Am 19. Juni 2016 um 09:18:35, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

One problem I found with the mentioned suggestion of mine is this:

public protocol A {}

public class B {}

// 'public' modifier cannot be used with
// extensions that declare protocol conformances
public extension B: A {}
Why is that like this?
Can this be fixed?
Implicit public extension feels odd and is inconsistent if you ask me.
Any statement to the pitched suggestion?

--
Adrian Zubarev
Sent with Airmail

Am 17. Juni 2016 um 11:28:18, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

I’ve spotted some inconsistency on access control modifier I’d like to discuss in this thread (the behavior is not groundbreaking but inconsistent from readers and developers view perspective).

Why are extensions not marked as public in public apis when the module is imported?

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.

Alternatively, you can mark an extension with an explicit access level modifier (for example, private extension) to set a new default access level for all members defined within the extension. This new default can still be overridden within the extension for individual type members.

Source: Apple Inc. The Swift Programming Language (Swift 2.2).
This does not tell us how the access control modifier works on extensions. Here are three examples:
public struct A {}
       
/* implicitly internal */ extension A {}
       
// This extension won't be exported
as soon as at least one member modifier of an extension is public (if the extended type allows that and is also public), the extension itself becomes implicitly public:
public struct B {}
       
/* implicitly public */ extension B {
           
    public func foo() {}
           
    /* implicitly internal */ func boo() {}
}
       
// This extension will be exported as
       
extension B {
           
    public func foo()
}
This is inconsistent to other types, because you can not leave out the modifier for instance on classes, then add a public member and assume that your class is implicitly public! The compiler knows that and provides a correct warning that the mentioned class is internal.

public struct C {}
       
public extension C {
           
    public func foo() {}
           
    /* implicitly internal */ func boo() {}
}
       
// This extension will be exported as
       
/* public is missing here */ extension C {
           
    public func foo()
}
Extensions seem to behave differently and its behaviors is inconsistent and not documented.

protocols has explicit public modifier on its members when the module is imported. However, 'public' modifier cannot be used in protocols is how the compiler treat modifiers in protocols on declaration.

public protocol D {
           
    func doSomething()
}
Protocol D will be exported as:

public protocol D {
           
    public func doSomething()
}
Here is my suggestion:

Fix the behavior for extensions and document it correctly:

public struct B {}
       
/* implicitly public */ extension B {
           
    public func foo() {}
           
    /* implicitly internal */ func boo() {}
}
Such an extension should not be implicitly public and behave like other types and stay implicitly internal.
The compiler should provide a warning for the foo() function: Declaring a public function for an internal type
Extensions should have explicit public modifier like other types if the extension was marked as public and was imported into an other project/module.
This inconsistent behavior is source breaking and I’d suggest this change to happen in Swift 3!
Remove public modifier from imported protocols OR document this behavior correctly!

--
Adrian Zubarev
Sent with Airmail


(Gwynne Raskind) #4

I'd strongly favor correcting this inconsistency; I’ve run into it and been confused until I remembered the semantics more than once. I would much prefer always explicitly specifying the access modifier to receive its effect (versus changing the documentation) - in short I agree that extensions should be internal until declared otherwise.

-- Gwynne Raskind

···

On Jun 20, 2016, at 03:35, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Everyone does feel comfortable with implicit public extensions?

--
Adrian Zubarev
Sent with Airmail

Am 19. Juni 2016 um 09:18:35, Adrian Zubarev (adrian.zubarev@devandartist.com <mailto:adrian.zubarev@devandartist.com>) schrieb:

One problem I found with the mentioned suggestion of mine is this:

public protocol A {}

public class B {}

// 'public' modifier cannot be used with
// extensions that declare protocol conformances
public extension B: A {}
Why is that like this?
Can this be fixed?
Implicit public extension feels odd and is inconsistent if you ask me.
Any statement to the pitched suggestion?

--
Adrian Zubarev
Sent with Airmail

Am 17. Juni 2016 um 11:28:18, Adrian Zubarev (adrian.zubarev@devandartist.com <mailto:adrian.zubarev@devandartist.com>) schrieb:

I’ve spotted some inconsistency on access control modifier I’d like to discuss in this thread (the behavior is not groundbreaking but inconsistent from readers and developers view perspective).

Why are extensions not marked as public in public apis when the module is imported?

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.

Alternatively, you can mark an extension with an explicit access level modifier (for example, private extension) to set a new default access level for all members defined within the extension. This new default can still be overridden within the extension for individual type members.

Source: Apple Inc. The Swift Programming Language (Swift 2.2).
This does not tell us how the access control modifier works on extensions. Here are three examples:
public struct A {}
       
/* implicitly internal */ extension A {}
       
// This extension won't be exported
as soon as at least one member modifier of an extension is public (if the extended type allows that and is also public), the extension itself becomes implicitly public:
public struct B {}
       
/* implicitly public */ extension B {
           
    public func foo() {}
           
    /* implicitly internal */ func boo() {}
}
       
// This extension will be exported as
       
extension B {
           
    public func foo()
}
This is inconsistent to other types, because you can not leave out the modifier for instance on classes, then add a public member and assume that your class is implicitly public! The compiler knows that and provides a correct warning that the mentioned class is internal.

public struct C {}
       
public extension C {
           
    public func foo() {}
           
    /* implicitly internal */ func boo() {}
}
       
// This extension will be exported as
       
/* public is missing here */ extension C {
           
    public func foo()
}
Extensions seem to behave differently and its behaviors is inconsistent and not documented.

protocols has explicit public modifier on its members when the module is imported. However, 'public' modifier cannot be used in protocols is how the compiler treat modifiers in protocols on declaration.

public protocol D {
           
    func doSomething()
}
Protocol D will be exported as:

public protocol D {
           
    public func doSomething()
}
Here is my suggestion:

Fix the behavior for extensions and document it correctly:

public struct B {}
       
/* implicitly public */ extension B {
           
    public func foo() {}
           
    /* implicitly internal */ func boo() {}
}
Such an extension should not be implicitly public and behave like other types and stay implicitly internal.
The compiler should provide a warning for the foo() function: Declaring a public function for an internal type
Extensions should have explicit public modifier like other types if the extension was marked as public and was imported into an other project/module.
This inconsistent behavior is source breaking and I’d suggest this change to happen in Swift 3!
Remove public modifier from imported protocols OR document this behavior correctly!

--
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


(Adrian Zubarev) #5

Access control modifier: public > internal == /* implicitly internal */ > fileprivate >= private

//===== TOPLEVEL / File - Scope

//===== some protocols

public protocol A {
     
    func aa()
}

private protocol B {
     
    func bb()
}

fileprivate protocol C {
     
    func cc()
}

internal protocol D {
     
    func dd()
}

/* implicitly internal */ protocol E {
     
    func ee()
}

//===== some types

public struct FF {}

private struct GG {}

fileprivate struct HH {}

internal struct II {}

/* implicitly internal */ struct JJ {}

//===== some extensions

// We should be able to extend it with all protocols: A, B, C, D, E
// BUT it depends whether all protocols are visible at this scope
// In this example they are visible!
//
// The compiler should force you to write modifier for: A, B and C
// `internal` is always optional

public extension FF: A, B, C, D, E {

    public func aa()
     
    // Visible in this scope only
    private func bb()
     
    // Visible in this file
    fileprivate func cc()
     
    // Visible for the whole module
    // We do not need to write out `internal` here
    /* implicitly internal */ func dd()
     
    // visible for the whole module
    /* implicitly internal */ func ee()
}

// At top-level `private` is like `fileprivate`
private extension FF: A, B, C, D, E {
     
    // Same as in `public extension`
    // Except everything is `private` (or `fileprivate`)
}

fileprivate extension FF: A, B, C, D, E {
     
    // Same as in `public extension`
    // Except everything is `fileprivate`
}

internal extension FF: A, B, C, D, E {
     
    // Same as in `public extension`
    // Except everything is `internal` or explicitly `private` or `fileprivate`
}

/* implicitly internal */ extension FF: A, B, C, D, E {
     
    // Same as in `internal extension`
}

// For GG, HH, II and JJ we cannot extend these types with a `public` modifier!
// GG and HH can't be extended with `internal` modifier
If I missed anything, please let me know.

Protocols should not import an access modifier on their members, because this is a documented rule that we use the modifier from protocols definition.

public protocol A {
    var someMember: Type {}
}

// SHOULD NOT LOOK LIKE THIS:
public protocol A {
    public var someMember: Type {}
}

// CORRECT VERSION:
public protocol A {
var someMember: Type {}
}

···

--
Adrian Zubarev
Sent with Airmail

Am 20. Juni 2016 um 10:35:59, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Everyone does feel comfortable with implicit public extensions?

--
Adrian Zubarev
Sent with Airmail

Am 19. Juni 2016 um 09:18:35, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

One problem I found with the mentioned suggestion of mine is this:

public protocol A {}

public class B {}

// 'public' modifier cannot be used with
// extensions that declare protocol conformances
public extension B: A {}
Why is that like this?
Can this be fixed?
Implicit public extension feels odd and is inconsistent if you ask me.
Any statement to the pitched suggestion?

--
Adrian Zubarev
Sent with Airmail

Am 17. Juni 2016 um 11:28:18, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

I’ve spotted some inconsistency on access control modifier I’d like to discuss in this thread (the behavior is not groundbreaking but inconsistent from readers and developers view perspective).

Why are extensions not marked as public in public apis when the module is imported?

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.

Alternatively, you can mark an extension with an explicit access level modifier (for example, private extension) to set a new default access level for all members defined within the extension. This new default can still be overridden within the extension for individual type members.

Source: Apple Inc. The Swift Programming Language (Swift 2.2).
This does not tell us how the access control modifier works on extensions. Here are three examples:
public struct A {}
        
/* implicitly internal */ extension A {}
        
// This extension won't be exported
as soon as at least one member modifier of an extension is public (if the extended type allows that and is also public), the extension itself becomes implicitly public:
public struct B {}
        
/* implicitly public */ extension B {
            
    public func foo() {}
            
    /* implicitly internal */ func boo() {}
}
        
// This extension will be exported as
        
extension B {
            
    public func foo()
}
This is inconsistent to other types, because you can not leave out the modifier for instance on classes, then add a public member and assume that your class is implicitly public! The compiler knows that and provides a correct warning that the mentioned class is internal.

public struct C {}
        
public extension C {
            
    public func foo() {}
            
    /* implicitly internal */ func boo() {}
}
        
// This extension will be exported as
        
/* public is missing here */ extension C {
            
    public func foo()
}
Extensions seem to behave differently and its behaviors is inconsistent and not documented.

protocols has explicit public modifier on its members when the module is imported. However, 'public' modifier cannot be used in protocols is how the compiler treat modifiers in protocols on declaration.

public protocol D {
            
    func doSomething()
}
Protocol D will be exported as:

public protocol D {
            
    public func doSomething()
}
Here is my suggestion:

Fix the behavior for extensions and document it correctly:

public struct B {}
        
/* implicitly public */ extension B {
            
    public func foo() {}
            
    /* implicitly internal */ func boo() {}
}
Such an extension should not be implicitly public and behave like other types and stay implicitly internal.
The compiler should provide a warning for the foo() function: Declaring a public function for an internal type
Extensions should have explicit public modifier like other types if the extension was marked as public and was imported into an other project/module.
This inconsistent behavior is source breaking and I’d suggest this change to happen in Swift 3!
Remove public modifier from imported protocols OR document this behavior correctly!

--
Adrian Zubarev
Sent with Airmail


(L Mihalkovic) #6

Everyone does feel comfortable with implicit public extensions?

--
Adrian Zubarev
Sent with Airmail

Am 19. Juni 2016 um 09:18:35, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

One problem I found with the mentioned suggestion of mine is this:

public protocol A {}

public class B {}

// 'public' modifier cannot be used with
// extensions that declare protocol conformances
public extension B: A {}
Why is that like this?
Can this be fixed?
Implicit public extension feels odd and is inconsistent if you ask me.

Doug explained some of the compiler internals as related to protocol conformance on protocols (particularly why private conformance is at this point unlikely). Reading it might give more perspective to the current state. I'm sorry I cannot point to his post exactly, but it did happen in the last 3 weeks.

···

On Jun 20, 2016, at 10:35 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Any statement to the pitched suggestion?

--
Adrian Zubarev
Sent with Airmail

Am 17. Juni 2016 um 11:28:18, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

I’ve spotted some inconsistency on access control modifier I’d like to discuss in this thread (the behavior is not groundbreaking but inconsistent from readers and developers view perspective).

Why are extensions not marked as public in public apis when the module is imported?

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.

Alternatively, you can mark an extension with an explicit access level modifier (for example, private extension) to set a new default access level for all members defined within the extension. This new default can still be overridden within the extension for individual type members.

Source: Apple Inc. The Swift Programming Language (Swift 2.2).
This does not tell us how the access control modifier works on extensions. Here are three examples:
public struct A {}
       
/* implicitly internal */ extension A {}
       
// This extension won't be exported
as soon as at least one member modifier of an extension is public (if the extended type allows that and is also public), the extension itself becomes implicitly public:
public struct B {}
       
/* implicitly public */ extension B {
           
    public func foo() {}
           
    /* implicitly internal */ func boo() {}
}
       
// This extension will be exported as
       
extension B {
           
    public func foo()
}
This is inconsistent to other types, because you can not leave out the modifier for instance on classes, then add a public member and assume that your class is implicitly public! The compiler knows that and provides a correct warning that the mentioned class is internal.

public struct C {}
       
public extension C {
           
    public func foo() {}
           
    /* implicitly internal */ func boo() {}
}
       
// This extension will be exported as
       
/* public is missing here */ extension C {
           
    public func foo()
}
Extensions seem to behave differently and its behaviors is inconsistent and not documented.

protocols has explicit public modifier on its members when the module is imported. However, 'public' modifier cannot be used in protocols is how the compiler treat modifiers in protocols on declaration.

public protocol D {
           
    func doSomething()
}
Protocol D will be exported as:

public protocol D {
           
    public func doSomething()
}
Here is my suggestion:

Fix the behavior for extensions and document it correctly:

public struct B {}
       
/* implicitly public */ extension B {
           
    public func foo() {}
           
    /* implicitly internal */ func boo() {}
}
Such an extension should not be implicitly public and behave like other types and stay implicitly internal.
The compiler should provide a warning for the foo() function: Declaring a public function for an internal type
Extensions should have explicit public modifier like other types if the extension was marked as public and was imported into an other project/module.
This inconsistent behavior is source breaking and I’d suggest this change to happen in Swift 3!
Remove public modifier from imported protocols OR document this behavior correctly!

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution