Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated 
on this list have been. Directories, access controls, @exported imports... For 
comparison's sake here's one that is *really* simple and short I wrote today. 
Best of all: it requires no new access modifier. 

I also expect everyone to scream at it because it does not include all the 
desirable features of a submodule system. At the very least I'll have redefined 
the meaning of lightweight in that discussion. Good reading.

Also available here: 
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18


## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so 
you can avoid exposing every `internal` declaration within a big module to all 
other files in the module.

Not a goal: addressing the file-level access fileprivate/private or 
scoped/protected debates. This is left to other proposals.


## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also 
limits the visibility of `internal` to files with a matching `submodule` or 
`import submodule` declaration.

Submodules are never exposed outside of the module. They only change the 
visibility of internal declarations, so there is no point in exposing them 
publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.


## Details

A `submodule <name>` declaration at the beginning of a file contains an 
identifier with the submodule name:

        submodule Foo

        internal func foo() {}
        public func pub() {}

`internal` declarations within that file are only visible to other files 
sharing the same submodule name. The submodule only protects `internal` 
declarations: `public` declarations in the file are visible everywhere (in 
other submodules and in other modules).

A file can be part of more than one submodule:

        submodule Foo
        submodule Bar

        internal func achoo() {
                foo() // available in Foo (from other file)
        }

This makes the internal `achoo` function visible within both the `Foo` and 
`Bar` submodules. Also note that it makes internal members of both submodules 
`Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose 
its own internal functions to the submodule with `import submodule`:

        submodule Test
        import submodule Foo

        internal func test() {
                foo() // internal, imported from Foo
                achoo() // internal, imported from Foo
                pub() // public, so always visible
        }

Finally, when a file has no submodule declaration, its internal declarations 
are visible everywhere in the module and all its submodules:

        --- Hello.swift ---
        // no submodule declaration
        internal func hello() {}

        --- World.swift ---
        submodule World
        internal func test() {
                hello() // visible!
        }


## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules 
is not a conflict:

        --- Foo1.swift ---
        submodule Foo1
        class Foo {} // added to Foo1

        --- Foo2.swift ---
        submodule Foo2
        submodule Foo3
        class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` 
declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You 
can disambiguate using the submodule name as a prefix:

        --- Main.swift ---
        import submodule Foo1
        import submodule Foo2
        import submodule Foo3
        let f0 = Foo() // ambiguity error
        let f1 = Foo1.Foo() // good
        let f2 = Foo2.Foo() // good
        let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.


## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could 
continue to disallow conflicting `internal` declarations even when they belong 
to different submodules. This would make the design simpler, as it is closer to 
how `internal` currently works and prevent ambiguity errors from arising when 
importing multiple submodules. The behavior would be a little surprising 
however.

We could also simplify by removing the ability to use the submodule name as a 
prefix to disambiguate. This has the advantage that if you put a type inside of 
a submodule with the same name, no conflict can arise between the name of the 
type and the name of the submodule. Disambiguation would have to be done by 
renaming one of the conflicting declarations. Since this ambiguity can only 
affect `internal` declarations (submodules only group internal declarations), 
requiring a rename will never break any public API. But forcing a rename is not 
a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name 
could make it clearer that we are giving access to internal declarations of the 
submodule. But it also make the import less relatable to the `submodule` 
declaration in other files.


## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this 
proposal, the belief is that this proposal can live without it, and not having 
this greatly reduce the little details to explore and thus simplifies the 
design.

In many cases you can work around this by putting "private" stuff in a separate 
submodule (somewhat similar to private headers in C land). For instance:

        --- Stuff.swift ---
        submodule Stuff
        submdoule StuffImpl

        func pack() { packImpl() }

        --- StuffImpl.swift ---
        submodule StuffImpl

        func packImpl() { ... }

This will not work for stored properties however. A future proposal could 
suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of 
some declarations to only those files that are part of a specific module. 


-- 
Michel Fortin
https://michelf.ca

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

Reply via email to