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/NNNN-member-import-visibility.md
+27-31Lines changed: 27 additions & 31 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -24,20 +24,7 @@ This proposal unifies the behavior of name lookup by changing the rules to bring
24
24
25
25
## Motivation
26
26
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
-
publicstructRecipe { /*...*/ }
33
-
34
-
extensionString {
35
-
/// Returns the recipe represented by this string.
36
-
publicfuncparse() -> 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`:
41
28
42
29
```swift
43
30
// main.swift
@@ -46,20 +33,20 @@ import RecipeKit
46
33
let recipe ="2 slices of bread, 1.5 tbs peanut butter".parse()
47
34
```
48
35
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:
50
37
51
38
```swift
52
-
// GroceryKit interface
53
-
publicstructGroceryList { /*...*/ }
39
+
// RecipeKit interface
40
+
41
+
publicstructRecipe { /*...*/ }
54
42
55
43
extensionString {
56
-
/// Returns the grocery list represented by this string.
57
-
publicfuncparse() ->GroceryList?
44
+
/// Returns the recipe represented by this string.
45
+
publicfuncparse() ->Recipe?
58
46
}
59
-
60
47
```
61
48
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`:
63
50
64
51
```swift
65
52
// Groceries.swift
@@ -69,7 +56,7 @@ var groceries = GroceryList()
69
56
// ...
70
57
```
71
58
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`:
73
60
74
61
```swift
75
62
// main.swift
@@ -79,12 +66,26 @@ let recipe = "2 slices of bread, 1.5 tbs peanut butter".parse()
79
66
// error: Ambiguous use of 'parse()'
80
67
```
81
68
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
+
publicstructGroceryList { /*...*/ }
74
+
75
+
extensionString {
76
+
/// Returns the grocery list represented by this string.
77
+
publicfuncparse() -> 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
+
83
84
```swift
84
85
let recipe: Recipe ="2 slices of bread, 1.5 tbs peanut butter".parse() // OK
85
86
```
86
87
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.
88
89
89
90
## Proposed solution
90
91
@@ -98,14 +99,9 @@ A reference to a member in a source file will only be accepted if that member is
98
99
- The module is directly imported from the bridging header.
99
100
- 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.
100
101
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.
107
103
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.
0 commit comments