Okay, lots of people want to have some kind of submodule feature, so I'd like 
to sketch one out so we can hopefully agree on what submodules might look like.

***

Any group of Swift files can be grouped together to form a submodule. 
Submodules belong within a particular module, and have a dotted name: If 
`ModKit` is a module, it might have a submodule called `ModKit.Foo`. Submodules 
can be nested within one another: `ModKit.Foo.Bar` is a submodule of 
`ModKit.Foo`, which is a submodule of `ModKit`.

No new access levels are necessary. `internal` APIs are only visible within the 
submodule they're declared in; a module cannot see its submodules' `internal` 
APIs, and a submodule cannot see its parent module's `internal` APIs. If a 
submodule wants to expose some of its APIs to its parent or sibling modules, it 
must mark them as `public` or `open`. Then they can import the submodule to see 
its APIs:

        import ModKit.Foo

By default, outside modules cannot import a submodule. But an import in the 
parent module can be decorated by an access control keyword to allow that:

        /// Any module outside ModKit can import ModKit.Foo and access its 
`public` and `open` APIs.
        open import ModKit.Foo

        /// Any module outside ModKit can import ModKit.Foo and access its 
`public` and `open` APIs, 
        /// except `open` APIs are treated as `public`.
        public import ModKit.Foo

Imports may also be decorated by the `@exported` attribute, which exposes the 
submodule's APIs as though they were parent module APIs:

        @exported open import ModKit.Foo

        @exported public import ModKit.Foo

(This is sort of half-implemented already in a buggy `@_exported` attribute.)

Finally, the standard syntax for importing individual symbols can be used to 
cherry-pick types to treat differently:

        // Most ModKit.Foo APIs are not importable...
        import ModKit.Foo

        // ...but SomeEnum can be imported as public...
        public import enum ModKit.Foo.SomeEnum

        // ...SomeClass can be imported as open...
        open import class ModKit.Foo.SomeClass

        // And ImportantStruct will import whenever you import ModKit.
        @exported public import struct ModKit.Foo.ImportantStruct

(This syntax should be enhanced to allow cherry-picked importing of global 
functions, constants, and variables.)

If there are several different `import`s covering the same submodule or 
submodule symbol, the most permissive one wins.

(In large projects, `public`, `open`, and `@exported` imports will most likely 
all be put in a single Policy.swift file or something, but this is not enforced 
by the language.)

A submodule may not import any direct parent module (parent, grandparent, 
etc.), but may import any other submodule in the same module. This list shows 
permitted imports for a project with four modules/submodules:

        ModKit
                - ModKit.Foo
                - ModKit.Foo.Bar
                - ModKit.Quux
        ModKit.Foo
                - ModKit.Foo.Bar
                - ModKit.Quux
        ModKit.Foo.Bar
                - ModKit.Quux
        ModKit.Quux
                - ModKit.Foo
                - ModKit.Foo.Bar

However, submodules may not form circular dependencies through imports—if 
`ModKit.Quux` imports `ModKit.Foo`, then `ModKit.Foo` cannot import 
`ModKit.Quux`. The `#if canImport()` feature cannot be used to probe for other 
submodules within the same top-level module you're in.

At the compiler driver level, a submodule is specified by giving a 
`-module-name` parameter with a dot in it. When a file is compiled, only the 
filenames of the other .swift files in the same module are specified, along 
with .o files for any submodules; then all the .o files within that submodule 
are linked into a single .o file for the whole submodule. So files in 
`ModKit.Foo` would be compiled with only the .swift files in `ModKit.Foo` and 
the .o file for `ModKit.Foo.Bar`; then all the `ModKit.Foo` .o files would be 
linked into one .o file for the top-level `ModKit` to use. None of 
`ModKit.Foo`'s .swift files would be included in the command line when 
compiling the top-level `ModKit` module.

(That bit is kind of speculative—particularly the idea of linking submodule 
files into a single .o file—but I think something like what I'm describing 
could work.)

Because the compiler driver is used to group submodules together, Xcode can 
specify submodules in project file metadata and calculate a submodule 
dependency graph, while SwiftPM can use folders and compile submodules whenever 
the compiler emits an error indicating that a file tried to import a 
nonexistent submodule. Other build systems can do whatever best suits their 
style.

***

Thoughts?

-- 
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to