Hello all,

TJ Usiyan, Harlan Haskins, and I have been working on a proposal to rework 
qualified imports and introduce an explicit module system to Swift that we’d 
like to publish for your viewing pleasure.

The initial impetus was set out in a radar (rdar://17630570) I sent fairly 
early on that didn’t receive a response, so I started a swift-evolution 
<http://permalink.gmane.org/gmane.comp.lang.swift.evolution/1378> thread 
discussing the basics of this proposal.  It has been refined and expanded a bit 
to include an effort to make Swift modules explicit and updated with the 
feedback of that first thread.  Contents of the proposal are inline and can 
also be had as a gist 
<https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6> or on Github. 
<https://github.com/apple/swift-evolution/pull/440>

Cheers,

~Robert Widmann

Qualified Imports and Modules

Proposal: SE-NNNN 
<https://gist.github.com/CodaFi/NNNN-first-class-qualified-imports.md>
Authors: Robert Widmann <https://github.com/codafi>, Harlan Haskins 
<https://github.com/harlanhaskins>, TJ Usiyan <https://github.com/griotspeak>
Status: Awaiting review
Review manager: TBD
 
<https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#introduction>Introduction

We propose a complete overhaul of the qualified imports syntax and semantics 
and the introduction of a module system.

 
<https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#motivation>Motivation

Swift code is modular by default. However, it is not clear how to decompose 
existing modules further into submodules. In addition, it is difficult to tell 
how importing a module affects its export to consumers of a library. This leads 
many to either fake namespaces with enums, attempt to structure Swift code with 
modulemaps, or use a large amount of version-control submodules. All of these 
can be rolled into one complete package in the form of a comprehensive rethink 
of the qualified import system and the introduction of a module system.

 
<https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#proposed-solution>Proposed
 solution

Modules will now become an explicit part of working with canonical Swift code. 
The grammar and semantics of qualified imports will change completely with the 
addition of import qualifiers and import directives. We also introduce three 
new contextual keywords: using, hiding, and renaming, to facilitate 
fine-grained usage of module contents.

 
<https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#detailed-design>Detailed
 design

Qualified import syntax will be revised to the following

module-decl -> module <module-path>
import-decl -> <access-level-modifier> import <module-path> <(opt) 
import-directive-list>
module-path -> <identifier>
            -> <identifier>.<import-path>
import-directive-list -> <import-directive>
                      -> <import-directive> <import-directive-list>
import-directive -> using (<identifier>, ...)
                 -> hiding (<identifier>, ...)
                 -> renaming (<identifier>, to: <identifier>, ...)
This introduces the concept of an import directive. An import directive is a 
file-local modification of an imported identifier. A directive can be one of 3 
operations:

1) using: The using directive is followed by a list of identifiers within the 
imported module that should be exposed to this file. 

// The only visible parts of Foundation in this file are 
// Date.init(), Date.hashValue, and Date.description.
import Foundation.Date using (Date.init(), Date.hashValue, Date.description)
2) hiding: The hiding directive is followed by a list of identifiers within the 
imported module that should be hidden from this file.

// Imports all of Foundation.Date except `Date.compare()`
import Foundation.Date hiding (Date.compare())
3) renaming: The renaming directive is followed by a list of identifiers 
separated by to: that should be exposed to this file but renamed. 

// Imports all of Dispatch.DispatchQueue but renames the static member 
// DispatchQueue.main, to DispatchQueue.mainQueue
import Dispatch.DispatchQueue renaming (DispatchQueue.Type.main to: 
DispatchQueue.Type.mainQueue)
// Renaming can also rename modules.  All members of UIKit have to be qualified 
with
// `UI` now.
import UIKit renaming (UIKit, to: UI)
Import directives chain to one another and can be used to create a fine-grained 
module import:

// Imports all of Foundation except `DateFormatter` and renames `Cache` to 
`LRUCache`
import Foundation hiding (DateFormatter) renaming (Cache to: LRUCache)
// Imports SCNNode except SCNNode.init(mdlObject:) and renames `.description` to
// `.nodeDescription` 
import SceneKit using (SCNNode) 
                renaming (SCNNode.description, to: SCNNode.nodeDescription)
                hiding (SCNNode.init(mdlObject:))
Directive chaining occurs left-to-right:

// This says to 1) Hide nothing 2) Use nothing 3) rename Int to INT.  It is 
invalid
// because 1) We will show everything 2) Then hide everything 3) Therefore Int 
is unavailable, error.
import Swift hiding () using () renaming (Int, to: INT)
// This says to 1) Use Int 2) Hide String 3) rename Double to Triple.  It is 
invalid
// because 1) Int is available 2) String is not, error. 3) Double is 
unavailable, error.
import Swift using (Int) hiding (String) renaming (Double, to: Triple)
// Valid.  This will be merged as `using (Int)`
import Swift using () using (Int)
// Valid.  This will be merged as `hiding (String, Double)`
import Swift hiding (String) hiding (Double) hiding ()
// Valid (if redundant). This will be merged as `using ()`
import Swift using (String) hiding (String)
Module scope is delimited by the keyword module followed by a fully qualified 
name and must occur as the first declaration in a file. For example:

// ./Math/Integers/Arithmetic.swift
module Math.Integers.Arithmetic

public protocol _IntegerArithmetic {}

public struct _Abs {}

@_versioned
internal func _abs<Args>(_ args: Args) -> (_Abs, Args) {}

// ./Math/Integers.swift
module Math.Integers

// _abs is visible in this module and all others within the project, 
// but is not exported along with it.
internal import Math.Integers.Arithmetic

public protocol IntegerArithmetic : _IntegerArithmetic, Comparable {}
public protocol SignedNumber : Comparable, ExpressibleByIntegerLiteral {}


// Math.swift
module Math

// Exports the entire public contents of Math.Integers, but nothing in 
// Math.Integers.Arithmetic.
public import Math.Integers
Modules names are tied to a directory structure that describes their location 
relative to the current module and it will now be an error to violate this 
rule. For example:

module String // lives in ./String.swift
module String.Core // lives in ./String/Core.swift
module String.Core.Internals.Do.You.Even.Write // lives in 
./String/Core/Internals/Do/You/Even/Write.swift
Existing projects that do not adopt these rules will still retain their 
implicit module name (usually defined as the name of the framework or 
application that is being built) and may continue to use whatever directory 
structure they wish, however they may not declare any explicit modules.

This proposal also solves the problem of module export. A module that is 
imported without an access level modifier will default to an internal import 
per usual. However, when it is useful to fully expose the public content of 
submodules to a client, a public modifier can be used. Similarly, when it is 
useful to access internal or [file]private APIs, but not expose them to 
clients, those access modifiers may be used. The rule of thumb is: Only 
identifiers that are at least as visible as the qualifier on the import make 
for valid import declarations. For example:

// A submodule declaring a `private` class that gets imported with 
// an `internal` qualifier with a `using` directive is an invalid import 
// declaration.  
module Foo.Bar

private class PrivateThing {}

module Foo

// Error: PrivateThing not visible, use `private import`
import Foo.Bar using (PrivateThing) 
// However, a submodule declaring a `public` struct that gets imported with 
// an `private` qualifier is a valid import declaration.
module Foo.Bar

public class PublicThing {}

module Foo

// All good!  Foo can see Foo.Bar.PrivateThing.
private import Foo.Bar using (PublicThing) 
Because import directives are file-local, they will never be exported along 
with a public import and will default to exporting the entire contents of the 
module as though you had never declared them.

// In this file and this file alone, the directives apply.  To the user
// of this module, it is as though this declaration were simply:
// public import Foundation.Date
public import Foundation.Date hiding (Date.init()) 
                              renaming (Date.Type.distantPast, 
                                        to: Date.Type.letsGoLivingInThePast,
                                        
Date.Type.timeIntervalSinceReferenceDate, 
                                        to: Date.Type.startOfTheUniverse)
                              renaming (Date.Type.<, to: Date.Type.<<<<<)
 
<https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#impact-on-existing-code>Impact
 on existing code

Existing code that is using qualified module import syntax (import 
{func|class|typealias|class|struct|enum|protocol} <qualified-name>) will be 
deprecated. Code that is not organized into modules will remain unaffected and 
organized into one contiguous top-level module. However, it is strongly 
recommended that frameworks be decomposed and reorganized around the new module 
system.

As a case study, the public interface to the standard library appears to 
already be mostly broken down into submodules as described in GroupInfo.json 
<https://github.com/apple/swift/blob/master/stdlib/public/core/GroupInfo.json>.

Code that is defined in modulemaps already defines a module structure that can 
be imported directly into this scheme.

 
<https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#alternatives-considered>Alternatives
 considered

Module export can also be placed on the module declaration itself. The relevant 
parts of the grammar that have changed are below with an example:

module-decl -> <access-level-modifier> module <module-path>
import-decl -> import <module-path> <(opt) import-directive-list>
private module String.Core.Internals

// Shh, it's a secret.
While this style makes it immediately obvious to the library author which 
modules are public or private, it causes the consumer problems because 
submodule exports are no longer explicit and are entirely ad-hoc. In the 
interest of enabling, for one, users of IDEs to drill into public submodules, 
making export local to import seems more appropriate.
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to