Skip to content

Commit a1752a3

Browse files
committed
Update 'Member import visibility' proposal:
- Re-organize the Motivation section for clarity - Correct the explanation of Clang module re-exports
1 parent 5452236 commit a1752a3

File tree

1 file changed

+27
-31
lines changed

1 file changed

+27
-31
lines changed

proposals/NNNN-member-import-visibility.md

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,7 @@ This proposal unifies the behavior of name lookup by changing the rules to bring
2424

2525
## Motivation
2626

27-
Suppose you have a program depends on a library named `RecipeKit`. The library interface looks like this:
28-
29-
```swift
30-
// RecipeKit interface
31-
32-
public struct Recipe { /*...*/ }
33-
34-
extension String {
35-
/// Returns the recipe represented by this string.
36-
public func parse() -> Recipe?
37-
}
38-
```
39-
40-
To start, your program contains a single source file `main.swift` that imports `RecipeKit`:
27+
Suppose you have an app that depends on an external library named `RecipeKit`. To start, your app contains a single source file `main.swift` that imports `RecipeKit`:
4128

4229
```swift
4330
// main.swift
@@ -46,20 +33,20 @@ import RecipeKit
4633
let recipe = "2 slices of bread, 1.5 tbs peanut butter".parse()
4734
```
4835

49-
Later, you decide to integrate with a new library named `GroceryKit` which happens to also declares its own `parse()` method in an extension on `String`:
36+
The interface of `RecipeKit` looks like this:
5037

5138
```swift
52-
// GroceryKit interface
53-
public struct GroceryList { /*...*/ }
39+
// RecipeKit interface
40+
41+
public struct Recipe { /*...*/ }
5442

5543
extension String {
56-
/// Returns the grocery list represented by this string.
57-
public func parse() -> GroceryList?
44+
/// Returns the recipe represented by this string.
45+
public func parse() -> Recipe?
5846
}
59-
6047
```
6148

62-
You add a second file that imports `GroceryKit`:
49+
Later, you decide to integrate with a new library named `GroceryKit`. You add a second file to your app that imports `GroceryKit`:
6350

6451
```swift
6552
// Groceries.swift
@@ -69,7 +56,7 @@ var groceries = GroceryList()
6956
// ...
7057
```
7158

72-
Surprisingly, now that `GroceryKit` is a transitive dependency of `main.swift`, there's a new compilation error:
59+
Surprisingly, after adding the second file there's now a compilation error in `main.swift`:
7360

7461
```swift
7562
// main.swift
@@ -79,12 +66,26 @@ let recipe = "2 slices of bread, 1.5 tbs peanut butter".parse()
7966
// error: Ambiguous use of 'parse()'
8067
```
8168

82-
Before the new file was added, `parse()` could only refer to the extension member from `RecipeKit`. Now that it might also reference the extension member in `GroceryKit` the compiler considers the use of `parse()` to be ambiguous. To resolve the ambiguity, the developer must add a type annotation to the declaration of the variable `recipe` to give the compiler the additional context it needs to disambiguate:
69+
The call to `parse()` is now ambiguous because `GroceryKit` happens to also declares its own `parse()` method in an extension on `String`:
70+
71+
```swift
72+
// GroceryKit interface
73+
public struct GroceryList { /*...*/ }
74+
75+
extension String {
76+
/// Returns the grocery list represented by this string.
77+
public func parse() -> GroceryList?
78+
}
79+
80+
```
81+
82+
Even though `GroceryKit` was not imported in `main.swift`, its `parse()` method is now a candidate in that file. To resolve the ambiguity, you can add a type annotation to the declaration of the variable `recipe` to give the compiler the additional context it needs to disambiguate the call:
83+
8384
```swift
8485
let recipe: Recipe = "2 slices of bread, 1.5 tbs peanut butter".parse() // OK
8586
```
8687

87-
This example demonstrates why "leaky" member visibility is undesirable. Although the fix for the new error is relatively simple in this code, providing disambiguation context to the compiler is not always so straightforward. Additionally, the fact that some declarations from `GroceryKit` are now visible in `main.swift` contradicts developer expectations, since visibility rules for top level declarations do not behave this way. This idiosyncrasy in Swift's import visibility rules harms local reasoning and results in confusing errors.
88+
This example demonstrates why Swift's existing "leaky" member visibility is undesirable. Although the fix for the new error is relatively simple in this code, providing disambiguation context to the compiler is not always so straightforward. Additionally, the fact that some declarations from `GroceryKit` are now visible in `main.swift` contradicts developer expectations, since visibility rules for top level declarations do not behave this way. This idiosyncrasy in Swift's import visibility rules harms local reasoning and results in confusing errors.
8889

8990
## Proposed solution
9091

@@ -98,14 +99,9 @@ A reference to a member in a source file will only be accepted if that member is
9899
- The module is directly imported from the bridging header.
99100
- The module is in the set of modules that is re-exported by any module that is either directly imported in the file or directly imported in the bridging header.
100101

101-
A module is considered to be re-exported by the module that imports it when any of the following statements are true:
102-
103-
- The associated import statement has the `@_exported` attribute.
104-
- The exporting module is a clang module.
105-
106-
Re-exports are transitive, so if module `A` re-exports module `B`, and module `B` re-exports module `C`, then declarations from `A`, `B`, and `C` are all in scope in a file that directly imports `A`.
102+
A Swift module re-exports any modules that have been imported using the `@_exported` attribute. Clang modules list the modules that they re-export in their modulemap files, and it is common for a Clang module to re-export every module it imports using `export *`. Re-exports are also transitive, so if module `A` re-exports module `B`, and module `B` re-exports module `C`, then declarations from `A`, `B`, and `C` are all in scope in a file that only imports `A` directly.
107103

108-
Note that there are some imports that are added to every source file implicitly by the compiler for normal programs. The implicitly imported modules include the standard library and the module being compiled. As a subtle consequence of the implicit import of the current module, any module that is `@_exported` in any source file of the module is also part of the set of re-exported modules that are visible in the file.
104+
Note that there are some imports that are added to every source file implicitly by the compiler for normal programs. The implicitly imported modules include the standard library and the module being compiled. As a subtle consequence implicitly importing the current module, any module that is `@_exported` in any source file is also considered visible in every other source file because it is a re-export of a direct import.
109105

110106
## Source compatibility
111107

0 commit comments

Comments
 (0)