You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: proposals/0409-access-level-on-imports.md
+39-28Lines changed: 39 additions & 28 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -109,7 +109,7 @@ It marks the dependency as visible to clients.
109
109
110
110
Current type-checking enforces that declaration respect their respective access levels.
111
111
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.
113
113
114
114
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.
115
115
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,
154
154
it becomes possible to hide the dependency from clients.
155
155
The clients can then be built without loading the transitive dependency.
156
156
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.
158
158
159
159
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.
163
163
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.
166
166
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`.
168
168
169
169
```
170
170
module IndirectDependency
@@ -175,45 +175,48 @@ module TransitiveClient
175
175
```
176
176
177
177
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.
181
181
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.
183
183
184
184
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.
185
189
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.
188
192
We consider two modules to be in the same package when their package name matches,
189
193
applying the same logic used for package declarations.
190
194
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.
194
198
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.
198
201
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.
200
203
However, the binary associated to the module still needs to be distributed to execute the resulting program.
201
204
202
205
### Default import access level in Swift 5 and Swift 6
203
206
204
207
The access level of a default import declaration without an explicit access-level modifier depends on the language version.
205
208
We list here the implicit access levels and reasoning behind this choice.
206
209
207
-
In Swift 5 an import is public by default.
210
+
In Swift 5, an import is `public` by default.
208
211
This choice preserves source compatibility.
209
212
The only official import previously available in Swift 5 behaves like the public import proposed in this document.
210
213
211
-
In Swift 6 mode an import is internal by default.
214
+
In Swift 6 mode, an import is `internal` by default.
212
215
This will align the behavior of imports with declarations where the implicit access level is internal.
213
216
It should help limit unintentional dependency creep as marking a dependency public will require an explicit modifier.
214
217
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
217
220
importADependency
218
221
```
219
222
@@ -223,9 +226,9 @@ Where the tool is unavailable, a simple script can insert a `public` modifier in
223
226
224
227
The upcoming feature flag `InternalImportsByDefault` will enable the Swift 6 behavior even when using Swift 5.
225
228
226
-
### Relation with other attributes on imports
229
+
### Interactions with other modifiers on imports
227
230
228
-
The `@_exported` attribute is a step above a `public` import
231
+
The `@_exported` attribute is a step above a `public` import,
229
232
as clients see the imported module declarations is if they were part of the local module.
230
233
With this proposal, `@_exported` is accepted only on public import declarations,
231
234
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
242
245
but will change for non-resilient modules where transitive dependencies must always be loaded.
243
246
In all cases, updating modules relying on `@_implementationOnly` to instead use internal imports is strongly encouraged.
244
247
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
+
publicimportstructFoo.Bar
254
+
```
255
+
245
256
## Source compatibility
246
257
247
258
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.
273
284
274
285
## Future directions
275
286
276
-
### Hiding dependency for non-resilient modules
287
+
### Hiding dependencies for non-resilient modules
277
288
278
289
Hiding dependencies on non-resilient modules would be possible in theory but requires rethinking a few restrictions in the compilation process.
279
290
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