[Draft] @intendedcontext to hint at symbol relevance


(Félix Cloutier) #1

Hi all,

I'd like to share this draft for an @intendedcontext attribute: https://gist.github.com/zneak/e53494c38bb3739201ac

The purpose, broadly, is to help tools determine whether a (technically) visible symbol should be used where you have your cursor at or not. It could help with shorter autocompletion lists and linters.

Félix


(Janosch Hildebrand) #2

Hi Félix,

Perhaps it might make more sense to model such non-binding extra information as doc comment fields instead of attributes.

From swift/Changelog.md:

Three new doc comment fields, namely - keyword:, - recommended: and - recommendedover:, allow Swift users to cooperate with code completion engine to deliver more effective code completion results. The - keyword: field specifies concepts that are not fully manifested in declaration names. - recommended: indicates other declarations are preferred to the one decorated; to the contrary, - recommendedover: indicates the decorated declaration is preferred to those declarations whose names are specified.

This seems to be similar in spirit and I think it makes sense to separate such annotations from actual code.

···

On 08 Jan 2016, at 00:15, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Hi all,

I'd like to share this draft for an @intendedcontext attribute: https://gist.github.com/zneak/e53494c38bb3739201ac

The purpose, broadly, is to help tools determine whether a (technically) visible symbol should be used where you have your cursor at or not. It could help with shorter autocompletion lists and linters.

Félix

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

- Janosch


(Félix Cloutier) #3

I wasn't aware of that. I'll revisit it some time later today or tomorrow.

Félix

···

Le 7 janv. 2016 à 18:30:24, Janosch Hildebrand <jnosh@jnosh.com> a écrit :

Hi Félix,

Perhaps it might make more sense to model such non-binding extra information as doc comment fields instead of attributes.

From swift/Changelog.md:

Three new doc comment fields, namely - keyword:, - recommended: and - recommendedover:, allow Swift users to cooperate with code completion engine to deliver more effective code completion results. The - keyword: field specifies concepts that are not fully manifested in declaration names. - recommended: indicates other declarations are preferred to the one decorated; to the contrary, - recommendedover: indicates the decorated declaration is preferred to those declarations whose names are specified.

This seems to be similar in spirit and I think it makes sense to separate such annotations from actual code.

On 08 Jan 2016, at 00:15, Félix Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi all,

I'd like to share this draft for an @intendedcontext attribute: https://gist.github.com/zneak/e53494c38bb3739201ac

The purpose, broadly, is to help tools determine whether a (technically) visible symbol should be used where you have your cursor at or not. It could help with shorter autocompletion lists and linters.

Félix

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

- Janosch


(Félix Cloutier) #4

Hi all,

I finally updated my proposal to use a documentation tag instead of an attribute (thanks Janosch): https://gist.github.com/zneak/e53494c38bb3739201ac

Félix

···

---

# `intendeduse` Documentation Attribute

* Proposal: SE-NNNN
* Author(s): [Félix Cloutier](https://github.com/zneak)
* Status: **Draft**
* Review manager: TBD

## Introduction

This proposal suggests a new documentation field that can be used to hint that a symbol is relevant only in certain contexts. This hint can be picked up by tools (like SourceKit) and analyzers (like [SwiftLint][1]) to offer better diagnostics or less irrelevant autocomplete suggestions.

## Motivation

Swift does not adopt typical object-oriented access modifiers. Instead, its `public`, `internal` and `private` modifiers map closer to the linker's notion of visibility. While this reduces surprises about what's accessible and what isn't in a native program, in some scenarios it will make symbols discoverable in locations where they aren't helpful, cluttering autocomplete suggestions at best and misleading developers at worst.

There are several relevant scenarios where one could prefer to show a member in specific contexts only. For instance:

* Some Swift users like to separate a class's implementation across several files by defining the core in one and using extensions in the others. This pattern is also popular in Objective-C, where class implementations can be split into multiple categories. Other languages have similar notions: for instance, C# supports "partial classes" which do essentially the same thing. However, using this pattern in Swift, some class members members that could otherwise be marked `private` now need to be marked `internal` to be reachable from extension files. The members now show up as autocompleting suggestions in every file of the project, even though the developer only cares about them in a few select files. ([swift-evolution thread][2])
* Even though Swift generally does not encourage classes to grow into a hierarchy, it is sometimes useful to have functions that are meant to be called from subclasses. These currently have to be marked `internal` or `public` because Swift does not have a `protected` modifier. Even though there is no intention to enforce more specific access control with this proposal, a member marked "for subclasses" is useful documentation to other developers.
* Some classes, like NSView, expose significant API surface for customization. It can become overwhelming, to the point where developers are pushed to make a chart of which method are intended for external use and which methods are intended to be merely overridden (and not called directly). All these methods will clutter autocomplete and obscure which ones should be called directly. ([swift-evolution thread][5])
* Some Swift users interoperate with Objective-C to a degree large enough that it makes sense to have API entry points better suited for one language or the other. On its side, Apple added an Objective-C attribute called `swift_private` that indicates that a symbol can be used from Objective-C but not from Swift. On the Swift side, some developers would like to be able to mark an entry point as meant specifically for Objective-C, simply because a better one exists for Swift. The Objective-C-specific entry point will however show side-by-side with the Swift one because there's no way to mask it. If it becomes possible to call Swift code from C or any other language, it may eventually become a problem there as well. ([swift-evolution thread][4])
* Tools generating Swift code may find it useful to mark members as "tool-private": members that need to have a looser access modifier for technical reasons but that shouldn't be used directly by the client. This is the use case that .NET's similar [`EditorBrowsable`][3] attribute supports.

## Proposed solution

This proposal suggests a new `- intendeduse` (name to be bikeshedded in typical Internet fashion) documentation tag that hints whether a member should be used in a certain context or not. It is not meant to implement access control: the compiler must not restrict access to the member based on it. It is meant to document symbols and help tools offer better suggestions.

It exists in the spirit of the new-ish `keyword`, `recommended` and `recommendedover` documentation tags that allow "[to cooperate with code completion engine to deliver more effective code completion results][6]".

The documentation tag would accept a context parameter and an optional message that explains why the symbol is not intended to be used in the full extent of its access modifier.

Tools like SourceKit and SwiftLint would be responsible for how they wish to interpret the hint. For instance, Xcode could hide or strike members that don't belong. SwiftLint could detect violations and report them, showing the message parameter (if any).

## Detailed design

The basic syntax would be:

    /// - intendeduse: `context` (`optional message`)

Backticks are used as delimiters here and are not intended to be part of the syntax. For instance:

    /// - intendeduse: private (don't call me directly!)

Context values could be one of:

  * `extension`: this type member is intended to be used from extensions.
  * `interop`: this symbol is intended to be used from other languages that interface with Swift.
  * `override`: this symbol is intended to be overridden only.
  * `private`: this symbol is not intended to be used directly.
  * `subclass`: this type member is intended to be used from subclasses.

The `extension` context can only be used on type members. A member with this intended context is considered "in context" if it is used from a file that defines an extension to its host type.

The `interop` context can be used on any non-private symbol [somebody remind me if mixing Swift and Objective-C in the same target allows Objective-C to access `internal` members?]. These symbols are never considered "in context" when used in Swift code.

The `override` context can be used on any virtual symbol. These symbols are intended to be overridden by subclasses to implement customized behavior. These symbols are considered "in context" when editing the declaring class or an extension to it.

The `private` context can be used on any non-private symbol. These symbols are never considered "in context". [The paradox is that they will be used somewhere, otherwise they wouldn't be needed at all. While it's pretty clear that these shouldn't show up in autocompletion lists, it's unclear how a linter should react to it.]

The `subclass` context can be used on any class member. These symbols are considered "in context" when they're used from the file that declares the base class and any other file that declares a subclass.

## Possible future directions

In the future, this documentation tag could perhaps take over the role of `@deprecated`. `@available` is also fundamentally documentation, but it needs to abort compilation because the symbol isn't available, so it is less likely within the scope of a documentation attribute.

Through some sort of convention, it could become possible to offer fix-it hints for some out-of-context uses.

One solution to the `private` gambit would be to allow user-defined contexts. A tool could emit code that uses a string as a context name and specify that some file, class, function, or other code unit can rightfully use it:

    /// - intendeduse: supertool (don't use me directly!)
    func leaveMeAlone() { ... }
    
    @context("supertool")
    func publicEntryPoint() {
      leaveMeAlone()
    }

## Impact on existing code

This change does not alter any existing behavior.

## Alternatives considered

The most obvious alternative is to do nothing about it. Even though it is not the author's opinion, it can be argued that the Swift language doesn't need an attribute that looks like it wants to provide finer-grained access control but doesn't really do it.

Other alternatives considered were the piecewise solutions that the three aforementioned swift-evolution threads considered:

  * An attribute that hides the entry point from Swift entirely, causing an error when it is used. However, completely hiding the entry point makes the attribute less useful in the context of gradually porting an Objective-C program or library to Swift. If creating Swift-ier entry points is a low-priority task, we can assume that existing Swift code uses to-be Objective-C entry points; therefore, hiding them would force more modifications than may be desirable.
  * A new access modifier, like `private(extension)`, that acts like `internal` in files that declare an extension to the owning type but `private` elsewhere. This modifier's behavior does not align with linker visibility and would be significantly different from what currently exists.
  * A new access modifier, like `private(call)`, that means that a member can be overridden but not called. This modifier's behavior does not align with linker visibility and would be significantly different from what currently exists.

  [1]: https://github.com/realm/SwiftLint
  [2]: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/005446.html
  [3]: https://msdn.microsoft.com/en-us/library/system.componentmodel.editorbrowsableattribute(v=vs.110).aspx
  [4]: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/005406.html
  [5]: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160111/006321.html
  [6]: https://github.com/apple/swift/blob/master/CHANGELOG.md