Skip to content

Commit 740cf1a

Browse files
authored
Merge pull request #2175 from xymus/access-level-imports-2
SE-0409: Add some information about scoped imports
2 parents 106a441 + 05c616b commit 740cf1a

File tree

1 file changed

+39
-28
lines changed

1 file changed

+39
-28
lines changed

proposals/0409-access-level-on-imports.md

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ It marks the dependency as visible to clients.
109109

110110
Current type-checking enforces that declaration respect their respective access levels.
111111
It reports as errors when a more visible declaration refers to a less visible declaration.
112-
For example, it raises an error if a public function signature uses an internal type.
112+
For example, it raises an error if a `public` function signature uses an `internal` type.
113113

114114
This proposal extends the existing logic by using the access level on the import declaration as an upper bound to the visibility of imported declarations within the source file with the import.
115115
For example, when type-checking a source file with an `internal import SomeModule`,
@@ -154,17 +154,17 @@ if a dependency is never imported publicly and other requirements are met,
154154
it becomes possible to hide the dependency from clients.
155155
The clients can then be built without loading the transitive dependency.
156156
This can speed up build times and
157-
lift the need to distribute modules that are implementation details.
157+
avoid the need to distribute modules that are implementation details.
158158

159159
The same dependency can be imported with different access levels by different files of a same module.
160-
At the module level we only take into account the most permissive access level.
161-
For example, if a dependency is imported both as `package` and `internal` from two different files,
162-
we consider the dependency to be of a `package` visibility at the module level.
160+
At the module level, we only take into account the most permissive access level.
161+
For example, if a dependency is imported as `package` and `internal` from two different files,
162+
we consider the dependency to be of `package` visibility at the module level.
163163

164-
The module level information implies different behaviors for transitive clients of the dependency.
165-
Transitive clients are modules that indirectly import that dependency.
164+
The module level information implies different behaviors for transitive clients.
165+
Transitive clients are modules that have an indirect dependency on the module.
166166
For example, in the following scenario, `TransitiveClient` is a transitive client
167-
of the `IndirectDependency` via the import of `MiddleModule`.
167+
of `IndirectDependency` via the import of `MiddleModule`.
168168

169169
```
170170
module IndirectDependency
@@ -175,45 +175,48 @@ module TransitiveClient
175175
```
176176

177177
Depending on how the indirect dependency is imported from the middle module,
178-
the transitive client may need to load it at compile time or not.
179-
There are four factors requiring a transitive dependency to be loaded,
180-
if none of these apply the dependency can be hidden.
178+
the transitive client may or may not need to load it at compile time.
179+
There are four factors requiring a transitive dependency to be loaded;
180+
if none of these apply, the dependency can be hidden.
181181

182-
1. Public or `@usableFromInline` dependencies must always be loaded by transitive clients.
182+
1. `public` or `@usableFromInline` dependencies must always be loaded by transitive clients.
183183

184184
2. All dependencies of a non-resilient module must be loaded by transitive clients.
185+
This is because types in the module can use types from those dependencies in their storage,
186+
and the compiler needs complete information about the storage of non-resilient types
187+
in order to emit code correctly.
188+
This restriction is discussed further in the Future Directions section.
185189

186-
3. Package dependencies must be loaded by transitive clients if the middle module and the transitive client are part of the same package.
187-
This allows for the signature of package declarations to reference that dependency.
190+
3. `package` dependencies of a module must be loaded by its transitive clients if the module and the transitive client are part of the same package.
191+
This is because `package` declarations in the module may use types from that dependency in their signatures.
188192
We consider two modules to be in the same package when their package name matches,
189193
applying the same logic used for package declarations.
190194

191-
4. All the dependencies must be loaded when the transitive client has a `@testable` import of the middle module.
192-
Testable clients can use internal declarations which may rely on all levels of import visibility.
193-
Even `private` and `fileprivate` dependencies must be loaded as they can contribute to the memory layout of the non-resilient internal types.
195+
4. All dependencies of a module must be loaded if the transitive client has a `@testable` import of it.
196+
This is because testable clients can use `internal` declarations, which may rely on dependencies with any level of import visibility.
197+
Even `private` and `fileprivate` dependencies must be loaded.
194198

195-
In all other cases not covered by these four factors,
196-
we consider the dependency to be hidden and it doesn't have to be loaded by transitive clients.
197-
Note that the same dependency may still be loaded for a different import path.
199+
In all other cases, the dependency is hidden, and it doesn't have to be loaded by transitive clients.
200+
Note that a dependency hidden on one import path may still need to be loaded because of a different import path.
198201

199-
The module associated with a hidden dependency doesn't need to be distributed to clients.
202+
The module interface associated with a hidden dependency doesn't need to be distributed to clients.
200203
However, the binary associated to the module still needs to be distributed to execute the resulting program.
201204

202205
### Default import access level in Swift 5 and Swift 6
203206

204207
The access level of a default import declaration without an explicit access-level modifier depends on the language version.
205208
We list here the implicit access levels and reasoning behind this choice.
206209

207-
In Swift 5 an import is public by default.
210+
In Swift 5, an import is `public` by default.
208211
This choice preserves source compatibility.
209212
The only official import previously available in Swift 5 behaves like the public import proposed in this document.
210213

211-
In Swift 6 mode an import is internal by default.
214+
In Swift 6 mode, an import is `internal` by default.
212215
This will align the behavior of imports with declarations where the implicit access level is internal.
213216
It should help limit unintentional dependency creep as marking a dependency public will require an explicit modifier.
214217

215-
As a result, the following import is public in Swift 5, and internal in Swift 6 mode.
216-
```
218+
As a result, the following import is `public` in Swift 5 and `internal` in Swift 6 mode:
219+
```swift
217220
import ADependency
218221
```
219222

@@ -223,9 +226,9 @@ Where the tool is unavailable, a simple script can insert a `public` modifier in
223226

224227
The upcoming feature flag `InternalImportsByDefault` will enable the Swift 6 behavior even when using Swift 5.
225228

226-
### Relation with other attributes on imports
229+
### Interactions with other modifiers on imports
227230

228-
The `@_exported` attribute is a step above a `public` import
231+
The `@_exported` attribute is a step above a `public` import,
229232
as clients see the imported module declarations is if they were part of the local module.
230233
With this proposal, `@_exported` is accepted only on public import declarations,
231234
both with the modifier or the default public visibility in Swift 5 mode.
@@ -242,6 +245,14 @@ After replacing with an internal import, the transitive dependency loading requi
242245
but will change for non-resilient modules where transitive dependencies must always be loaded.
243246
In all cases, updating modules relying on `@_implementationOnly` to instead use internal imports is strongly encouraged.
244247

248+
The scoped imports feature is independent from the access level declared on the same import.
249+
In the example below, the module `Foo` is a public dependency at the module level and can be referenced from public declaration signatures in the local source file.
250+
The scoped part, `struct Foo.Bar`, limits lookup so only `Bar` can be referenced from this file; it also prioritizes resolving references to this `Bar` if there are other `Bar` declarations in other imports.
251+
Scoped imports cannot be used to restrict the access level of a single declaration.
252+
```swift
253+
public import struct Foo.Bar
254+
```
255+
245256
## Source compatibility
246257

247258
To preserve source compatibility, imports are public by default in Swift 5.
@@ -273,7 +284,7 @@ it can break source compatibility in code relying of those behaviors.
273284

274285
## Future directions
275286

276-
### Hiding dependency for non-resilient modules
287+
### Hiding dependencies for non-resilient modules
277288

278289
Hiding dependencies on non-resilient modules would be possible in theory but requires rethinking a few restrictions in the compilation process.
279290
The main restriction is the need of the compiler to know the memory layout of imported types, which can depend on transitive dependencies.

0 commit comments

Comments
 (0)