I suggest to discuss the idea brought up by Ross O'Brien in "SE-0159: Fix Private Access Levels" thread about "'conformance region' inside a type declaration - a scope nested within the type declaration scope" in this separate thread. Initially Ross suggested the special 'conformance' keyword(you can check the thread or quoted text in this email), but it seems like it is more reasonable to reuse 'extension' keyword for the same purpose. Please find my last reply below the Xiaodi's reply, if you are interested in discussion.

I assume this is a purely additive feature and is not in current plans for Swift 4, but we could discuss if we want such feature in such implementation for future proposals.

I see such benefits of this idea:
* We leave current model of extensions "as is", no changes for extensions
declared outside of the type.
* IMO we need stored properties in extensions at least in the same file
with type declaration(as was shown by discussion in the list) - and the
proposed solution gives them to us
* IMO this solution is better than just allow stored properties in
extensions in the same file:
* in the latter case extension has no access to 'scoped' members of type but this can be very useful to implement protocol conformance with help of internal(scoped) details, that should not be exposed to whole file level.
        * with proposed solution it is clear that any member declared in
extension block is a native part of type. We can see all members in the same type definition. * proposed solution has a simple mental model - "all that is defined inside type declaration is naturally a part of that type, stored properties allowed only for nested extensions, 'scoped' access 'uses' same rules as usually".


On 28.03.2017 1:33, Xiaodi Wu wrote:
Note that allowing nested extensions will require breaking rules for access
modifiers on all extensions. This is because, during revisions for SE-0025,
the idea of aligning rules for extensions to rules for types was brought up
and rejected. Subsequent proposals to do so also failed at various stages.

The current rule for types is as follows: the visibility of the type is the
upper limit for visibility of its members. Members have an implicit access
level of internal but may state any other access level, even if higher than
the containing type, and the _effective_ access level will be restricted by
the containing type. For example: `internal struct S { public var i: Int }`
has a member that is _effectively_ internal.

The current rule for extensions is as follows: although extensions are a
scope for the purposes of "new private", they are not a first-class entity
and have no existence in the type system. As such, they cannot be the upper
limit for members. Instead, the access modifier for an extension is unique
in Swift: it is regarded as a "shorthand" for the default access modifier
for contained members and also the maximum permitted access level for those
members; for example, inside a "public extension" the members default to
public (not internal). Attempts to remove this behavior were fiercely
rejected by those who want to use the shorthand to have an extension full
of public members without restating "public".

If, however, it is a "private extension", then the default access modifier
for members in that extension is "fileprivate". This is correct *only* if
extensions can never be nested. There is no spelling for the maximum
possible visibility of a member inside a _nested_ private extension, and it
is not permitted to state an explicit access level greater than that of the
extension itself. If you want to support nested extensions, then this rule
cannot continue. However, previous attempts at harmonizing these rules were
not successful.

OK, I see your point, thank you for detailed description.

First, probably (as was discussed in this thread) we should disallow 'scoped' access level(current 'private') at top level of file. This IMO will remove one of the big confusion point : 'scoped' will be allowed only inside a scope less than file. So, in this case, you'll have no "private extension"(current 'private') - only "fileprivate extension" at top level.

(I'll use 'scoped' and 'fileprivate' below to be clear what I mean. Currently they are 'private' and 'fileprivate', and probably will be 'scoped' and 'private')

Second - "There is no spelling for the maximum possible visibility of a member inside a _nested_ private extension".. I.e. member with even 'public' access modifier in such extension will be effectively 'scoped' and so nothing can be used outside of the extension ? If I understood correctly - then it seems like we should just not allow 'scoped' for nested extensions as such extension just has no sense.

So, both top level extensions and nested would have only 'fileprivate', 'internal' or 'public'. 'scoped' is not allowed for them(if we accept the above suggestions):

class MyType { // internal
  public var a = 10 // internal(because of type's access level)
  var b = 20 // internal
  scoped var c = 30 // scoped for type and for nested extensions

  // default and max level is fileprivate, bounded by type's access level
  fileprivate extension MyProtoA {
    scoped var d = 10 // scoped for extension, inaccessible outside  of it

    var e = 20 // fileprivate
    internal var f = 20 // fileprivate
    public var g = 30 // fileprivate

    func foo() { print(c, i, m) } // can access c, i, m
  }

  // default and max level is internal, bounded by type's access level
  extension MyProtoB {
    scoped var h = 40 // scoped for extension, inaccessible outside  of it

    var i = 50 // internal
    internal var j = 60 // internal
    public var k = 70 // public->internal(type's access level)
  }

  // default and max level is public, bounded by type's access level
  public extension MyProtoC {
    scoped var l = 80 // scoped for extension, inaccessible outside  of it

    var m = 90 // public -> internal(type's access level)
    internal var n = 100 // internal
    public var o = 110 // public->internal(type's access level)
  }
}

Do you see any drawbacks in such design? (given that we disallow scoped access modifier at top level of file and for nested extensions in type)


On Mon, Mar 27, 2017 at 15:59 Vladimir.S via swift-evolution
<swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

    On 27.03.2017 20:00, Ross O'Brien via swift-evolution wrote:
    > I'm considering this from a different angle.
    >
    > When we declare a type, we declare properties and functions which
    that type
    > has.
    > When we extend a type, we add functions to the type. (I'm including
    > computed properties in this.)
    > It's become an idiom of Swift to declare an extension to a type for each
    > protocol we want it to conform to, for reasons of code organisation and
    > readability. This may be true even if conformance to the protocol was a
    > primary intent of creating the type in the first place.
    >
    > The intent of the scoped access level is to allow programmers to create
    > properties and functions which are limited to the scope of their
    > declaration. A protocol conformance can be written, with the aid of
    helper
    > functions, in the confidence that the helper functions are not visible
    > outside the extension, minimising their impact on other components of the
    > module.
    > However, some protocol conformances require the type to have a specific
    > property, which the extension cannot facilitate. Some protocol
    conformances
    > don't require a property, but it would be really useful to have one, and
    > again an extension can't facilitate.
    >
    > Example: we want to be able to write this, but we can't:
    >
    > privateprotocolBar
    >
    > {
    >
    >   varinteger : Int{ get}
    >
    >   funcincrement()
    >
    > }
    >
    >
    > structFoo
    >
    > {
    >
    > }
    >
    >
    > extensionFoo: Bar
    >
    > {
    >
    >   varinteger : Int
    >
    >
    >   privatevarcounter : Int
    >
    >   funcincrement()
    >
    >   {
    >
    >   counter += 1
    >
    >   }
    >
    > }
    >
    >
    > This leads to a workaround: that properties are added to the original
    type,
    > and declared as fileprivate. They're not intended to be visible to any
    > scope other than the conforming extension - not even, really, to the
    type's
    > original scope.
    >
    > Continuing the example: we've compromised and written this:
    >
    > privateprotocolBar
    >
    > {
    >
    >   varinteger : Int{ get}
    >
    >   funcincrement()
    >
    > }
    >
    >
    > structFoo
    >
    > {
    >
    >   fileprivatevarinteger : Int
    >
    >   fileprivatevarcounter : Int
    >
    > }
    >
    >
    > extensionFoo: Bar
    >
    > {
    >
    >   funcincrement()
    >
    >   {
    >
    >   counter += 1
    >
    >   }
    >
    > }
    >
    >
    > This is not a fault of fileprivate (though it's a clunky name), or
    private.
    > Renaming these levels does not solve the problem. Removing private, such
    > that everything becomes fileprivate, does not solve the problem. The
    > problem is in the extension system.
    > (At this point I realise I'm focusing on one problem as if it's the
    only one.)
    >
    > Supposing we approached extensions differently. I think around SE-0025 we
    > were considering a 'nested' access level.
    >
    > Supposing we created a 'conformance region' inside a type declaration - a
    > scope nested within the type declaration scope - and that this
    conformance
    > region had its own access level. It's inside the type declaration, not
    > separate from it like an extension, so we can declare properties
    inside it.
    > But literally the only properties and functions declared inside the
    region
    > but visible anywhere outside of it, would be properties and functions
    > declared in the named protocol being conformed to.
    >
    > So, visually it might look like this:
    >
    >
    > privateprotocolBar
    >
    > {
    >
    >   varinteger : Int{ get}
    >
    >   funcincrement()
    >
    > }
    >
    >
    > structFoo
    >
    > {
    >
    >   conformance Bar // or conformance Foo : Bar, but since the region is
    > inside Foo that's redundant
    >
    >   {
    >
    >   varinteger : Int // visible because Foo : Bar, at Bar's access level
    >
    >
    >   varcounter : Int = 0// only visible inside the conformance scope,
    because
    > not declared in Bar
    >
    >
    >   funcincrement() // visible because Foo : Bar, at Bar's access level
    >
    >   {
    >
    >   counter += 1
    >
    >   }
    >
    >   }
    >
    > }
    >
    >
    > I've introduced a new keyword, conformance, though it may be clear enough
    > to keep using extension inside a scope for this. Foo still conforms
    to Bar,
    > in the same file. We've removed 'extension Foo :' and moved a '}' for
    this,
    > but that's not a breaking change as this is an addition. Readability is
    > compromised to the extent that this conformance is indented one level.
    >
    > I've not long had the idea. It's a different approach and may be worth a
    > discussion thread of its own for - or someone might point out some
    > glaringly obvious flaw in it. If it's useful, I don't know the full
    > implications this would have, such as how much this would reduce the use
    > of fileprivate (e.g. to none, to the minimal levels expected in
    SE-0025, or
    > no effect at all). It's just intended to remove a problem
    > which fileprivate is applied as a bad workaround for.

    IMO It seems like really great suggestion - I'd like to discuss it in
    separate thread, so please start it if you are interested in this.

    Currently I think the proposal should allow extensions(not new keyword)
    inside type declaration, and such extensions should be allowed to to have
    stored properties and theirs 'scoped' members should be inaccessible
    outside such extension declaration, but such extensions should be allowed
    to access 'scoped' members of class (because such extension is defined in
    the same scope)

    I.e.

    class MyClass {
       scoped var foo = 10

       extension ProtoA {
         scoped var bar = 20 // visible only in this extension(scope)
         var baz = 30 // will be accessible as 'normal' property of MyClass
         func barFunc() { print(bar); print(foo); } // can access foo
       }

       extension ProtoB {
         scoped var bar = 40 // visible only in this extension(scope)
         //var baz = 50 // can't be re-definied here
         func anotherFunc() { print(baz); print(foo); } // can access baz
       }
    }

    I see such benefits of this:
    * We leave current model of extensions "as is", no changes for extensions
    declared outside of the type.
    * IMO we need stored properties in extensions at least in the same file
    with type declaration(as was shown by discussion in the list) - and the
    proposed solution gives them to us
    * IMO this solution is better than just allow stored properties in
    extensions in the same file:
       * in the latter case extension has no access to 'scoped' members of type
    but this can be very useful to implement protocol conformance with help of
    internal(scoped) details, that should not be exposed to whole file level.
       * with proposed solution it is clear that any memeber declared in
    extension block is a native part of type. We see all members in the same
    type declaration.
       * proposed solution has a simple mental model - "all that is defined
    inside type declaration is naturally a part of that type, 'scoped' access
    'uses' same rules as usually".

    Vladimir.

    >
    > Ross
    >
    >
    > On Mon, Mar 27, 2017 at 4:26 PM, Rien via swift-evolution
    > <swift-evolution@swift.org <mailto:swift-evolution@swift.org>
    <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>>
    wrote:
    >
    >
    >     > On 27 Mar 2017, at 16:46, Steven Knodl via swift-evolution
    <swift-evolution@swift.org <mailto:swift-evolution@swift.org>
    <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>>
    wrote:
    >     >
    >     > Late to the party here
    >     >
    >     > * What is your evaluation of the proposal?
    >     > I’m -1 on this.  The proposal is not a difficult read, but may
    have been simply more simply named “Remove Scoped Access Level/Revert
    SE-0025” as that is what is being proposed. “Fix” seems to me to be a
    unfortunately worded judgmental proposal title.
    >     >
    >     > * Is the problem being addressed significant enough to warrant
    a change?
    >     > No.  I consider myself to be a fairly “new/n00b” Obj-C/Swift
    developer. Although SE-0025 was a change that required source changes
    when implemented, the reasoning was not difficult to understand and
    changes were simple to make once identified.   Here they are from SE-0025
    >     >
    >     >       • public: symbol visible outside the current module
    >     >       • internal: symbol visible within the current module
    >     >       • fileprivate: symbol visible within the current file
    >     >       • private: symbol visible within the current declaration
    >     >
    >     > Moving forward these changes are not difficult to comprehend.
    I tend to make _everything_ “private” up front so I don’t have any API
    leakage.  Then dial back to “fileprivate” as needed.  It’s not
    difficult for me I guess.
    >
    >     Right. I do that myself more than I would like to admit.
    >     But when we only loosen up/tighten down during coding then access
    >     levels are almost useless.
    >     The point of access level control is in the design, not in the
    coding.
    >     If we made a design (including access levels) and then have to dial
    >     back, that should be a warning that something is wrong.
    >     To me, this is an argument in favour of the proposal.
    >
    >     Rien.
    >
    >
    >
    >     >   As such, I don’t believe that this change was “Actively Harmful”,
    >     especially for new developers who have a clean slate or simply are
    >     leaving everything unmarked (internal) anyhow until they move up to
    >     more advanced topics.  Unraveling a generic or functional code
    someone
    >     else wrote uses way more cognitive power.
    >     >
    >     > I’d like to address the suggestion that the migration for SE-0159
    >     could “simply” be a search and replace without loss of functionality.
    >     This doesn’t make sense if you consider the entire code lifecycle.
    >     Sure the code gets migrated and compiles.  This is fine if they code
    >     _never_ has to be read again.  But as we know, code is written
    once and
    >     _read many times_ as it will need to be maintained.  The distinction
    >     between private and fileprivate contains information, and although it
    >     may work correctly now, some information meant to help maintain that
    >     code has been lost if these keywords are merged and the functionality
    >     of scoped access is removed.  So yes if you don’t maintain the code
    >     where this migration takes place, this would be ok. But Swift strives
    >     for readability.  Moving classes to separate files to address these
    >     issues, creates a multitude of Bunny classes where again for
    >     readability some classes belong together in the same file for ease of
    >     comprehension (again, code is written once , read many times)
    >     >
    >     > * Does this proposal fit well with the feel and direction of Swift?
    >     > The spirit of the proposal to simplify access levels is well taken.
    >     This proposal however simplifies at the expense of lost functionality
    >     (Scoped Access levels) with no replacement.  The threads talk a about
    >     submodules and other solutions that could fill this gap that are
    not on
    >     the roadmap, planned or possible which makes them  non-admissible in
    >     considering this proposal.
    >     >
    >     > * If you have used other languages, libraries, or package managers
    >     with a similar feature, how do you feel that this proposal
    compares to
    >     those?
    >     > I am more familiar with scoped access so perhaps that feels more
    >     natural to me.  But with the current implementation Swift users can
    >     choose whether they use File Based or Scope Based tools, so although
    >     not ideal to either side, acceptable until a suitable replacement
    could
    >     be forged.
    >     >
    >     > * How much effort did you put into your review? A glance, a quick
    >     reading, or an in-depth study?
    >     > Re-read SE-0025/proposal/most of this very long thread
    >     >
    >     >
    >     >
    >     >
    >     > From: <swift-evolution-boun...@swift.org
    <mailto:swift-evolution-boun...@swift.org>
    >     <mailto:swift-evolution-boun...@swift.org
    <mailto:swift-evolution-boun...@swift.org>>> on behalf of Tino Heth via
    >     swift-evolution <swift-evolution@swift.org
    <mailto:swift-evolution@swift.org>
    >     <mailto:swift-evolution@swift.org
    <mailto:swift-evolution@swift.org>>>
    >     > Reply-To: Tino Heth <2...@gmx.de <mailto:2...@gmx.de>
    <mailto:2...@gmx.de <mailto:2...@gmx.de>>>
    >     > Date: Monday, March 27, 2017 at 6:48 AM
    >     > To: Zach Waldowski <z...@waldowski.me
    <mailto:z...@waldowski.me> <mailto:z...@waldowski.me
    <mailto:z...@waldowski.me>>>
    >     > Cc: <swift-evolution@swift.org
    <mailto:swift-evolution@swift.org> <mailto:swift-evolution@swift.org
    <mailto:swift-evolution@swift.org>>>
    >     > Subject: Re: [swift-evolution] [Review] SE-0159: Fix Private Access
    >     Levels
    >     >
    >     >
    >     >
    >     >> I am now absolutely thrilled to create a filter to Mark As Read
    >     anything else arising from this thread. Good luck.
    >     >
    >     > That might be a good idea — after more than 200 messages, and a
    quite
    >     circular discussion with an unhealthy amount of ignorance for the
    >     opposing side ;-).
    >     >
    >     > To fight the latter, I just tried to take the position that "new
    >     private" is really important, and this imho leads to interesting
    >     consequences...
    >     > This access modifier really doesn't solve a problem, like "let"
    does
    >     (unless the problem you want to solve is having a language with
    private
    >     access).
    >     > Have a look at this:
    >     >
    >     > public struct SeperateConcerns {
    >     >        private var foo: Int = 0
    >     >        public mutating func updateFoo(_ value: Int) {
    >     >               print("The only allowed way to change foo was
    invoked")
    >     >               foo = value
    >     >        }
    >     >
    >     >        private var bar: Int = 0
    >     >        public mutating func updateBar(_ value: Int) {
    >     >               print("The only allowed way to change bar was
    invoked")
    >     >               bar = value
    >     >        }
    >     >
    >     >        private var foobar: Int = 0
    >     >        public mutating func updateFoobar(_ value: Int) {
    >     >               print("The only allowed way to change foobar was
    invoked")
    >     >               foobar = value
    >     >        }
    >     > }
    >     >
    >     >
    >     > You can protect foo from being changed by code in other files, and
    >     from extensions in the same file — and if the latter is a concern,
    >     there should also be a way to limit access to foo to specific
    function
    >     in scope.
    >     > Afaik, somebody proposed "partial" type declarations, but without
    >     them, the meaning of private is rather arbitrary, and the feature is
    >     only useful for a tiny special case.
    >     > If we had partial types, the situation would be different, and if
    >     would be possible to declare extensions inside a partial
    declaration of
    >     another type, we could even remove fileprivate without an replacement
    >     (I guess I should write a separate mail for this thought…)
    >     > _______________________________________________ swift-evolution
    >     mailing list swift-evolution@swift.org
    <mailto:swift-evolution@swift.org>
    >     <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>
    >     https://lists.swift.org/mailman/listinfo/swift-evolution
    >     <https://lists.swift.org/mailman/listinfo/swift-evolution>
    >     > _______________________________________________
    >     > swift-evolution mailing list
    >     > swift-evolution@swift.org <mailto:swift-evolution@swift.org>
    <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>
    >     > https://lists.swift.org/mailman/listinfo/swift-evolution
    >     <https://lists.swift.org/mailman/listinfo/swift-evolution>
    >
    >     _______________________________________________
    >     swift-evolution mailing list
    >     swift-evolution@swift.org <mailto:swift-evolution@swift.org>
    <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>
    >     https://lists.swift.org/mailman/listinfo/swift-evolution
    >     <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
    >
    _______________________________________________
    swift-evolution mailing list
    swift-evolution@swift.org <mailto:swift-evolution@swift.org>
    https://lists.swift.org/mailman/listinfo/swift-evolution

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

Reply via email to