Skip to content

Commit 806125d

Browse files
committed
Favor private imports during name lookup
Private imports are intended to make the file performing the import more or less source-compatible with the file being imported from, so that code from the original file can be modified by relatively simple syntactic transformations. However, their name shadowing behavior was very different from the original file. In the original file, other declarations in the same module would have shadowed declarations imported from any other module; in a file using a @_private import, they would all be considered imports, and thus all would be preferred equally. This could cause ambiguity in a file using a @_private import that was not present in the original file. This commit changes that behavior by favoring @_private imports over other imports, so that if a name is visible through both a private and a non-private import, the one visible through the private import will shadow the other. This shadowing takes a higher priority than a scoped import, but a lower priority than the check for whether one of the modules is only visible through the other. Fixes rdar://68312053.
1 parent 9dcec05 commit 806125d

File tree

6 files changed

+130
-0
lines changed

6 files changed

+130
-0
lines changed

lib/AST/NameLookup.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,27 @@ static void recordShadowedDeclsAfterTypeMatch(
286286
return false;
287287
};
288288

289+
auto isPrivateImport = [&](ModuleDecl *module) {
290+
auto file = dc->getParentSourceFile();
291+
if (!file) return false;
292+
for (const auto &import : file->getImports()) {
293+
if (import.importOptions.contains(
294+
SourceFile::ImportFlags::PrivateImport)
295+
&& import.module.importedModule == module
296+
&& import.module.accessPath.matches(name))
297+
return true;
298+
}
299+
return false;
300+
};
301+
302+
bool firstPrivate = isPrivateImport(firstModule);
303+
289304
for (unsigned secondIdx : range(firstIdx + 1, decls.size())) {
290305
// Determine whether one module takes precedence over another.
291306
auto secondDecl = decls[secondIdx];
292307
auto secondModule = secondDecl->getModuleContext();
293308
bool secondTopLevel = secondDecl->getDeclContext()->isModuleScopeContext();
309+
bool secondPrivate = isPrivateImport(secondModule);
294310

295311
// For member types, we skip most of the below rules. Instead, we allow
296312
// member types defined in a subclass to shadow member types defined in
@@ -348,6 +364,18 @@ static void recordShadowedDeclsAfterTypeMatch(
348364
continue;
349365
}
350366

367+
// If neither module shadows the other, but one was imported with
368+
// '@_private import' in dc, we want to favor that module. This makes
369+
// name lookup in this file behave more like name lookup in the file we
370+
// imported from, avoiding headaches for source-transforming tools.
371+
if (!firstPrivate && secondPrivate) {
372+
shadowed.insert(firstDecl);
373+
break;
374+
} else if (firstPrivate && !secondPrivate) {
375+
shadowed.insert(secondDecl);
376+
continue;
377+
}
378+
351379
// We might be in a situation where neither module shadows the
352380
// other, but one declaration is visible via a scoped import.
353381
bool firstScoped = isScopedImport(firstPaths);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@available(*, deprecated, message: "got Ghost version")
2+
public enum Coast { case airCurrent } // Also defined in Most
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Most
2+
3+
struct Post {}
4+
struct Boast {} // Deprecated in Most
5+
6+
@available(*, deprecated, message: "got Host version")
7+
struct Roast {} // Different definition in Toast
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@available(*, deprecated, message: "got Boast version")
2+
public struct Boast { } // Not deprecated in Host
3+
4+
@available(*, deprecated, message: "got Boast version")
5+
public enum Coast { case downhill } // Also defined in Ghost
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@available(*, deprecated, message: "got Toast version")
2+
public struct Roast {} // Different definition in Host

test/NameLookup/private_import.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// '@_private import' is intended to be used to compile "thunks" which contain
2+
// modified versions of declarations. For this to work properly, name lookup
3+
// needs to favor declarations in the private import over other imports. This
4+
// emulates the increased visibility of same-module declarations in the original
5+
// file, preserving source compatibility as much as possible.
6+
//
7+
// In this test:
8+
//
9+
// * Module Host contains this file and Host.swift. Note that this file compiles
10+
// successfully with COMPILING_THUNK undefined.
11+
// * This file is also built as a thunk with COMPILING_THUNK defined to "edit"
12+
// it.
13+
// * We also build three other modules called Most, Ghost, and Toast. Each of
14+
// these is imported by both Host and the thunk.
15+
//
16+
// There are various same-name types scattered throughout these modules. If
17+
// name lookup selects the right ones for each of the test cases, the compiler
18+
// will generate the expected diagnostics and the test will pass. If it selects
19+
// incorrect types, deprecation warnings will be emitted and the test will fail.
20+
21+
// We'll put our modules here.
22+
// RUN: %empty-directory(%t)
23+
24+
// Build some libraries to work with.
25+
// RUN: %target-swift-frontend -emit-module -parse-as-library -module-name Most -emit-module-path %t/Most.swiftmodule -primary-file %S/Inputs/private_import/Most.swift
26+
// RUN: %target-swift-frontend -emit-module -parse-as-library -module-name Ghost -emit-module-path %t/Ghost.swiftmodule -primary-file %S/Inputs/private_import/Ghost.swift
27+
// RUN: %target-swift-frontend -emit-module -enable-private-imports -parse-as-library -module-name Toast -emit-module-path %t/Toast.swiftmodule -primary-file %S/Inputs/private_import/Toast.swift
28+
29+
// Build the host module that the thunk is pretending to be part of.
30+
// RUN: %target-swift-frontend -emit-module -module-name Host -I %t -emit-module-path %t/Host.swiftmodule -enable-private-imports %s %S/Inputs/private_import/Host.swift
31+
32+
// Build a thunk for the host module.
33+
// RUN: %target-typecheck-verify-swift -parse-as-library -I %t -DCOMPILING_THUNK
34+
35+
#if COMPILING_THUNK
36+
@_private(sourceFile: "private_import.swift") import Host
37+
#endif
38+
39+
import Most
40+
import Ghost
41+
42+
@_private(sourceFile: "Toast.swift") import Toast
43+
44+
//
45+
// Types with varying definitions
46+
//
47+
48+
struct Hunk {} // Present in both Host and thunk
49+
50+
#if COMPILING_THUNK
51+
52+
struct Thunk {} // Only present in thunk
53+
struct Bunk {} // Not deprecated in thunk
54+
55+
#else
56+
57+
struct Shrunk {} // Only present in Host
58+
@available(*, deprecated, message: "got Host version")
59+
struct Bunk {} // Only deprecated in Host
60+
61+
#endif
62+
63+
//
64+
// Test cases
65+
//
66+
67+
#if COMPILING_THUNK
68+
func thunkCanUseTypesOnlyInThunk(_: Thunk) {}
69+
#endif
70+
71+
func thunkCanRedeclareTypes(_: Hunk) {}
72+
func thunkCanUseTypesOnlyInHost(_: Shrunk, _: Post) {}
73+
func thunkTypesShadowHostTypes(_: Bunk) {}
74+
75+
func hostTypesShadowMostTypes(_: Boast) {}
76+
// no-error@-1; previously, this would give "'Boast' is ambiguous for type lookup in this context"
77+
78+
#if COMPILING_THUNK
79+
func ambiguousWithTwoNonPrivateImports(_: Coast) {}
80+
// expected-error@-1{{'Coast' is ambiguous for type lookup in this context}}
81+
#endif
82+
83+
// The intended clients of '@_private import' should not actually use it twice
84+
// in a single file, so this behavior is more accidental than anything.
85+
func ambiguousWithTwoPrivateImports(_: Roast) {}
86+
// expected-error@-1{{'Roast' is ambiguous for type lookup in this context}}

0 commit comments

Comments
 (0)