Skip to content

Commit 4aabd27

Browse files
authored
Merge pull request swiftlang#33864 from brentdax/imported-by-the-east-aldenard-trading-company
Fix non-public imported declaration diagnostic bug + Favor private imports during name lookup
2 parents 4c50c00 + 806125d commit 4aabd27

File tree

11 files changed

+189
-10
lines changed

11 files changed

+189
-10
lines changed

include/swift/AST/PrintOptions.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ struct PrintOptions {
499499
}
500500

501501
/// Retrieve the set of options suitable for diagnostics printing.
502-
static PrintOptions printForDiagnostics() {
502+
static PrintOptions printForDiagnostics(AccessLevel accessFilter) {
503503
PrintOptions result = printVerbose();
504504
result.PrintAccess = true;
505505
result.Indent = 4;
@@ -512,7 +512,7 @@ struct PrintOptions {
512512
result.ExcludeAttrList.push_back(DAK_Optimize);
513513
result.ExcludeAttrList.push_back(DAK_Rethrows);
514514
result.PrintOverrideKeyword = false;
515-
result.AccessFilter = AccessLevel::Public;
515+
result.AccessFilter = accessFilter;
516516
result.PrintIfConfig = false;
517517
result.ShouldQualifyNestedDeclarations =
518518
QualifyNestedDeclarations::TypesOnly;
@@ -522,7 +522,7 @@ struct PrintOptions {
522522

523523
/// Retrieve the set of options suitable for interface generation.
524524
static PrintOptions printInterface() {
525-
PrintOptions result = printForDiagnostics();
525+
PrintOptions result = printForDiagnostics(AccessLevel::Public);
526526
result.SkipUnavailable = true;
527527
result.SkipImplicit = true;
528528
result.SkipSwiftPrivateClangDecls = true;

lib/AST/DiagnosticEngine.cpp

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,20 @@ void DiagnosticEngine::emitTentativeDiagnostics() {
846846
TentativeDiagnostics.clear();
847847
}
848848

849+
/// Returns the access level of the least accessible PrettyPrintedDeclarations
850+
/// buffer that \p decl should appear in.
851+
///
852+
/// This is always \c Public unless \p decl is a \c ValueDecl and its
853+
/// access level is below \c Public. (That can happen with @testable and
854+
/// @_private imports.)
855+
static AccessLevel getBufferAccessLevel(const Decl *decl) {
856+
AccessLevel level = AccessLevel::Public;
857+
if (auto *VD = dyn_cast<ValueDecl>(decl))
858+
level = VD->getFormalAccessScope().accessLevelForDiagnostics();
859+
if (level > AccessLevel::Public) level = AccessLevel::Public;
860+
return level;
861+
}
862+
849863
Optional<DiagnosticInfo>
850864
DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
851865
auto behavior = state.determineBehavior(diagnostic.getID());
@@ -867,21 +881,31 @@ DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
867881
if (ppLoc.isInvalid()) {
868882
class TrackingPrinter : public StreamPrinter {
869883
SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries;
884+
AccessLevel bufferAccessLevel;
870885

871886
public:
872887
TrackingPrinter(
873888
SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries,
874-
raw_ostream &OS) :
875-
StreamPrinter(OS), Entries(Entries) {}
889+
raw_ostream &OS, AccessLevel bufferAccessLevel) :
890+
StreamPrinter(OS), Entries(Entries),
891+
bufferAccessLevel(bufferAccessLevel) {}
876892

877893
void printDeclLoc(const Decl *D) override {
878-
Entries.push_back({ D, OS.tell() });
894+
if (getBufferAccessLevel(D) == bufferAccessLevel)
895+
Entries.push_back({ D, OS.tell() });
879896
}
880897
};
881898
SmallVector<std::pair<const Decl *, uint64_t>, 8> entries;
882899
llvm::SmallString<128> buffer;
883900
llvm::SmallString<128> bufferName;
884901
{
902+
// The access level of the buffer we want to print. Declarations below
903+
// this access level will be omitted from the buffer; declarations
904+
// above it will be printed, but (except for Open declarations in the
905+
// Public buffer) will not be recorded in PrettyPrintedDeclarations as
906+
// the "true" SourceLoc for the declaration.
907+
AccessLevel bufferAccessLevel = getBufferAccessLevel(decl);
908+
885909
// Figure out which declaration to print. It's the top-most
886910
// declaration (not a module).
887911
const Decl *ppDecl = decl;
@@ -942,10 +966,21 @@ DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
942966
bufferName += ext->getExtendedType().getString();
943967
}
944968

969+
// If we're using a lowered access level, give the buffer a distinct
970+
// name.
971+
if (bufferAccessLevel != AccessLevel::Public) {
972+
assert(bufferAccessLevel < AccessLevel::Public
973+
&& "Above-public access levels should use public buffer");
974+
bufferName += " (";
975+
bufferName += getAccessLevelSpelling(bufferAccessLevel);
976+
bufferName += ")";
977+
}
978+
945979
// Pretty-print the declaration we've picked.
946980
llvm::raw_svector_ostream out(buffer);
947-
TrackingPrinter printer(entries, out);
948-
ppDecl->print(printer, PrintOptions::printForDiagnostics());
981+
TrackingPrinter printer(entries, out, bufferAccessLevel);
982+
ppDecl->print(printer,
983+
PrintOptions::printForDiagnostics(bufferAccessLevel));
949984
}
950985

951986
// Build a buffer with the pretty-printed declaration.

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);

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2974,9 +2974,9 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
29742974
}
29752975
}
29762976

2977-
PrintOptions Options = PrintOptions::printForDiagnostics();
2977+
PrintOptions Options =
2978+
PrintOptions::printForDiagnostics(AccessLevel::Private);
29782979
Options.PrintDocumentationComments = false;
2979-
Options.AccessFilter = AccessLevel::Private;
29802980
Options.PrintAccess = false;
29812981
Options.SkipAttributes = true;
29822982
Options.FunctionDefinitions = true;
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}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
func ambiguous() {}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -module-name ModuleA -emit-module-path %t/ModuleA.swiftmodule -primary-file %S/Inputs/TestablePrintASTLocationsModule.swift -enable-testing
3+
// RUN: %target-swift-frontend -emit-module -module-name ModuleB -emit-module-path %t/ModuleB.swiftmodule -primary-file %S/Inputs/TestablePrintASTLocationsModule.swift -enable-testing
4+
// RUN: not %target-swift-frontend -typecheck -I %t %s 2>&1 | %FileCheck %s
5+
6+
@testable import ModuleA
7+
@testable import ModuleB
8+
9+
ambiguous()
10+
11+
// CHECK: testable-printast-locations.swift:[[@LINE-2]]:1: error: ambiguous use of 'ambiguous()'
12+
// CHECK: ModuleA.ambiguous (internal):1:15: note: found this candidate
13+
// CHECK: ModuleB.ambiguous (internal):1:15: note: found this candidate

0 commit comments

Comments
 (0)