private(call) and internal(call)


(Charles Srstka) #1

Motivation:

The Cocoa frameworks have a long-standing issue regarding methods that are intended as primitives for subclasses to override or for protocol implementers to provide, but which aren’t meant for client code to call directly. Often, the only way to determine that a method shouldn’t be called is to look at the documentation, but that isn’t much of a help when auto-complete suggests such methods, which sometimes look perfectly usable, even tempting, to the naked eye. NSView has a number of them that have come up many a time on mailing lists and support forums in the past, to the extent that Apple has actually created a chart intended to indicate which should be called directly and which shouldn’t:

https://developer.apple.com/library/mac/releasenotes/UserExperience/RNAutomaticLayout/#//apple_ref/doc/uid/TP40010631-CH1-SW14

Clearly advertising things that are not supposed to be actually called is non-ideal in terms of interface.

Proposed Solution:

Following the example of private(set), I propose a new access modifier for methods and properties called private(call). A method declared private(call) would behave exactly the same as a private method in almost all circumstances, the one exception being when a subclass attempts to override it (for a class) or a class, struct, or enum implements it (for a protocol). These methods would not be callable by any code outside of the source file that declared the property, and would not show up in code completion.

A similar access modifier, internal(call), would allow the method to be called only by code in the same module, but publicly would be override/implement only.

Wait, isn’t this the same as protected?

No. The difference between this and protected is that while protected allows subclasses to access the method and/or property as if it were public, private(call) does *not*. Subclass code is forbidden from accessing the member, exactly as any code from any other class would be. This makes implementation simpler than it would be for protected; private(call) can be treated exactly the same as private for all use cases other than:

1. Displaying the generated “headers” to show the class or protocol’s interface.

2. A method or property declaration in a source code file that either begins with “override” or implements a required member of a protocol.

In all other cases, the compiler can treat private(call) and internal(call) exactly as it would private and internal respectively. It is also more secure than protected, since it cannot be worked around simply by making a subclass of the class in question, and since it reduces the ways a subclass can do something unexpected with the property or method.

Alternatives Considered:

In some cases, an exception could be made for Objective-C methods marked OBJC_REQUIRES_SUPER; in such cases, the subclass could be allowed to call super’s implementation, but only from within the override. This could be extended to pure-Swift classes once Swift acquires an equivalent to OBJC_REQUIRES_SUPER. Other than this one exception, even subclasses would not be able to call a method marked private(call). This would, however, complicate the implementation somewhat, and may not be necessary, since any work that the superclass needs to do could be done in a separate method, before calling the private(call) method. There may be some cases where the subclass wants to begin with the return value from the superclass method and modify it slightly, however, in which case this variant may be helpful. It could also possibly be useful in cases where there is a chain of subclasses going several layers down, where each subclass wants to do the work of its own superclass’s implementation before continuing.

Summary:

All in all, I think this would be a positive contribution to the language. What do you think?

Charles


(Félix Cloutier) #2

This is the reply that I wrote to someone else who wanted a private(extension) <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/005450.html> modifier. Swift's access modifiers align with linker access modifiers and visibility styles: `private` is not exported beyond the translation unit (and can be very aggressively optimized), `internal` is hidden (not exported to the dynamic symbol table) and `public` is exported to the symbol table of the resulting executable. `Private(set)` just means that the setter method is not exported but the getter is.

So as I said in the other thread, I wouldn't consider that these new modifiers follow the example that private(set) set.

The great thing about using the linker to implement access control is that it can be enforced. The reason internal or private can't be called from outside an executable is that no data about it exists in the final executable As a matter of fact, the symbol itself might not even exist in the fully optimized executable.

I also think that it's fundamentally the same as protected, because nothing prevents you from sharing the logic outside. You can forward the protected call to a public call, which is exactly the same as a public call forwarding to a protected call in terms of encapsulation.

If I can toot my horn, you should consider as an alternative better cooperation between the language and the autocomplete/linters <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/005807.html>. I haven't got around to a new proposal with documentation comments yet but the idea's there.

Félix

···

Le 11 janv. 2016 à 19:53:57, Charles Srstka via swift-evolution <swift-evolution@swift.org> a écrit :

Motivation:

The Cocoa frameworks have a long-standing issue regarding methods that are intended as primitives for subclasses to override or for protocol implementers to provide, but which aren’t meant for client code to call directly. Often, the only way to determine that a method shouldn’t be called is to look at the documentation, but that isn’t much of a help when auto-complete suggests such methods, which sometimes look perfectly usable, even tempting, to the naked eye. NSView has a number of them that have come up many a time on mailing lists and support forums in the past, to the extent that Apple has actually created a chart intended to indicate which should be called directly and which shouldn’t:

https://developer.apple.com/library/mac/releasenotes/UserExperience/RNAutomaticLayout/#//apple_ref/doc/uid/TP40010631-CH1-SW14

Clearly advertising things that are not supposed to be actually called is non-ideal in terms of interface.

Proposed Solution:

Following the example of private(set), I propose a new access modifier for methods and properties called private(call). A method declared private(call) would behave exactly the same as a private method in almost all circumstances, the one exception being when a subclass attempts to override it (for a class) or a class, struct, or enum implements it (for a protocol). These methods would not be callable by any code outside of the source file that declared the property, and would not show up in code completion.

A similar access modifier, internal(call), would allow the method to be called only by code in the same module, but publicly would be override/implement only.

Wait, isn’t this the same as protected?

No. The difference between this and protected is that while protected allows subclasses to access the method and/or property as if it were public, private(call) does *not*. Subclass code is forbidden from accessing the member, exactly as any code from any other class would be. This makes implementation simpler than it would be for protected; private(call) can be treated exactly the same as private for all use cases other than:

1. Displaying the generated “headers” to show the class or protocol’s interface.

2. A method or property declaration in a source code file that either begins with “override” or implements a required member of a protocol.

In all other cases, the compiler can treat private(call) and internal(call) exactly as it would private and internal respectively. It is also more secure than protected, since it cannot be worked around simply by making a subclass of the class in question, and since it reduces the ways a subclass can do something unexpected with the property or method.

Alternatives Considered:

In some cases, an exception could be made for Objective-C methods marked OBJC_REQUIRES_SUPER; in such cases, the subclass could be allowed to call super’s implementation, but only from within the override. This could be extended to pure-Swift classes once Swift acquires an equivalent to OBJC_REQUIRES_SUPER. Other than this one exception, even subclasses would not be able to call a method marked private(call). This would, however, complicate the implementation somewhat, and may not be necessary, since any work that the superclass needs to do could be done in a separate method, before calling the private(call) method. There may be some cases where the subclass wants to begin with the return value from the superclass method and modify it slightly, however, in which case this variant may be helpful. It could also possibly be useful in cases where there is a chain of subclasses going several layers down, where each subclass wants to do the work of its own superclass’s implementation before continuing.

Summary:

All in all, I think this would be a positive contribution to the language. What do you think?

Charles

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


(Michel Fortin) #3

The idea looks good to me on the surface. But the details are awkward.

First, if you can't call the super implementation from an override, there is no need to require that the overriding function be private(call) because all it will contain is client code that can't call the library's implementation. So the derived class can make the function more visible by reimplementing it.

But that's not the common case. In general you want to allow the overriding function to call the super implementation. So...

Second, if you allow a call to the super implementation from an override, then you need to make that overriding function uncallable by anyone. Only the base class can call it using virtual dispatch. You can't call it even from the same file, so it's *less visible than private*.

That's a bit weird.

···

Le 11 janv. 2016 à 19:53, Charles Srstka via swift-evolution <swift-evolution@swift.org> a écrit :

What do you think?

--
Michel Fortin
https://michelf.ca


(Brent Royal-Gordon) #4

The reason internal or private can't be called from outside an executable is that no data about it exists in the final executable

Actually, I believe internal symbols *are* in the final executable. (If they aren't now, they certainly were for a while.)

···

--
Brent Royal-Gordon
Architechies


(Charles Srstka) #5

This is the reply that I wrote to someone else who wanted a
private(extension) [2] modifier. Swift's access modifiers align with
linker access modifiers and visibility styles: `private` is not
exported beyond the translation unit (and can be very aggressively
optimized), `internal` is hidden (not exported to the dynamic symbol
table) and `public` is exported to the symbol table of the resulting
executable. `Private(set)` just means that the setter method is not
exported but the getter is.

So as I said in the other thread, I wouldn't consider that these new
modifiers follow the example that private(set) set.

Okay, amend that statement to say that it follows the _syntactic_ example of private(set). How it is implemented under the hood is just that: implementation details.

The great thing about using the linker to implement access control is
that it can be enforced. The reason internal or private can't be
called from outside an executable is that no data about it exists in
the final executable As a matter of fact, the symbol itself might not
even exist in the fully optimized executable.

Access modifiers aren't just across linker boundaries. Private, private(set), and, if implemented, private(call) would help enforce boundaries between classes within a single module as well.

I also think that it's fundamentally the same as protected, because
nothing prevents you from sharing the logic outside. You can forward
the protected call to a public call, which is exactly the same as a
public call forwarding to a protected call in terms of encapsulation.

Um, if that's your metric, then private is fundamentally the same as internal is the same as public, and there's no such thing as access modifiers at all. *Anything* can be forwarded to a public call, whether it's private, public, internal, my proposed private(call), or protected, if it's done within the class that owns it. However, code from outside the class (or struct, or enum, or whatever) wouldn't be able to access something defined as private(call), even if it's a class and they're a subclass of it, whereas with protected, if you're a subclass it's basically open season.

Charles

···

On 2016-01-11 23:33, Félix Cloutier wrote:


(Jordan Rose) #6

This is not the recommended way to think about Swift access control, and it is not guaranteed. For example, an overridable internal method in a publicly-subclassable class currently needs to appear in a subclass's vtable, even if the subclass is in another module and cannot possibly override this method. That's an implementation detail, sure, and it isn't a long-term constraint (because we want you to always be allowed to add new methods), but it's an example of a case where symbol linkage doesn't match up with access control.

Access-control-related features should be evaluated on their own merits, not on the implementation details of exported and non-exported symbols.

Jordan

···

On Jan 11, 2016, at 20:33 , Félix Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This is the reply that I wrote to someone else who wanted a private(extension) <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/005450.html> modifier. Swift's access modifiers align with linker access modifiers and visibility styles: `private` is not exported beyond the translation unit (and can be very aggressively optimized), `internal` is hidden (not exported to the dynamic symbol table) and `public` is exported to the symbol table of the resulting executable. `Private(set)` just means that the setter method is not exported but the getter is.

So as I said in the other thread, I wouldn't consider that these new modifiers follow the example that private(set) set.

The great thing about using the linker to implement access control is that it can be enforced. The reason internal or private can't be called from outside an executable is that no data about it exists in the final executable As a matter of fact, the symbol itself might not even exist in the fully optimized executable.


(Charles Srstka) #7

What do you think?

The idea looks good to me on the surface. But the details are awkward.

First, if you can't call the super implementation from an override,
there is no need to require that the overriding function be
private(call) because all it will contain is client code that can't
call the library's implementation. So the derived class can make the
function more visible by reimplementing it.

My thinking is that if the subclass wants to make the functionality behind the method more visible, it's better to just spin it off into a separate method rather than ask callers to get used to calling a method that, according to the API contract of the superclass, is more an implementation detail than a part of the external interface.

But that's not the common case. In general you want to allow the
overriding function to call the super implementation. So...

Second, if you allow a call to the super implementation from an
override, then you need to make that overriding function uncallable by
anyone. Only the base class can call it using virtual dispatch. You
can't call it even from the same file, so it's *less visible than
private*.

That's a bit weird.

It may be a bit weird, but it is what the API contract is already asking the class to do. It's just that currently, it's not enforced other than in documentation.

I should point out that we, essentially, already have methods like this; Swift initializers, unlike init methods in Objective-C, are similarly invisible; they cannot be called by any other code, even in the same file, unless that code is marked as a convenience initializer. Deinit, likewise, can't be called by anything except for the Swift runtime itself.

Charles

···

On 2016-01-12 13:50, Michel Fortin wrote:

Le 11 janv. 2016 à 19:53, Charles Srstka via swift-evolution > <swift-evolution@swift.org> a écrit :


(Félix Cloutier) #8

The reason internal or private can't be called from outside an executable is that no data about it exists in the final executable

Actually, I believe internal symbols *are* in the final executable. (If they aren't now, they certainly were for a while.)

The LLVM bitcode for internal symbols has the "hidden" visibility style <http://llvm.org/docs/LangRef.html#visibilitystyles> (you can test with swift -emit-ir). It's possible that the symbol will show up in debug executables, but the intent is to get rid of it at release.

The great thing about using the linker to implement access control is
that it can be enforced. The reason internal or private can't be
called from outside an executable is that no data about it exists in
the final executable As a matter of fact, the symbol itself might not
even exist in the fully optimized executable.

Access modifiers aren't just across linker boundaries. Private, private(set), and, if implemented, private(call) would help enforce boundaries between classes within a single module as well.

Currently, access modifiers *are* just across linker boundaries. The translation unit (single file) level is a linker boundary. Private is very close to C's static.

I also think that it's fundamentally the same as protected, because
nothing prevents you from sharing the logic outside. You can forward
the protected call to a public call, which is exactly the same as a
public call forwarding to a protected call in terms of encapsulation.

Um, if that's your metric, then private is fundamentally the same as internal is the same as public, and there's no such thing as access modifiers at all. *Anything* can be forwarded to a public call, whether it's private, public, internal, my proposed private(call), or protected, if it's done within the class that owns it. However, code from outside the class (or struct, or enum, or whatever) wouldn't be able to access something defined as private(call), even if it's a class and they're a subclass of it, whereas with protected, if you're a subclass it's basically open season.

They're not, because as the writer of the Swift library, *you* are in control of internal and private members. I believe that the worry has never been what you could do with your own code, but rather what others can do with your own code, and in the case of a `private(call)` member, if the person really wants that logic to be callable, they have means to do it (whereas they don't have the means to call an internal or private symbol).

The only un-abusable justification for the feature is to document that a symbol shouldn't be used directly, hence the suggestion to use documentation features to achieve that goal.

···

Le 12 janv. 2016 à 00:28:55, cocoadev@charlessoft.com a écrit :


(Félix Cloutier) #9

To be clear, I agree that there is a problem. I just perceive it as a documentation problem more than as an access control problem.

There are at least 2 other proposals that suggest(ed?) a more complex access control model (`private(extension)`, `private(objc)`), and I don't like adding exceptions to `private`.

Félix

···

Le 12 janv. 2016 à 16:43:39, Charles Srstka via swift-evolution <swift-evolution@swift.org> a écrit :

On 2016-01-12 13:50, Michel Fortin wrote:

Le 11 janv. 2016 à 19:53, Charles Srstka via swift-evolution >> <swift-evolution@swift.org> a écrit :

What do you think?

The idea looks good to me on the surface. But the details are awkward.
First, if you can't call the super implementation from an override,
there is no need to require that the overriding function be
private(call) because all it will contain is client code that can't
call the library's implementation. So the derived class can make the
function more visible by reimplementing it.

My thinking is that if the subclass wants to make the functionality behind the method more visible, it's better to just spin it off into a separate method rather than ask callers to get used to calling a method that, according to the API contract of the superclass, is more an implementation detail than a part of the external interface.

But that's not the common case. In general you want to allow the
overriding function to call the super implementation. So...
Second, if you allow a call to the super implementation from an
override, then you need to make that overriding function uncallable by
anyone. Only the base class can call it using virtual dispatch. You
can't call it even from the same file, so it's *less visible than
private*.
That's a bit weird.

It may be a bit weird, but it is what the API contract is already asking the class to do. It's just that currently, it's not enforced other than in documentation.

I should point out that we, essentially, already have methods like this; Swift initializers, unlike init methods in Objective-C, are similarly invisible; they cannot be called by any other code, even in the same file, unless that code is marked as a convenience initializer. Deinit, likewise, can't be called by anything except for the Swift runtime itself.

Charles

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


(Michel Fortin) #10

What do you think?

The idea looks good to me on the surface. But the details are awkward.
First, if you can't call the super implementation from an override,
there is no need to require that the overriding function be
private(call) because all it will contain is client code that can't
call the library's implementation. So the derived class can make the
function more visible by reimplementing it.

My thinking is that if the subclass wants to make the functionality behind the method more visible, it's better to just spin it off into a separate method rather than ask callers to get used to calling a method that, according to the API contract of the superclass, is more an implementation detail than a part of the external interface.

True. In any case, this case isn't the problematic one.

But that's not the common case. In general you want to allow the
overriding function to call the super implementation. So...
Second, if you allow a call to the super implementation from an
override, then you need to make that overriding function uncallable by
anyone. Only the base class can call it using virtual dispatch. You
can't call it even from the same file, so it's *less visible than
private*.
That's a bit weird.

It may be a bit weird, but it is what the API contract is already asking the class to do. It's just that currently, it's not enforced other than in documentation.

What I meant here is that you can't even mandate `private` at the override point because it's *less visible than private*. So writing `private` or `private(call)` at the override point is a lie. You can't define `private(call)` to mean "private access for calling this but if there is a override keyword next to it then it means less visible than private and you can't call it", that's too awkward.

I understand what the API contract is, but all I'm saying is that I wonder if there is a syntax that can express all this sanely.

I should point out that we, essentially, already have methods like this; Swift initializers, unlike init methods in Objective-C, are similarly invisible; they cannot be called by any other code, even in the same file, unless that code is marked as a convenience initializer. Deinit, likewise, can't be called by anything except for the Swift runtime itself.

Sure, but `init` and `deinit` in Swift are function-like constructs that aren't quite functions in order to accommodate all this. Surely you don't want to go that far here.

···

Le 12 janv. 2016 à 16:43, cocoadev@charlessoft.com a écrit :
On 2016-01-12 13:50, Michel Fortin wrote:

Le 11 janv. 2016 à 19:53, Charles Srstka via swift-evolution >> <swift-evolution@swift.org> a écrit :

--
Michel Fortin
https://michelf.ca


(Charles Srstka) #11

They're not, because as the writer of the Swift library, *you* are in
control of internal and private members. I believe that the worry has
never been what you could do with your own code, but rather what
others can do with your own code, and in the case of a `private(call)`
member, if the person really wants that logic to be callable, they
have means to do it (whereas they don't have the means to call an
internal or private symbol).

Unless we are talking about someone hacking their way in via C or assembler or something, no, they don't have the means to do so. And if we *are* talking about hacking, then that's impossible to prevent, anyway, if someone is *really* determined. I mean, even if the stupid thing is inlined, you could still disassemble the thing and copy-and-paste out the relevant part. Making things 100% bulletproof is not the goal, and if it were, we would have been publishing every single API, private and public, in Objective-C, since in that language it was staggeringly easy, even for a relative neophyte, to find and call private methods on pretty much anything. Despite that, we still found it useful to hide things in private headers and implementation files, which suggests that access control does have a use beyond the linker level after all.

The only un-abusable justification for the feature is to document that
a symbol shouldn't be used directly, hence the suggestion to use
documentation features to achieve that goal.

It has multiple goals:

1) To document that it shouldn't be called directly

2) To keep it from being called accidentally via code completion

3) To make it enough of a PITA to call that people will be more likely to just do things the right way.

Charles

···

On 2016-01-12 01:36, Félix Cloutier wrote:


(Charles Srstka) #12

It may be a bit weird, but it is what the API contract is already asking the class to do. It's just that currently, it's not enforced other than in documentation.

What I meant here is that you can't even mandate `private` at the
override point because it's *less visible than private*. So writing
`private` or `private(call)` at the override point is a lie. You can't
define `private(call)` to mean "private access for calling this but if
there is a override keyword next to it then it means less visible than
private and you can't call it", that's too awkward.

I understand what the API contract is, but all I'm saying is that I
wonder if there is a syntax that can express all this sanely.

I've read this paragraph a large number of times, and I still don't understand what the objection is here.

1. It's not less visible than private, it's more visible by definition, because it appears in the interface and you can interact with it (by subclassing a class or implementing a protocol). If it were just plain private, it would be completely invisible and there would be no way to interact with it or even see that it exists outside of hacky things.

2. How this means that you can't mandate private(call), or that it would be "a lie", simply confuses me.

3. "You can override this but you can't call it" isn't too complicated to wrap one's head around, and to my mind sums up this feature pretty succinctly.

I should point out that we, essentially, already have methods like this; Swift initializers, unlike init methods in Objective-C, are similarly invisible; they cannot be called by any other code, even in the same file, unless that code is marked as a convenience initializer. Deinit, likewise, can't be called by anything except for the Swift runtime itself.

Sure, but `init` and `deinit` in Swift are function-like constructs
that aren't quite functions in order to accommodate all this. Surely
you don't want to go that far here.

Under the hood, they're just methods; if your class derives from NSObject, you can even inspect it with the Objective-C runtime functions. It's just that they have special semantics for calling them.

Charles

···

On 2016-01-12 17:34, Michel Fortin wrote:


(Félix Cloutier) #13

Unless we are talking about someone hacking their way in via C or assembler or something, no, they don't have the means to do so. And if we *are* talking about hacking, then that's impossible to prevent, anyway, if someone is *really* determined. I mean, even if the stupid thing is inlined, you could still disassemble the thing and copy-and-paste out the relevant part. Making things 100% bulletproof is not the goal, and if it were, we would have been publishing every single API, private and public, in Objective-C, since in that language it was staggeringly easy, even for a relative neophyte, to find and call private methods on pretty much anything. Despite that, we still found it useful to hide things in private headers and implementation files, which suggests that access control does have a use beyond the linker level after all.

class A {
  private(call) func logicYouShouldImplementButNotCall() {
  }
}

/* other module */ class B {
  private(call) override func logicYouShouldImplementButNotCall() {
    iActuallyWantToCallItWhyWontYouLetMeDoIt()
  }
  
  func iActuallyWantToCallItWhyWontYouLetMeDoIt() {
    /* snip */
  }
}

The only un-abusable justification for the feature is to document that
a symbol shouldn't be used directly, hence the suggestion to use
documentation features to achieve that goal.

It has multiple goals:

1) To document that it shouldn't be called directly

2) To keep it from being called accidentally via code completion

3) To make it enough of a PITA to call that people will be more likely to just do things the right way.

I'm not in favor of complexifying access control for the sake of documentation. The first two goals can be implemented with just a little more collaboration between tools and documentation and you can get 90% of the way there for the third one as well.

Félix

···

Le 12 janv. 2016 à 02:11:07, cocoadev@charlessoft.com a écrit :


(Michel Fortin) #14

It may be a bit weird, but it is what the API contract is already asking the class to do. It's just that currently, it's not enforced other than in documentation.

What I meant here is that you can't even mandate `private` at the
override point because it's *less visible than private*. So writing
`private` or `private(call)` at the override point is a lie. You can't
define `private(call)` to mean "private access for calling this but if
there is a override keyword next to it then it means less visible than
private and you can't call it", that's too awkward.
I understand what the API contract is, but all I'm saying is that I
wonder if there is a syntax that can express all this sanely.

I've read this paragraph a large number of times, and I still don't understand what the objection is here.

1. It's not less visible than private, it's more visible by definition, because it appears in the interface and you can interact with it (by subclassing a class or implementing a protocol). If it were just plain private, it would be completely invisible and there would be no way to interact with it or even see that it exists outside of hacky things.

I realize I was probably mistaken by an example that was not written by you but by Félix Cloutier. If you were the one that wrote this example in the thread then my criticisms would apply:

class A {
  private(call) func logicYouShouldImplementButNotCall() {
  }
}

/* other module */ class B {
  private(call) override func logicYouShouldImplementButNotCall() {
    iActuallyWantToCallItWhyWontYouLetMeDoIt()
  }
  
  func iActuallyWantToCallItWhyWontYouLetMeDoIt() {
    /* snip */
  }
}

In this example, class B is qualified `private(call)`, but it can't be made private here (nor internal or public), because it has to be less visible than any of those. (Actually, to confuse things further it could because it does not call the super implementation, but let's ignore that for now.)

2. How this means that you can't mandate private(call), or that it would be "a lie", simply confuses me.

3. "You can override this but you can't call it" isn't too complicated to wrap one's head around, and to my mind sums up this feature pretty succinctly.

The question is: what is the syntax at the override point? I mistakenly thought it'd require `private(call)` because I saw this example, but there's actually nothing about that in the initial proposal.

Sorry for the confusion.

···

Le 12 janv. 2016 à 21:46, cocoadev@charlessoft.com a écrit :
On 2016-01-12 17:34, Michel Fortin wrote:

--
Michel Fortin
https://michelf.ca


(Félix Cloutier) #15

Are you allowed to call `super.functionMarkedPrivateCall()`?

Félix

···

Le 12 janv. 2016 à 11:58:21, Félix Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

Le 12 janv. 2016 à 02:11:07, cocoadev@charlessoft.com a écrit :

Unless we are talking about someone hacking their way in via C or assembler or something, no, they don't have the means to do so. And if we *are* talking about hacking, then that's impossible to prevent, anyway, if someone is *really* determined. I mean, even if the stupid thing is inlined, you could still disassemble the thing and copy-and-paste out the relevant part. Making things 100% bulletproof is not the goal, and if it were, we would have been publishing every single API, private and public, in Objective-C, since in that language it was staggeringly easy, even for a relative neophyte, to find and call private methods on pretty much anything. Despite that, we still found it useful to hide things in private headers and implementation files, which suggests that access control does have a use beyond the linker level after all.

class A {
  private(call) func logicYouShouldImplementButNotCall() {
  }
}

/* other module */ class B {
  private(call) override func logicYouShouldImplementButNotCall() {
    iActuallyWantToCallItWhyWontYouLetMeDoIt()
  }
  
  func iActuallyWantToCallItWhyWontYouLetMeDoIt() {
    /* snip */
  }
}

The only un-abusable justification for the feature is to document that
a symbol shouldn't be used directly, hence the suggestion to use
documentation features to achieve that goal.

It has multiple goals:

1) To document that it shouldn't be called directly

2) To keep it from being called accidentally via code completion

3) To make it enough of a PITA to call that people will be more likely to just do things the right way.

I'm not in favor of complexifying access control for the sake of documentation. The first two goals can be implemented with just a little more collaboration between tools and documentation and you can get 90% of the way there for the third one as well.

Félix

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


(Charles Srstka) #16

Unless we are talking about someone hacking their way in via C or assembler or something, no, they don't have the means to do so. And if we *are* talking about hacking, then that's impossible to prevent, anyway, if someone is *really* determined. I mean, even if the stupid thing is inlined, you could still disassemble the thing and copy-and-paste out the relevant part. Making things 100% bulletproof is not the goal, and if it were, we would have been publishing every single API, private and public, in Objective-C, since in that language it was staggeringly easy, even for a relative neophyte, to find and call private methods on pretty much anything. Despite that, we still found it useful to hide things in private headers and implementation files, which suggests that access control does have a use beyond the linker level after all.

class A {
  private(call) func logicYouShouldImplementButNotCall() {
  }
}

/* other module */ class B {
  private(call) override func logicYouShouldImplementButNotCall() {
    iActuallyWantToCallItWhyWontYouLetMeDoIt()
  }

  func iActuallyWantToCallItWhyWontYouLetMeDoIt() {
    /* snip */
  }
}

B can also expose any private method or property that it has via a separate method.

class B {
     private func mineAllMine() {
         ohNoes()
     }

     func ohNoes() {}
}

How is that new?

Anyway, if the author of the class did that, I'd assume they had some reason for it. And if there *were* some reason to expose the logic backing the primitive method, I'd say it's better to create a new public method anyway to make your intentions clear, rather than asking users to call the method that according to your superclass's API contract isn't supposed to be called.

Class B cannot call Class *A*'s implementation of it, nor will a user of both these classes mistake logicYouShouldImplementButNotCall() as something they should use as an access point.

I'm not in favor of complexifying access control for the sake of
documentation. The first two goals can be implemented with just a
little more collaboration between tools and documentation and you can
get 90% of the way there for the third one as well.

Again, things like -[NSView display] have long been a stumbling block for newbies who often don't realize that they should be calling -setNeedsDisplay: instead until someone tells them. We can make our APIs more intuitive, and we should.

Charles

···

On 2016-01-12 11:58, Félix Cloutier wrote:

Le 12 janv. 2016 à 02:11:07, cocoadev@charlessoft.com a écrit :


(Charles Srstka) #17

I realize I was probably mistaken by an example that was not written
by you but by Félix Cloutier. If you were the one that wrote this
example in the thread then my criticisms would apply:

class A {
  private(call) func logicYouShouldImplementButNotCall() {
  }
}

/* other module */ class B {
  private(call) override func logicYouShouldImplementButNotCall() {
    iActuallyWantToCallItWhyWontYouLetMeDoIt()
  }

  func iActuallyWantToCallItWhyWontYouLetMeDoIt() {
    /* snip */
  }
}

In this example, class B is qualified `private(call)`, but it can't be
made private here (nor internal or public), because it has to be less
visible than any of those. (Actually, to confuse things further it
could because it does not call the super implementation, but let's
ignore that for now.)

The class B isn't qualified 'private(call)', but rather just the method it's overriding. The method itself would probably need to be private(call) as well, because Swift requires methods to be at least as visible as the original methods from a superclass or protocol. This bugs me, actually, since it causes your class interface to be cluttered up by stuff required for protocol conformance that could have been happily left out of the header file in Objective-C, with callers still knowing that those methods were supported due to their being present in a protocol that the class implements, or in a superclass. But that's a separate issue.

I'm still confused by how something you can override but not call could be considered less visible than something you can't override, call, or even see.

2. How this means that you can't mandate private(call), or that it would be "a lie", simply confuses me.

3. "You can override this but you can't call it" isn't too complicated to wrap one's head around, and to my mind sums up this feature pretty succinctly.

The question is: what is the syntax at the override point? I
mistakenly thought it'd require `private(call)` because I saw this
example, but there's actually nothing about that in the initial
proposal.

Sorry for the confusion.

Well, Swift's existing rules state that the overriding method needs to be at least as visible as in the superclass, so private is already out. Whether you can change it to internal or public depends on whether you feel that it should be possible to make a primitive method more visible and have people calling it. Personally, my thought on the matter is that if you want to expose that functionality, it's better to spin it off into a separate method as Félix did, but you could argue either way on that, I suppose.

In any case, the most common use case, and the one that IMO makes the most sense, would be to define the overriding method as private(call) as well.

Charles

···

On 2016-01-12 22:18, Michel Fortin wrote:


(Simon Pilkington) #18

I do wonder if this is something that can have a clean technical solution. It *feels* to me like something better served by cleaner architecture and API design - where logic that is essentially part of a delegate helper is clearly identified in a seperate protocol.

-Simon

···

On 13 Jan 2016, at 2:18 PM, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:

Le 12 janv. 2016 à 21:46, cocoadev@charlessoft.com a écrit :

On 2016-01-12 17:34, Michel Fortin wrote:

It may be a bit weird, but it is what the API contract is already asking the class to do. It's just that currently, it's not enforced other than in documentation.

What I meant here is that you can't even mandate `private` at the
override point because it's *less visible than private*. So writing
`private` or `private(call)` at the override point is a lie. You can't
define `private(call)` to mean "private access for calling this but if
there is a override keyword next to it then it means less visible than
private and you can't call it", that's too awkward.
I understand what the API contract is, but all I'm saying is that I
wonder if there is a syntax that can express all this sanely.

I've read this paragraph a large number of times, and I still don't understand what the objection is here.

1. It's not less visible than private, it's more visible by definition, because it appears in the interface and you can interact with it (by subclassing a class or implementing a protocol). If it were just plain private, it would be completely invisible and there would be no way to interact with it or even see that it exists outside of hacky things.

I realize I was probably mistaken by an example that was not written by you but by Félix Cloutier. If you were the one that wrote this example in the thread then my criticisms would apply:

class A {
  private(call) func logicYouShouldImplementButNotCall() {
  }
}

/* other module */ class B {
  private(call) override func logicYouShouldImplementButNotCall() {
    iActuallyWantToCallItWhyWontYouLetMeDoIt()
  }
  
  func iActuallyWantToCallItWhyWontYouLetMeDoIt() {
    /* snip */
  }
}

In this example, class B is qualified `private(call)`, but it can't be made private here (nor internal or public), because it has to be less visible than any of those. (Actually, to confuse things further it could because it does not call the super implementation, but let's ignore that for now.)

2. How this means that you can't mandate private(call), or that it would be "a lie", simply confuses me.

3. "You can override this but you can't call it" isn't too complicated to wrap one's head around, and to my mind sums up this feature pretty succinctly.

The question is: what is the syntax at the override point? I mistakenly thought it'd require `private(call)` because I saw this example, but there's actually nothing about that in the initial proposal.

Sorry for the confusion.

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>

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


(Michel Fortin) #19

Ok then, that's the lie. (Not that you're lying, but the syntax you propose is a lie.)

When I read `private(call)`, I read it "private visibility for calling the method", just like `private(set)` means "private visibility for the setter". A private visibility limits the usage to the current file. And the usage being limited to the current file is the ability to call the method.

When you apply `private(call)` in the base class context, the method is callable only from the current file (thus the call ability is private). That's exactly what's needed. All is good.

But when you apply `private(call)` at the override point, you want the method to not be callable from the current file. The syntax is saying the call ability is private (limited to the current file), but that's simply not true. What you really want to say here is `unavailable(call)`, but that does not exist.

···

Le 12 janv. 2016 à 22:52, cocoadev@charlessoft.com a écrit :

In any case, the most common use case, and the one that IMO makes the most sense, would be to define the overriding method as private(call) as well.

--
Michel Fortin
https://michelf.ca


#20

I am not an expert, but if we were to do anything along these lines at all,
wouldn’t it make more sense to have something like @warn_on_call with which
to annotate functions/methods that should never be called from client code
but may need to be overridden? IDE’s could hide those items from
autocomplete too.

Nevin

···

On Tue, Jan 12, 2016 at 11:16 PM, Michel Fortin via swift-evolution < swift-evolution@swift.org> wrote:

Le 12 janv. 2016 à 22:52, cocoadev@charlessoft.com a écrit :

> In any case, the most common use case, and the one that IMO makes the
most sense, would be to define the overriding method as private(call) as
well.

Ok then, that's the lie. (Not that you're lying, but the syntax you
propose is a lie.)

When I read `private(call)`, I read it "private visibility for calling the
method", just like `private(set)` means "private visibility for the
setter". A private visibility limits the usage to the current file. And the
usage being limited to the current file is the ability to call the method.

When you apply `private(call)` in the base class context, the method is
callable only from the current file (thus the call ability is private).
That's exactly what's needed. All is good.

But when you apply `private(call)` at the override point, you want the
method to not be callable from the current file. The syntax is saying the
call ability is private (limited to the current file), but that's simply
not true. What you really want to say here is `unavailable(call)`, but that
does not exist.

--
Michel Fortin
https://michelf.ca

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