Replace Fileprivate with Hidden + Import Hidden


(Jon Hull) #1

I keep wanting a “protected” access level, but I must also admit that there was something really elegant about Swift 2’s access scheme (and I think most of us feel like the word ‘fileprivate’ feels out of place). I was thinking about how to mesh those two ideas, and I think I may have come up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level. Hidden would work exactly the same way as fileprivate does now, but adds the connotation that what is hidden can also be unhidden. By adding ‘import hidden TypeName’ to another file, that file also gains access to all of the hidden items of that type (kind of like if it was in the same file).

#FileA
  import Foundation

  Struct A {
    private var x:Int
    hidden var y:Int //This is just like fileprivate, but can also be shared with other files
  }

  extension A {
    //y can be accessed here because they are in the same file
  }

#FileB
  import Foundation
  import hidden A //This allows the entire file to see A’s hidden variables

  extension A {
    //y can be accessed here because of the ‘import hidden’ declaration
  }

#FileC
  import Foundation

  extension A {
    //y can NOT be seen or accessed here because it is hidden
  }

I think this is a fairly elegant solution to our protected dilemma, which also feels in sync with Swift 2’s file-based scheme. The key features:
  • Extensions no longer need to be piled in the same file if it is getting too long
  • Subclasses can be in their own file, but still have access to the necessary parts of their superclass
  • It communicates the author’s intent that the items are not meant to be visible to its users, but that it is expected to be used for extension/subclassing
  • It requires an explicit statement ‘import hidden’ to access the hidden variables. Safe by default, with override.
  • It is not bound by module boundaries (i.e. you could use it for subclassing classes from an imported module)
  • Roughly the same length as ‘private’ and ‘public’ so various declarations packed together are much easier to read (fileprivate breaks reading rhythm)

Worth a formal proposal?

Thanks,
Jon


(TJ Usiyan) #2

I don't like this at all and it comes down to "what is hidden can also be
unhidden". This, to me, feels like it would create more confusion than it
would address. Why not just use `internal` for `hidden` items? If we're ok
with modifying import statements, why not simply have a command that
imports `fileprivate` stuff? (not advocating for this).

I think that submodules would have really helped with this issue and it is
unfortunate that we couldn't get them in for swift 3.

···

On Sun, Oct 16, 2016 at 4:34 PM, Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

I keep wanting a “protected” access level, but I must also admit that
there was something really elegant about Swift 2’s access scheme (and I
think most of us feel like the word ‘fileprivate’ feels out of place). I
was thinking about how to mesh those two ideas, and I think I may have come
up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level.
Hidden would work exactly the same way as fileprivate does now, but adds
the connotation that what is hidden can also be unhidden. By adding
‘import hidden TypeName’ to another file, that file also gains access to
all of the hidden items of that type (kind of like if it was in the same
file).

#FileA
        import Foundation

        Struct A {
                private var x:Int
                hidden var y:Int //This is just like fileprivate, but can
also be shared with other files
        }

        extension A {
                //y can be accessed here because they are in the same file
        }

#FileB
        import Foundation
        import hidden A //This allows the entire file to see A’s hidden
variables

        extension A {
                //y can be accessed here because of the ‘import hidden’
declaration
        }

#FileC
        import Foundation

        extension A {
                //y can NOT be seen or accessed here because it is hidden
        }

I think this is a fairly elegant solution to our protected dilemma, which
also feels in sync with Swift 2’s file-based scheme. The key features:
        • Extensions no longer need to be piled in the same file if it is
getting too long
        • Subclasses can be in their own file, but still have access to
the necessary parts of their superclass
        • It communicates the author’s intent that the items are not meant
to be visible to its users, but that it is expected to be used for
extension/subclassing
        • It requires an explicit statement ‘import hidden’ to access the
hidden variables. Safe by default, with override.
        • It is not bound by module boundaries (i.e. you could use it for
subclassing classes from an imported module)
        • Roughly the same length as ‘private’ and ‘public’ so various
declarations packed together are much easier to read (fileprivate breaks
reading rhythm)

Worth a formal proposal?

Thanks,
Jon

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


(David Waite) #3

fileprivate (and internal, in a sense) are really in the “friend” camp. Rather than declaring particular types are friends, these require code locality - in the same file in the case of fileprivate, in the same module in the case of internal.

This does mean that the more stringent you wish to control internals, the more code gets stuffed into a file or module - but could actually be considered a benefit, as depending on internals naturally makes code highly coupled.

That reason is why I’m not exactly fan of expanding ‘friend’-style permissions further - this could completely disguise the level of effort needed to change ‘hidden’ code in one class.

I’m against allowing hidden cross-module. If external code is explicitly allowed to depend on ‘hidden’ API, then its still API which would be expected to be stably maintained. Why not just make it public with appropriate caveats?

-DW

···

On Oct 16, 2016, at 2:34 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I keep wanting a “protected” access level, but I must also admit that there was something really elegant about Swift 2’s access scheme (and I think most of us feel like the word ‘fileprivate’ feels out of place). I was thinking about how to mesh those two ideas, and I think I may have come up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level. Hidden would work exactly the same way as fileprivate does now, but adds the connotation that what is hidden can also be unhidden. By adding ‘import hidden TypeName’ to another file, that file also gains access to all of the hidden items of that type (kind of like if it was in the same file).

#FileA
  import Foundation

  Struct A {
    private var x:Int
    hidden var y:Int //This is just like fileprivate, but can also be shared with other files
  }

  extension A {
    //y can be accessed here because they are in the same file
  }

#FileB
  import Foundation
  import hidden A //This allows the entire file to see A’s hidden variables

  extension A {
    //y can be accessed here because of the ‘import hidden’ declaration
  }

#FileC
  import Foundation

  extension A {
    //y can NOT be seen or accessed here because it is hidden
  }

I think this is a fairly elegant solution to our protected dilemma, which also feels in sync with Swift 2’s file-based scheme. The key features:
  • Extensions no longer need to be piled in the same file if it is getting too long
  • Subclasses can be in their own file, but still have access to the necessary parts of their superclass
  • It communicates the author’s intent that the items are not meant to be visible to its users, but that it is expected to be used for extension/subclassing
  • It requires an explicit statement ‘import hidden’ to access the hidden variables. Safe by default, with override.
  • It is not bound by module boundaries (i.e. you could use it for subclassing classes from an imported module)
  • Roughly the same length as ‘private’ and ‘public’ so various declarations packed together are much easier to read (fileprivate breaks reading rhythm)

Worth a formal proposal?

Thanks,
Jon

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


(Rien) #4

-1.

If an API designer wants to allow access to a “hidden’ member, he should be in control of that access.
The proposed chance simply opens up access completely and leads to API vulnerability.

(I am in favour of a “friend” solution, in which the API designer explicitly allows access to identified members of the API users.)

Rien.

···

On 16 Oct 2016, at 22:34, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I keep wanting a “protected” access level, but I must also admit that there was something really elegant about Swift 2’s access scheme (and I think most of us feel like the word ‘fileprivate’ feels out of place). I was thinking about how to mesh those two ideas, and I think I may have come up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level. Hidden would work exactly the same way as fileprivate does now, but adds the connotation that what is hidden can also be unhidden. By adding ‘import hidden TypeName’ to another file, that file also gains access to all of the hidden items of that type (kind of like if it was in the same file).

#FileA
  import Foundation

  Struct A {
    private var x:Int
    hidden var y:Int //This is just like fileprivate, but can also be shared with other files
  }

  extension A {
    //y can be accessed here because they are in the same file
  }

#FileB
  import Foundation
  import hidden A //This allows the entire file to see A’s hidden variables

  extension A {
    //y can be accessed here because of the ‘import hidden’ declaration
  }

#FileC
  import Foundation

  extension A {
    //y can NOT be seen or accessed here because it is hidden
  }

I think this is a fairly elegant solution to our protected dilemma, which also feels in sync with Swift 2’s file-based scheme. The key features:
  • Extensions no longer need to be piled in the same file if it is getting too long
  • Subclasses can be in their own file, but still have access to the necessary parts of their superclass
  • It communicates the author’s intent that the items are not meant to be visible to its users, but that it is expected to be used for extension/subclassing
  • It requires an explicit statement ‘import hidden’ to access the hidden variables. Safe by default, with override.
  • It is not bound by module boundaries (i.e. you could use it for subclassing classes from an imported module)
  • Roughly the same length as ‘private’ and ‘public’ so various declarations packed together are much easier to read (fileprivate breaks reading rhythm)

Worth a formal proposal?

Thanks,
Jon

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


(Jon Hull) #5

I don't like this at all and it comes down to "what is hidden can also be unhidden". This, to me, feels like it would create more confusion than it would address.

I don’t think it would cause confusion. Other languages have things like protected, which is currently a glaring hole in Swift’s access control. This addresses that without any of the complexity of friend classes, etc….

Why not just use `internal` for `hidden` items?

Because there is an important aspect of communicating the author’s intent.

I think it is important to be able to mark things which are not meant to be public (and internal is essentially public in the main module), but may be required by extensions/subclasses. With internal, it would be easy to accidentally create tightly coupled structures. With ‘hidden', it requires intentional annotation (via the ‘include hidden’ statement) to gain access, so more thought will be given to the coupling.

Also, internal would prevent extensions/subclasses outside the defining module. With ‘hidden’ you have an author approved way of extending a type (even from another module). I can’t actually think of a single use of ‘fileprivate’ where my intent/need was something other than allowing extensions. Unfortunately, with the current model those files can get quite a bit bigger than is ideal from an organizational standpoint.

If we're ok with modifying import statements, why not simply have a command that imports `fileprivate` stuff? (not advocating for this).

From a technical perspective, that is essentially what I am proposing, but terminology matters. The word fileprivate was chosen to convey that the associated item was private to scope of the file. With this, we need to convey a slightly different intent because, while it does open access at file granularity, it is no longer private to the scope of that single file, and the name would be misleading.

It doesn’t need to be the word ‘hidden’ necessarily, but it should be something with the correct connotation for the new behavior.

I think that submodules would have really helped with this issue and it is unfortunate that we couldn't get them in for swift 3.

I also want submodules, but they won’t fully solve the protected problem above. They would allow you to group KNOWN subclasses and extensions into a single submodule. But they would also prevent further extensions and subclasses from being made by the consumer of that module. If that is what you want, then submodules are the answer, but there are also lots of cases where an author wants to allow careful subclassing outside of the original module.

A good example of this is UIGestureRecognizer, which separates its protected items into a different include. It hides all of the dangerous properties/methods in that separate file, and 98% of the time you don’t need it. However, if you want to create your own gesture recognizer, you can’t really do it without those dangerous methods… thus you include the file. With the submodule approach, you would either be unable to create your own gesture recognizer subclasses or the dangerous methods would be made available inappropriately.

Thanks,
Jon

···

On Oct 16, 2016, at 3:28 PM, T.J. Usiyan <griotspeak@gmail.com> wrote:

On Sun, Oct 16, 2016 at 4:34 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I keep wanting a “protected” access level, but I must also admit that there was something really elegant about Swift 2’s access scheme (and I think most of us feel like the word ‘fileprivate’ feels out of place). I was thinking about how to mesh those two ideas, and I think I may have come up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level. Hidden would work exactly the same way as fileprivate does now, but adds the connotation that what is hidden can also be unhidden. By adding ‘import hidden TypeName’ to another file, that file also gains access to all of the hidden items of that type (kind of like if it was in the same file).

#FileA
        import Foundation

        Struct A {
                private var x:Int
                hidden var y:Int //This is just like fileprivate, but can also be shared with other files
        }

        extension A {
                //y can be accessed here because they are in the same file
        }

#FileB
        import Foundation
        import hidden A //This allows the entire file to see A’s hidden variables

        extension A {
                //y can be accessed here because of the ‘import hidden’ declaration
        }

#FileC
        import Foundation

        extension A {
                //y can NOT be seen or accessed here because it is hidden
        }

I think this is a fairly elegant solution to our protected dilemma, which also feels in sync with Swift 2’s file-based scheme. The key features:
        • Extensions no longer need to be piled in the same file if it is getting too long
        • Subclasses can be in their own file, but still have access to the necessary parts of their superclass
        • It communicates the author’s intent that the items are not meant to be visible to its users, but that it is expected to be used for extension/subclassing
        • It requires an explicit statement ‘import hidden’ to access the hidden variables. Safe by default, with override.
        • It is not bound by module boundaries (i.e. you could use it for subclassing classes from an imported module)
        • Roughly the same length as ‘private’ and ‘public’ so various declarations packed together are much easier to read (fileprivate breaks reading rhythm)

Worth a formal proposal?

Thanks,
Jon

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


(Jon Hull) #6

To put it more forcefully, I believe that ‘fileprivate’ currently suffers from the same problem that singletons do. That is, the main reason to make something ‘fileprivate’ instead of ‘private’ is to allow some sort of extension. It is highly unlikely that the point you were required to expose for that extension will not also be needed by other extensions. Both ‘internal’ and submodules would extend the boundaries of the problem from the file boundary to the module/submodule boundary… but the underlying problem still persists.

If it is the module designer’s intent to limit extension, then fine, but it also often forces a design to be much more brittle and fragile than it needs to be. One of two things happens: extension becomes impossible or there is access inflation, giving inappropriate levels of access (usually combined with a note in the documentation saying not to use it).

Thanks,
Jon

···

On Oct 16, 2016, at 3:28 PM, T.J. Usiyan <griotspeak@gmail.com> wrote:

I don't like this at all and it comes down to "what is hidden can also be unhidden". This, to me, feels like it would create more confusion than it would address. Why not just use `internal` for `hidden` items? If we're ok with modifying import statements, why not simply have a command that imports `fileprivate` stuff? (not advocating for this).

I think that submodules would have really helped with this issue and it is unfortunate that we couldn't get them in for swift 3.

On Sun, Oct 16, 2016 at 4:34 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I keep wanting a “protected” access level, but I must also admit that there was something really elegant about Swift 2’s access scheme (and I think most of us feel like the word ‘fileprivate’ feels out of place). I was thinking about how to mesh those two ideas, and I think I may have come up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level. Hidden would work exactly the same way as fileprivate does now, but adds the connotation that what is hidden can also be unhidden. By adding ‘import hidden TypeName’ to another file, that file also gains access to all of the hidden items of that type (kind of like if it was in the same file).

#FileA
        import Foundation

        Struct A {
                private var x:Int
                hidden var y:Int //This is just like fileprivate, but can also be shared with other files
        }

        extension A {
                //y can be accessed here because they are in the same file
        }

#FileB
        import Foundation
        import hidden A //This allows the entire file to see A’s hidden variables

        extension A {
                //y can be accessed here because of the ‘import hidden’ declaration
        }

#FileC
        import Foundation

        extension A {
                //y can NOT be seen or accessed here because it is hidden
        }

I think this is a fairly elegant solution to our protected dilemma, which also feels in sync with Swift 2’s file-based scheme. The key features:
        • Extensions no longer need to be piled in the same file if it is getting too long
        • Subclasses can be in their own file, but still have access to the necessary parts of their superclass
        • It communicates the author’s intent that the items are not meant to be visible to its users, but that it is expected to be used for extension/subclassing
        • It requires an explicit statement ‘import hidden’ to access the hidden variables. Safe by default, with override.
        • It is not bound by module boundaries (i.e. you could use it for subclassing classes from an imported module)
        • Roughly the same length as ‘private’ and ‘public’ so various declarations packed together are much easier to read (fileprivate breaks reading rhythm)

Worth a formal proposal?

Thanks,
Jon

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


(Jon Hull) #7

fileprivate (and internal, in a sense) are really in the “friend” camp. Rather than declaring particular types are friends, these require code locality - in the same file in the case of fileprivate, in the same module in the case of internal.

This does mean that the more stringent you wish to control internals, the more code gets stuffed into a file or module - but could actually be considered a benefit, as depending on internals naturally makes code highly coupled.

That reason is why I’m not exactly fan of expanding ‘friend’-style permissions further - this could completely disguise the level of effort needed to change ‘hidden’ code in one class.

I think that is a valid point. There is a tradeoff. Being able to spread an API over more files means that there are more files to change when you need to make a change (and there is always a chance that you forget a file). My argument would be that this allows the author to choose the tradeoff for themselves in each situation, whereas right now the choice is being made for them.

I’m against allowing hidden cross-module. If external code is explicitly allowed to depend on ‘hidden’ API, then its still API which would be expected to be stably maintained. Why not just make it public with appropriate caveats?

Yes, I think it would be expected to be stably maintained… but any sort of protected-style access is going to be the same, as you need a stable API to build subclasses/extensions on. There are several reasons for encapsulation beyond making it easier to update API. My main reason for wanting this is to be able to hide the levers which could get things into an inconsistent state internally, while still allowing for the few cases where that access is needed for efficient implementation. Safe by default, with override.

I do suppose, at the expense of a little more complexity, we could make ‘hidden' a separate axis of control. That is, you could have ‘public hidden’, ‘internal hidden’, and ‘private hidden’, with private hidden being exactly the same as today’s fileprivate (C# has protected internal, etc… so it isn’t unprecedented). That way, an author could choose the tradeoff between maintenance burden and extensibility.

Thanks,
Jon

···

On Oct 16, 2016, at 8:19 PM, David Waite <david@alkaline-solutions.com> wrote:

-DW

On Oct 16, 2016, at 2:34 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I keep wanting a “protected” access level, but I must also admit that there was something really elegant about Swift 2’s access scheme (and I think most of us feel like the word ‘fileprivate’ feels out of place). I was thinking about how to mesh those two ideas, and I think I may have come up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level. Hidden would work exactly the same way as fileprivate does now, but adds the connotation that what is hidden can also be unhidden. By adding ‘import hidden TypeName’ to another file, that file also gains access to all of the hidden items of that type (kind of like if it was in the same file).

#FileA
  import Foundation

  Struct A {
    private var x:Int
    hidden var y:Int //This is just like fileprivate, but can also be shared with other files
  }

  extension A {
    //y can be accessed here because they are in the same file
  }

#FileB
  import Foundation
  import hidden A //This allows the entire file to see A’s hidden variables

  extension A {
    //y can be accessed here because of the ‘import hidden’ declaration
  }

#FileC
  import Foundation

  extension A {
    //y can NOT be seen or accessed here because it is hidden
  }

I think this is a fairly elegant solution to our protected dilemma, which also feels in sync with Swift 2’s file-based scheme. The key features:
  • Extensions no longer need to be piled in the same file if it is getting too long
  • Subclasses can be in their own file, but still have access to the necessary parts of their superclass
  • It communicates the author’s intent that the items are not meant to be visible to its users, but that it is expected to be used for extension/subclassing
  • It requires an explicit statement ‘import hidden’ to access the hidden variables. Safe by default, with override.
  • It is not bound by module boundaries (i.e. you could use it for subclassing classes from an imported module)
  • Roughly the same length as ‘private’ and ‘public’ so various declarations packed together are much easier to read (fileprivate breaks reading rhythm)

Worth a formal proposal?

Thanks,
Jon

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


(Jon Hull) #8

First, let me say, thank you for your feedback. Just because I am arguing against others' points does not mean that I don’t appreciate those points.

-1.

If an API designer wants to allow access to a “hidden’ member, he should be in control of that access.

This seems to be one of the biggest internal arguments within the swift community. Who is in control of the use of frameworks? The author or the user?

I am trying to plot a middle path here which gives both a fair amount of control. I think that is part of why this proposal has been so unpopular, it is a compromise between both extremes, and everyone hates a compromise. Someone else just had the opinion that we should make everything public and note in the documentation what should and should not be used.

And as I said before, API like UIGestureRecognizer's is currently impossible to create solely in Swift. There is a hole in Swift’s access system which we will have to fix sometime during this phase. I think a lot of people think that submodules will magically solve all of our problems, (I want them too) but the core issue here will remain (just with a little less frequency), and we will be forced to go back to the drawing board in 6mo to a year. I am hoping to prevent that.

The proposed chance simply opens up access completely and leads to API vulnerability.

No, it only opens up access to API explicitly marked with ‘hidden’. That is, the framework author is explicitly saying: This API should only be used for subclassing/extension (but they also realize it can be accessed). The user of that API has to explicitly acknowledge that intention by writing ‘import hidden’. API marked private or internal will not be opened beyond it’s scope in any case.

As I said in another email, a potential further compromise would be to make ‘hidden’ a separate axis, and allow things like ‘internal hidden’, where access can only be opened up within the module. Would that solve your issues with the proposal?

I started out closer to your position, wanting more targeted permissions, but I ultimately realized that this would be good enough for real world use.

(I am in favour of a “friend” solution, in which the API designer explicitly allows access to identified members of the API users.)

But what happens when you need to be a friend and you don’t own the API? I get the thinking behind friend classes, but it is problematic in practice. As another poster mentioned, we basically have friend classes already, just based on location of code. The core issue I am trying to solve here cannot be solved with more variation of the same, we need a counterpoint to it, and then the two approaches can be mixed to get the desired balance/tradeoffs.

Thanks,
Jon

···

On Oct 17, 2016, at 12:37 AM, Rien <Rien@Balancingrock.nl> wrote:

Rien.

On 16 Oct 2016, at 22:34, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I keep wanting a “protected” access level, but I must also admit that there was something really elegant about Swift 2’s access scheme (and I think most of us feel like the word ‘fileprivate’ feels out of place). I was thinking about how to mesh those two ideas, and I think I may have come up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level. Hidden would work exactly the same way as fileprivate does now, but adds the connotation that what is hidden can also be unhidden. By adding ‘import hidden TypeName’ to another file, that file also gains access to all of the hidden items of that type (kind of like if it was in the same file).

#FileA
  import Foundation

  Struct A {
    private var x:Int
    hidden var y:Int //This is just like fileprivate, but can also be shared with other files
  }

  extension A {
    //y can be accessed here because they are in the same file
  }

#FileB
  import Foundation
  import hidden A //This allows the entire file to see A’s hidden variables

  extension A {
    //y can be accessed here because of the ‘import hidden’ declaration
  }

#FileC
  import Foundation

  extension A {
    //y can NOT be seen or accessed here because it is hidden
  }

I think this is a fairly elegant solution to our protected dilemma, which also feels in sync with Swift 2’s file-based scheme. The key features:
  • Extensions no longer need to be piled in the same file if it is getting too long
  • Subclasses can be in their own file, but still have access to the necessary parts of their superclass
  • It communicates the author’s intent that the items are not meant to be visible to its users, but that it is expected to be used for extension/subclassing
  • It requires an explicit statement ‘import hidden’ to access the hidden variables. Safe by default, with override.
  • It is not bound by module boundaries (i.e. you could use it for subclassing classes from an imported module)
  • Roughly the same length as ‘private’ and ‘public’ so various declarations packed together are much easier to read (fileprivate breaks reading rhythm)

Worth a formal proposal?

Thanks,
Jon

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


(Xiaodi Wu) #9

No need to rename fileprivate; it's irrelevant to your use case and is
resurrecting a bikeshedding discussion that really needs to be put to rest.

The proposal is essentially for something like @testable for fileprivate
members. Sounds fine to me.

···

On Mon, Oct 17, 2016 at 09:35 Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

On Oct 16, 2016, at 3:28 PM, T.J. Usiyan <griotspeak@gmail.com> wrote:

I don't like this at all and it comes down to "what is hidden can also
be unhidden". This, to me, feels like it would create more confusion than
it would address.

I don’t think it would cause confusion. Other languages have things like
protected, which is currently a glaring hole in Swift’s access control.
This addresses that without any of the complexity of friend classes, etc….

Why not just use `internal` for `hidden` items?

Because there is an important aspect of communicating the author’s intent.

I think it is important to be able to mark things which are not meant to
be public (and internal is essentially public in the main module), but may
be required by extensions/subclasses. With internal, it would be easy to
accidentally create tightly coupled structures. With ‘hidden', it requires
intentional annotation (via the ‘include hidden’ statement) to gain access,
so more thought will be given to the coupling.

Also, internal would prevent extensions/subclasses outside the defining
module. With ‘hidden’ you have an author approved way of extending a type
(even from another module). I can’t actually think of a single use of
‘fileprivate’ where my intent/need was something other than allowing
extensions. Unfortunately, with the current model those files can get quite
a bit bigger than is ideal from an organizational standpoint.

If we're ok with modifying import statements, why not simply have a
command that imports `fileprivate` stuff? (not advocating for this).

From a technical perspective, that is essentially what I am proposing, but
terminology matters. The word fileprivate was chosen to convey that the
associated item was private to scope of the file. With this, we need to
convey a slightly different intent because, while it does open access at
file granularity, it is no longer private to the scope of that single file,
and the name would be misleading.

It doesn’t need to be the word ‘hidden’ necessarily, but it should be
something with the correct connotation for the new behavior.

I think that submodules would have really helped with this issue and it is
unfortunate that we couldn't get them in for swift 3.

I also want submodules, but they won’t fully solve the protected problem
above. They would allow you to group KNOWN subclasses and extensions into
a single submodule. But they would also prevent further extensions and
subclasses from being made by the consumer of that module. If that is what
you want, then submodules are the answer, but there are also lots of cases
where an author wants to allow careful subclassing outside of the original
module.

A good example of this is UIGestureRecognizer, which separates its
protected items into a different include. It hides all of the dangerous
properties/methods in that separate file, and 98% of the time you don’t
need it. However, if you want to create your own gesture recognizer, you
can’t really do it without those dangerous methods… thus you include the
file. With the submodule approach, you would either be unable to create
your own gesture recognizer subclasses or the dangerous methods would be
made available inappropriately.

Thanks,
Jon

On Sun, Oct 16, 2016 at 4:34 PM, Jonathan Hull via swift-evolution < > swift-evolution@swift.org> wrote:

I keep wanting a “protected” access level, but I must also admit that
there was something really elegant about Swift 2’s access scheme (and I
think most of us feel like the word ‘fileprivate’ feels out of place). I
was thinking about how to mesh those two ideas, and I think I may have come
up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level.
Hidden would work exactly the same way as fileprivate does now, but adds
the connotation that what is hidden can also be unhidden. By adding
‘import hidden TypeName’ to another file, that file also gains access to
all of the hidden items of that type (kind of like if it was in the same
file).

#FileA
        import Foundation

        Struct A {
                private var x:Int
                hidden var y:Int //This is just like fileprivate, but can
also be shared with other files
        }

        extension A {
                //y can be accessed here because they are in the same file
        }

#FileB
        import Foundation
        import hidden A //This allows the entire file to see A’s hidden
variables

        extension A {
                //y can be accessed here because of the ‘import hidden’
declaration
        }

#FileC
        import Foundation

        extension A {
                //y can NOT be seen or accessed here because it is hidden
        }

I think this is a fairly elegant solution to our protected dilemma, which
also feels in sync with Swift 2’s file-based scheme. The key features:
        • Extensions no longer need to be piled in the same file if it is
getting too long
        • Subclasses can be in their own file, but still have access to
the necessary parts of their superclass
        • It communicates the author’s intent that the items are not meant
to be visible to its users, but that it is expected to be used for
extension/subclassing
        • It requires an explicit statement ‘import hidden’ to access the
hidden variables. Safe by default, with override.
        • It is not bound by module boundaries (i.e. you could use it for
subclassing classes from an imported module)
        • Roughly the same length as ‘private’ and ‘public’ so various
declarations packed together are much easier to read (fileprivate breaks
reading rhythm)

Worth a formal proposal?

Thanks,
Jon

_______________________________________________
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


(Xiaodi Wu) #10

On that last point, I would push back against calling it access inflation.
If a member needs to be visible outside the file for whatever reason, it
needs to have at least internal visibility. This is not at all "inflated"
(implying that it's a workaround) but an explicitly contemplated part of
Swift's access scheme.

What you're arguing is that we should be able to expressly enumerate the
specific files in which a member is accessible. This is a more granular
system, but increased granularity is not in and of itself sufficient
justification. After all, if more granularity is better--and this is an
argumentum ad absurdum--why not demand that we be able to expressly
enumerate the specific types that can access that member? why not the
specific methods of those types? why not the specific scope? why not the
specific line?

It's all about what use cases are enabled or not at different points along
this spectrum of granularity. So the question is, does your proposed scheme
enable additional use cases that `internal` does not? Does it prevent some
specific harm? I'm not convinced that having access to a member at a use
site when the declaration itself is totally under your own control, in the
same module, is "brittle" is any meaningful sense.

···

On Mon, Oct 17, 2016 at 09:51 Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

To put it more forcefully, I believe that ‘fileprivate’ currently suffers
from the same problem that singletons do. That is, the main reason to make
something ‘fileprivate’ instead of ‘private’ is to allow some sort of
extension. It is highly unlikely that the point you were required to
expose for that extension will not also be needed by other extensions.
Both ‘internal’ and submodules would extend the boundaries of the problem
from the file boundary to the module/submodule boundary… but the underlying
problem still persists.

If it is the module designer’s intent to limit extension, then fine, but
it also often forces a design to be much more brittle and fragile than it
needs to be. One of two things happens: extension becomes impossible or
there is access inflation, giving inappropriate levels of access (usually
combined with a note in the documentation saying not to use it).

Thanks,
Jon

On Oct 16, 2016, at 3:28 PM, T.J. Usiyan <griotspeak@gmail.com> wrote:

I don't like this at all and it comes down to "what is hidden can also
be unhidden". This, to me, feels like it would create more confusion than
it would address. Why not just use `internal` for `hidden` items? If we're
ok with modifying import statements, why not simply have a command that
imports `fileprivate` stuff? (not advocating for this).

I think that submodules would have really helped with this issue and it is
unfortunate that we couldn't get them in for swift 3.

On Sun, Oct 16, 2016 at 4:34 PM, Jonathan Hull via swift-evolution < > swift-evolution@swift.org> wrote:

I keep wanting a “protected” access level, but I must also admit that
there was something really elegant about Swift 2’s access scheme (and I
think most of us feel like the word ‘fileprivate’ feels out of place). I
was thinking about how to mesh those two ideas, and I think I may have come
up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level.
Hidden would work exactly the same way as fileprivate does now, but adds
the connotation that what is hidden can also be unhidden. By adding
‘import hidden TypeName’ to another file, that file also gains access to
all of the hidden items of that type (kind of like if it was in the same
file).

#FileA
        import Foundation

        Struct A {
                private var x:Int
                hidden var y:Int //This is just like fileprivate, but can
also be shared with other files
        }

        extension A {
                //y can be accessed here because they are in the same file
        }

#FileB
        import Foundation
        import hidden A //This allows the entire file to see A’s hidden
variables

        extension A {
                //y can be accessed here because of the ‘import hidden’
declaration
        }

#FileC
        import Foundation

        extension A {
                //y can NOT be seen or accessed here because it is hidden
        }

I think this is a fairly elegant solution to our protected dilemma, which
also feels in sync with Swift 2’s file-based scheme. The key features:
        • Extensions no longer need to be piled in the same file if it is
getting too long
        • Subclasses can be in their own file, but still have access to
the necessary parts of their superclass
        • It communicates the author’s intent that the items are not meant
to be visible to its users, but that it is expected to be used for
extension/subclassing
        • It requires an explicit statement ‘import hidden’ to access the
hidden variables. Safe by default, with override.
        • It is not bound by module boundaries (i.e. you could use it for
subclassing classes from an imported module)
        • Roughly the same length as ‘private’ and ‘public’ so various
declarations packed together are much easier to read (fileprivate breaks
reading rhythm)

Worth a formal proposal?

Thanks,
Jon

_______________________________________________
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


(Jay) #11

I'm not sure of my own opinions on this one as I can see good points on
both sides. There also seem to be a variety of different discussions with
similar concerns. So at the risk of confusing things I'm suggesting another
idea, with the hope/intention of promoting some deeper thought and helping
us figure out what is needed/wanted.

I will use the word 'sheltered' here, apologies if that is confusing, it
just means protected/hidden/whatever but without any existing notions of
what those might mean. It might not be the best word either, I just picked
it from a list of synonyms for 'protected'.

Class authors can (now) if they want provide their own way to explicitly
access some private members through a public interface:

public class Thing {
    private var x: Int
    public var shelteredX: Int {
        get { return x }
        set { x = newValue }
    }
}

But this isn't ideal as it just causes excess boilerplate code and doesn't
hide it from anyone or suggest they shouldn't normally use it (other than
the explicit naming). However, perhaps the compiler could help with this
type of pattern as follows:

public class Thing {
    sheltered var x: Int {
        get { return x } // not sure how this would work - x is also the
backing ivar so you don't have to declare it twice as _x and x.
        set { x = newValue }
    }
   // accessors provided - internal access to x is direct, sheltered access
is via accessors

    sheltered var y: Int
    // No accessors provided, sheltered access to y is direct
}

For members with 'sheltered' access:
* Don't by default allow access to the sheltered members or accessors -
they are equivalent to private (or maybe fileprivate)
* Provide a way to expose them in a given scope
* I think it can be applied to methods too

For example:
var aThing = Thing()
aThing.x = 7 // NOT ALLOWED: x is sheltered.
@unshelter(aThing) // aThing is unsheltered in the remainder of its scope.
aThing.x = 7 // NOW ALLOWED

One good thing is that this is very explicit where you use it.

Sheltered groups might also be a thing:

Definition:
sheltered<Subclasses> var x: Int
sheltered<Wotsits> var y: Int
Usage:
@unshelter<Subclasses>(aThing)
// x is available, y isn't
@unshelter<Subclasses, Wotsits>(aThing)
// x and y are available

"Subclasses" and "Wotsits" are author-defined identifiers, not keywords.
This is a bit like friends and protected, but using an honesty system,
where friends can easily declare themselves as friends by unsheltering all
the Wotsits group, and subclasses (not enforced) would unshelter the
Subclasses group. That is, the author's intentions are clear, but other
classes can abuse it to get access. If the author doesn't want to allow
abuse, they can do this:

Definition:
sheltered<Subclasses: Thing> var x: Int
sheltered<Wotsits: Wotsit> var y: Int

Usage:
@unshelter<Subclasses>(self); or
@unshelter<Subclasses>(anOtherThing)
// Works inside a Thing subclass, x becomes available. The scope in which
@unshelter is used must match the type of the shelter group.

@unshelter<Wotsits>(self); or
@unshelter<Wotsits>(aWotsit)
// Inside a Wotsit or a Wotsit subclass this would work, y becomes
available.

I'm just spitballing here, this isn't fully thought through. Perhaps a
where clause would be better instead of a type annotation for the shelter
groups, so you could list multiple types that are allowed to unshelter that
group. You might be able to abuse it still by putting something in an
extension of an allowed class, so that may need some further thought.

This is of course more complex that other proposed solutions, I'm just
trying to combine all the desires people seem to have and see what people
think - do we want more control and are happy with a bit more complexity?
or should it be simple and less control? Personally I actually don't have
much of an opinion on it because I haven't used Swift enough yet to come a
cropper of such things.


(Charles Srstka) #12

To me, it seems that if the author has control already. If s/he needs to hide symbols from outside the framework, the “internal” keyword already exists for that.

Charles

···

On Oct 17, 2016, at 12:47 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

On Oct 17, 2016, at 12:37 AM, Rien <Rien@Balancingrock.nl <mailto:Rien@Balancingrock.nl>> wrote:

-1.

If an API designer wants to allow access to a “hidden’ member, he should be in control of that access.

This seems to be one of the biggest internal arguments within the swift community. Who is in control of the use of frameworks? The author or the user?


(Rien) #13

First, let me say, thank you for your feedback. Just because I am arguing against others' points does not mean that I don’t appreciate those points.

-1.

If an API designer wants to allow access to a “hidden’ member, he should be in control of that access.

This seems to be one of the biggest internal arguments within the swift community. Who is in control of the use of frameworks? The author or the user?

I am trying to plot a middle path here which gives both a fair amount of control. I think that is part of why this proposal has been so unpopular, it is a compromise between both extremes, and everyone hates a compromise. Someone else just had the opinion that we should make everything public and note in the documentation what should and should not be used.

And as I said before, API like UIGestureRecognizer's is currently impossible to create solely in Swift. There is a hole in Swift’s access system which we will have to fix sometime during this phase. I think a lot of people think that submodules will magically solve all of our problems, (I want them too) but the core issue here will remain (just with a little less frequency), and we will be forced to go back to the drawing board in 6mo to a year. I am hoping to prevent that.

The proposed chance simply opens up access completely and leads to API vulnerability.

No, it only opens up access to API explicitly marked with ‘hidden’. That is, the framework author is explicitly saying: This API should only be used for subclassing/extension (but they also realize it can be accessed). The user of that API has to explicitly acknowledge that intention by writing ‘import hidden’. API marked private or internal will not be opened beyond it’s scope in any case.

As I said in another email, a potential further compromise would be to make ‘hidden’ a separate axis, and allow things like ‘internal hidden’, where access can only be opened up within the module. Would that solve your issues with the proposal?

I started out closer to your position, wanting more targeted permissions, but I ultimately realized that this would be good enough for real world use.

(I am in favour of a “friend” solution, in which the API designer explicitly allows access to identified members of the API users.)

But what happens when you need to be a friend and you don’t own the API? I get the thinking behind friend classes, but it is problematic in practice.

In that case, you don’t get to be a “friend”. Tough cookies. Life isn’t “fair”.
In an exact science like SW development, control must be hierarchical or singular. Any deviation, however small, will come back to bite.
Too many API developers have spend too much time to look for “errors” that inexperienced or unknowing API users caused because the API was too “open”. The API developer may think that “it is clearly documented”… but then again, look at stackoverflow…. people don’t read them, and the API developer will be held responsible for the error of others… if only in reputation...

As another poster mentioned, we basically have friend classes already, just based on location of code. The core issue I am trying to solve here cannot be solved with more variation of the same, we need a counterpoint to it, and then the two approaches can be mixed to get the desired balance/tradeoffs.

Balances and tradeoffs only get you deeper into the quicksand. Better not to tread there. Sometimes you have to say NO.

Rien.

PS: I do understand the lure, buts it’s (IMO) always better to resist.

···

On 17 Oct 2016, at 19:47, Jonathan Hull <jhull@gbis.com> wrote:

On Oct 17, 2016, at 12:37 AM, Rien <Rien@Balancingrock.nl> wrote:

Thanks,
Jon

Rien.

On 16 Oct 2016, at 22:34, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I keep wanting a “protected” access level, but I must also admit that there was something really elegant about Swift 2’s access scheme (and I think most of us feel like the word ‘fileprivate’ feels out of place). I was thinking about how to mesh those two ideas, and I think I may have come up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level. Hidden would work exactly the same way as fileprivate does now, but adds the connotation that what is hidden can also be unhidden. By adding ‘import hidden TypeName’ to another file, that file also gains access to all of the hidden items of that type (kind of like if it was in the same file).

#FileA
  import Foundation

  Struct A {
    private var x:Int
    hidden var y:Int //This is just like fileprivate, but can also be shared with other files
  }

  extension A {
    //y can be accessed here because they are in the same file
  }

#FileB
  import Foundation
  import hidden A //This allows the entire file to see A’s hidden variables

  extension A {
    //y can be accessed here because of the ‘import hidden’ declaration
  }

#FileC
  import Foundation

  extension A {
    //y can NOT be seen or accessed here because it is hidden
  }

I think this is a fairly elegant solution to our protected dilemma, which also feels in sync with Swift 2’s file-based scheme. The key features:
  • Extensions no longer need to be piled in the same file if it is getting too long
  • Subclasses can be in their own file, but still have access to the necessary parts of their superclass
  • It communicates the author’s intent that the items are not meant to be visible to its users, but that it is expected to be used for extension/subclassing
  • It requires an explicit statement ‘import hidden’ to access the hidden variables. Safe by default, with override.
  • It is not bound by module boundaries (i.e. you could use it for subclassing classes from an imported module)
  • Roughly the same length as ‘private’ and ‘public’ so various declarations packed together are much easier to read (fileprivate breaks reading rhythm)

Worth a formal proposal?

Thanks,
Jon

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


(Jon Hull) #14

On that last point, I would push back against calling it access inflation. If a member needs to be visible outside the file for whatever reason, it needs to have at least internal visibility. This is not at all "inflated" (implying that it's a workaround) but an explicitly contemplated part of Swift's access scheme.

But what happens when someone needs to make an extension outside of the module? For example, subclassing UIGestureRecognizer. Should things like setState: (which triggers a bunch of side effects and should never be called externally) be made public? The only two options in pure Swift 3 (it only gains it’s protected-style import from ObjC) are to make them public or to disallow subclassing outside the module (you have to call setState: as part of implementing a custom gesture recognizer).

What you're arguing is that we should be able to expressly enumerate the specific files in which a member is accessible. This is a more granular system, but increased granularity is not in and of itself sufficient justification. After all, if more granularity is better--and this is an argumentum ad absurdum--why not demand that we be able to expressly enumerate the specific types that can access that member? why not the specific methods of those types? why not the specific scope? why not the specific line?

Actually, I am arguing almost the opposite. I had originally wanted a more granular system like 'protected’ from most languages. This proposal comes from a realization that file granularity is most likely good enough for real world use cases.

What I am arguing is important:
1) The author’s ability to show which items should generally not be accessible/used, but are still provided/required for extension/subclassing purposes.
2) Requiring users of these items to acknowledge the author’s intent (by requiring ‘import hidden’) before using them
3) Terminology which has the correct connotation for this new behavior

It's all about what use cases are enabled or not at different points along this spectrum of granularity. So the question is, does your proposed scheme enable additional use cases that `internal` does not?

It allows use of ‘hidden’ variables/functions from other modules. It also allows the author to show their intention that the variable/function is meant to be accessed elsewhere if needed for extension (vs. internal which is the default)… and it requires the user to acknowledge that intention.

I'm not convinced that having access to a member at a use site when the declaration itself is totally under your own control, in the same module, is "brittle" is any meaningful sense.

What I mean by brittle is how much a change in one spot will ripple throughout your code by requiring other changes. It becomes a much larger issue when multiple people are working on a code base.

Thanks,
Jon

···

On Oct 16, 2016, at 7:23 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Oct 17, 2016 at 09:51 Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
To put it more forcefully, I believe that ‘fileprivate’ currently suffers from the same problem that singletons do. That is, the main reason to make something ‘fileprivate’ instead of ‘private’ is to allow some sort of extension. It is highly unlikely that the point you were required to expose for that extension will not also be needed by other extensions. Both ‘internal’ and submodules would extend the boundaries of the problem from the file boundary to the module/submodule boundary… but the underlying problem still persists.

If it is the module designer’s intent to limit extension, then fine, but it also often forces a design to be much more brittle and fragile than it needs to be. One of two things happens: extension becomes impossible or there is access inflation, giving inappropriate levels of access (usually combined with a note in the documentation saying not to use it).

Thanks,
Jon

On Oct 16, 2016, at 3:28 PM, T.J. Usiyan <griotspeak@gmail.com <mailto:griotspeak@gmail.com>> wrote:

I don't like this at all and it comes down to "what is hidden can also be unhidden". This, to me, feels like it would create more confusion than it would address. Why not just use `internal` for `hidden` items? If we're ok with modifying import statements, why not simply have a command that imports `fileprivate` stuff? (not advocating for this).

I think that submodules would have really helped with this issue and it is unfortunate that we couldn't get them in for swift 3.

On Sun, Oct 16, 2016 at 4:34 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I keep wanting a “protected” access level, but I must also admit that there was something really elegant about Swift 2’s access scheme (and I think most of us feel like the word ‘fileprivate’ feels out of place). I was thinking about how to mesh those two ideas, and I think I may have come up with a solution.

I propose we replace ‘fileprivate’ with a new ‘hidden’ access level. Hidden would work exactly the same way as fileprivate does now, but adds the connotation that what is hidden can also be unhidden. By adding ‘import hidden TypeName’ to another file, that file also gains access to all of the hidden items of that type (kind of like if it was in the same file).

#FileA
        import Foundation

        Struct A {
                private var x:Int
                hidden var y:Int //This is just like fileprivate, but can also be shared with other files
        }

        extension A {
                //y can be accessed here because they are in the same file
        }

#FileB
        import Foundation
        import hidden A //This allows the entire file to see A’s hidden variables

        extension A {
                //y can be accessed here because of the ‘import hidden’ declaration
        }

#FileC
        import Foundation

        extension A {
                //y can NOT be seen or accessed here because it is hidden
        }

I think this is a fairly elegant solution to our protected dilemma, which also feels in sync with Swift 2’s file-based scheme. The key features:
        • Extensions no longer need to be piled in the same file if it is getting too long
        • Subclasses can be in their own file, but still have access to the necessary parts of their superclass
        • It communicates the author’s intent that the items are not meant to be visible to its users, but that it is expected to be used for extension/subclassing
        • It requires an explicit statement ‘import hidden’ to access the hidden variables. Safe by default, with override.
        • It is not bound by module boundaries (i.e. you could use it for subclassing classes from an imported module)
        • Roughly the same length as ‘private’ and ‘public’ so various declarations packed together are much easier to read (fileprivate breaks reading rhythm)

Worth a formal proposal?

Thanks,
Jon

_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jay) #15

Oops, correction:

@unshelter<Wotsits>(self); or
@unshelter<Wotsits>(aWotsit)
// Inside a Wotsit or a Wotsit subclass this would work, y becomes
available.

Should be:

@unshelter<Wotsits>(aThing)
// Inside a Wotsit or a Wotsit subclass this would work, y becomes
available.

···

On Mon, 17 Oct 2016 at 13:38 Jay Abbott <jay@abbott.me.uk> wrote:

I'm not sure of my own opinions on this one as I can see good points on
both sides. There also seem to be a variety of different discussions with
similar concerns. So at the risk of confusing things I'm suggesting another
idea, with the hope/intention of promoting some deeper thought and helping
us figure out what is needed/wanted.

I will use the word 'sheltered' here, apologies if that is confusing, it
just means protected/hidden/whatever but without any existing notions of
what those might mean. It might not be the best word either, I just picked
it from a list of synonyms for 'protected'.

Class authors can (now) if they want provide their own way to explicitly
access some private members through a public interface:

public class Thing {
    private var x: Int
    public var shelteredX: Int {
        get { return x }
        set { x = newValue }
    }
}

But this isn't ideal as it just causes excess boilerplate code and doesn't
hide it from anyone or suggest they shouldn't normally use it (other than
the explicit naming). However, perhaps the compiler could help with this
type of pattern as follows:

public class Thing {
    sheltered var x: Int {
        get { return x } // not sure how this would work - x is also the
backing ivar so you don't have to declare it twice as _x and x.
        set { x = newValue }
    }
   // accessors provided - internal access to x is direct, sheltered
access is via accessors

    sheltered var y: Int
    // No accessors provided, sheltered access to y is direct
}

For members with 'sheltered' access:
* Don't by default allow access to the sheltered members or accessors -
they are equivalent to private (or maybe fileprivate)
* Provide a way to expose them in a given scope
* I think it can be applied to methods too

For example:
var aThing = Thing()
aThing.x = 7 // NOT ALLOWED: x is sheltered.
@unshelter(aThing) // aThing is unsheltered in the remainder of its scope.
aThing.x = 7 // NOW ALLOWED

One good thing is that this is very explicit where you use it.

Sheltered groups might also be a thing:

Definition:
sheltered<Subclasses> var x: Int
sheltered<Wotsits> var y: Int
Usage:
@unshelter<Subclasses>(aThing)
// x is available, y isn't
@unshelter<Subclasses, Wotsits>(aThing)
// x and y are available

"Subclasses" and "Wotsits" are author-defined identifiers, not keywords.
This is a bit like friends and protected, but using an honesty system,
where friends can easily declare themselves as friends by unsheltering all
the Wotsits group, and subclasses (not enforced) would unshelter the
Subclasses group. That is, the author's intentions are clear, but other
classes can abuse it to get access. If the author doesn't want to allow
abuse, they can do this:

Definition:
sheltered<Subclasses: Thing> var x: Int
sheltered<Wotsits: Wotsit> var y: Int

Usage:
@unshelter<Subclasses>(self); or
@unshelter<Subclasses>(anOtherThing)
// Works inside a Thing subclass, x becomes available. The scope in which
@unshelter is used must match the type of the shelter group.

@unshelter<Wotsits>(self); or
@unshelter<Wotsits>(aWotsit)
// Inside a Wotsit or a Wotsit subclass this would work, y becomes
available.

I'm just spitballing here, this isn't fully thought through. Perhaps a
where clause would be better instead of a type annotation for the shelter
groups, so you could list multiple types that are allowed to unshelter that
group. You might be able to abuse it still by putting something in an
extension of an allowed class, so that may need some further thought.

This is of course more complex that other proposed solutions, I'm just
trying to combine all the desires people seem to have and see what people
think - do we want more control and are happy with a bit more complexity?
or should it be simple and less control? Personally I actually don't have
much of an opinion on it because I haven't used Swift enough yet to come a
cropper of such things.