[Accepted with modifications] SE-0409: Access-level modifiers on import declarations

Hi all,

The review of SE-0409: Access-level modifiers on import declarations concluded on September 26th.

The Language Steering group has decided to accept the proposal with a single modification. Namely, in addition to access level modifiers (internal, public, etc.), import declarations will be able to be marked with @usableFromInline to mark an imported module whose declarations should be available for use in the body of @inlinable functions, but still restricted from appearing in the public interface of the importing module. The Language Steering group agreed that it did not make sense to force users to abandon the interface-level checking provided by this proposal (by making the import a public import) just to use a declaration from an inlinable function body.

Overall reception of the feature was generally positive, at least as to the problem raised being worth solving. There were some questions raised as to whether the proposed solution was on the right track.

Some reviewers wondered whether it was appropriate for global property such as "is this module a public dependency or an implementation detail" to be inferred based on the upper bound of it's imported visibility across all source-file-local import declarations, and suggested that marking dependencies with this information in, say, the package manifest might be a better approach. While the Language Steering Group does not rule out the possibility of some sort of marking in the build system which could enforce "this module cannot be imported at higher than internal visibility," the Group felt that:

  • The ultimate effect of different import access levels was significant enough to warrant some sort of marking in the source file, so that users reading code can easily see which dependencies in the file are permitted to be used in the interface with the corresponding access level(s).
  • The problem being solved is sufficiently a language-level concern that it should not be only the responsibility of the build system to support/enforce dependency visibility—it should be a first-class language feature that Swift itself allows programmers to express regardless of the build system they are using.

Accepting that some source-level marker (attached to import declarations) was appropriate, the Language Steering Group considered the concerns about the potential unclarity of the public import Foo spelling. Some reviewers felt that this spelling incorrectly implied behavior more similar to @_exported than what was actually proposed. That is, reviewers expected that public import Foo would 're-export' the contents of the Foo module for use from the importing module's namespace. One reviewer noted that perhaps a more accurate, if more awkward, spelling would be @usableFromPublic, @usableFromInline, etc.

The Language Steering Group acknowledges that the public in public import Foo has a different meaning than the public in, say, public struct Bar. However, the Group elected to stick with the proposed spelling for the following reasons:

  • The use of public import in library code is likely to be relatively pervasive, and a more heavyweight, explicit spelling like @usableFromPublic or @usableFrom(public) or similar would quickly become too noisy.
  • The possible misinterpretation is relatively benign: users who expect public import Foo to re-export Foo's contents from their own module will quickly discover that this is not, in fact, its function, and then be able to correct their usage in whatever way they choose (e.g., by applying @_exported).
  • Even if we were to formalize @_exported as an official language feature, we would not want to use the public import Foo spelling for such a specialized operation with such wide-reaching implications, and so using the public import spelling for the behavior proposed here does not 'close off' its use for different functionality in a way that is concerning.

There were some further questions and concerns raised about details of the proposal:

  • Some felt that the inclusion of the private access level for import statements was unnecessarily fine-grained. The Language Steering group felt that the use case of a wrapper file around a library that an author does not want to accidentally 'leak' to the rest of the module was sufficiently justification for private import, and that the alternative of factoring that file out into a separate module was too burdensome to be a suitable solution.
  • Others noted that private and fileprivate were redundant in this proposal because import declarations can only appear at file scope, and suggested that the language should just pick one to be used with import declarations and ban the other. The Language Steering group does not feel that an arbitrary restriction here is warranted, and that import declarations should be able to be marked as either private or fileprivate just like any other top-level declaration.

Thank you for participation in the evolution process!

Frederick Kellison-Linn
Review Manager

31 Likes

Have I understood correctly that internal, package or public imports will not allow for usage of the imported module’s symbols from other files within the same module as the file with the import statement? I currently use @_exported import in a single, dedicated file in each module to avoid having to rewrite the import declarations in each file of the module. Is this considered an anti-pattern? Will @_exported internal import Foo provide the symbols of Foo to all files in the module without exposing Foo to clients of the module?