Skip to content

Commit 5452236

Browse files
committed
Update the text of the 'Member import visibility' proposal to address feedback from the Swift Evolution forums.
1 parent e1069b7 commit 5452236

File tree

1 file changed

+26
-12
lines changed

1 file changed

+26
-12
lines changed

proposals/NNNN-member-import-visibility.md

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ let chained = chain([1], [2]) // error: Cannot find 'chain' in scope
2020

2121
The visibility rules for a member declaration, such as a method declared inside of a struct, are different though. When resolving a name to a member declaration, the member is in scope even if the module introducing the member is only *transitively* imported. A transitively imported module could be imported directly in another source file, or it could be a dependency of some direct dependency of your program. This inconsistency may be best understood as a subtle bug rather than an intentional design decision, and in a lot of Swift code it goes unnoticed. However, the import rules for members become more surprising when you consider the members of extensions, since an extension and its nominal type can be declared in different modules.
2222

23-
This proposal unifies the behavior of name lookup by changing the rules to consistently require direct module imports in order to bring both top-level declarations and members into scope.
23+
This proposal unifies the behavior of name lookup by changing the rules to bring both top-level declarations and members into scope using the same criteria.
2424

2525
## Motivation
2626

@@ -88,35 +88,49 @@ This example demonstrates why "leaky" member visibility is undesirable. Although
8888

8989
## Proposed solution
9090

91-
In a future language version, or whenever the `MemberImportVisibility` feature is enabled, the Swift compiler should only consider members from directly imported modules to be in scope.
91+
In a future language version, or whenever the `MemberImportVisibility` feature is enabled, both member declarations and top level declarations should be resolved from the same set of visible modules in a given source file.
9292

9393
## Detailed design
9494

95-
Excluding references to members from transitively imported modules in the local scope will prevent surprising ambiguities like the one detailed earlier. However, this change of behavior will also break some existing code that used to be accepted. The Swift compiler will now emit an error for member references that used to successfully resolve to a declaration in a transitively imported module:
95+
A reference to a member in a source file will only be accepted if that member is declared in a module that is contained in the set of visible modules for that source file. A module is in the set of visible modules if any of the following statements are true:
9696

97-
```swift
98-
// In this example, RecipeKit is imported in another file
97+
- The module is directly imported. In other words, some import statement in the source file names the module explicitly.
98+
- The module is directly imported from the bridging header.
99+
- 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.
99100

100-
// note: add import of module 'RecipeKit'
101+
A module is considered to be re-exported by the module that imports it when any of the following statements are true:
101102

102-
let recipe = "1 scoop ice cream, 1 tbs chocolate syrup".parse()
103-
// error: instance method 'parse()' is inaccessible due to missing import of defining module 'RecipeKit'
104-
```
103+
- The associated import statement has the `@_exported` attribute.
104+
- The exporting module is a clang module.
105105

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`.
106107

107-
Rather than simply indicating that the member's name is not in-scope, though, the compiler can identify the declaration you likely meant to use and suggest importing the module that defines it. An IDE may also offer a fix-it to add the missing module import to the file.
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.
108109

109110
## Source compatibility
110111

111-
The suggested change in behavior is source breaking because it adds stricter requirements to name lookup. There is much existing Swift code that will need to be updated to adhere to these new requirements, either by introducing additional import statements in some source files or by reorganizing code among files. This change in behavior therefore must be opt-in, which is why it should be limited to a future language mode with an upcoming feature identifier that allows opt-in with previous language modes.
112+
The proposed change in behavior is source breaking because it adds stricter requirements to name lookup. There is much existing Swift code that will need to be updated to adhere to these new requirements, either by introducing additional import statements in some source files or by reorganizing code among files. This change in behavior therefore must be opt-in, which is why it should be limited to a future language mode with an upcoming feature identifier that allows opt-in with previous language modes.
112113

113114
## ABI compatibility
114115

115116
This change does not affect ABI.
116117

117118
## Implications on adoption
118119

119-
Adopting this feature ought to be straightforward because the compiler can identify module imports that are missing under the new rules and guide the developer to add an explicit import to resolve the error. Furthermore, although enabling this feature would be source breaking for many packages, it's important to note that source code that conforms to the new import requirements will be backwards compatible with older compilers and language modes that don't enforce this requirement. This feature does not require any new syntax or introduce rules that contradict rules of previous language modes.
120+
To make it easier to migrate to the new language mode, the compiler can attempt to identify whether a member reference would resolve to a member declared in a transitively imported module and emit a fix-it to suggest adding a direct import to resolve the errors caused by the stricter look up rules:
121+
122+
```swift
123+
// In this example, RecipeKit is imported in another file
124+
125+
// note: add import of module 'RecipeKit'
126+
127+
let recipe = "1 scoop ice cream, 1 tbs chocolate syrup".parse()
128+
// error: instance method 'parse()' is inaccessible due to missing import of defining module 'RecipeKit'
129+
```
130+
131+
With these fix-its, the burden of updating source code to be compatible with the new language mode should be significantly reduced.
132+
133+
This feature will have some impact on source compatibility with older compilers and previous language modes. Adding new direct imports of modules that were previously only transitively imported is a backward compatible change syntactically. However, if the new language mode is necessary in order to make some source code unambiguous, then the ambiguity will become an issue when compiling the same code using an older language mode so maintaining backward compatibility would require additional measures to be taken.
120134

121135
## Future directions
122136

0 commit comments

Comments
 (0)