[swift-evolution] [Pitch] Let's talk about submodules

Brent Royal-Gordon brent at architechies.com
Mon Feb 20 19:36:58 CST 2017

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.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.



Brent Royal-Gordon

More information about the swift-evolution mailing list