Skip to content

Commit 9e67cf0

Browse files
committed
Sema: Improve MemberImportVisibility diagnostics for cross import overlays.
When `MemberImportVisibility` is enabled and a declaration from a cross import overlay is diagnosed because it has not been imported, suggest imports of the declaring and bystanding modules instead of the cross import overlay module (which is an implementation detail). Resolves rdar://149307959.
1 parent e9253b7 commit 9e67cf0

File tree

6 files changed

+194
-32
lines changed

6 files changed

+194
-32
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,13 @@ ERROR(init_candidate_inaccessible,none,
169169

170170
ERROR(candidate_from_missing_import,none,
171171
"%kind0 is not available due to missing import of defining module %1",
172-
(const ValueDecl *, ModuleDecl *))
172+
(const ValueDecl *, const ModuleDecl *))
173+
ERROR(candidate_from_missing_imports_2_or_more,none,
174+
"%kind0 is not available due to missing imports of defining modules "
175+
"%2%select{ and|, }1 %3%select{|, and others}1",
176+
(const ValueDecl *, bool, const ModuleDecl *, const ModuleDecl *))
173177
NOTE(candidate_add_import,none,
174-
"add import of module %0", (ModuleDecl *))
178+
"add import of module %0", (const ModuleDecl *))
175179

176180
ERROR(cannot_pass_rvalue_mutating_subelement,none,
177181
"cannot use mutating member on immutable value: %0",
@@ -6049,7 +6053,7 @@ ERROR(actor_isolation_multiple_attr_2,none,
60496053
"%kind0 has multiple actor-isolation attributes (%1 and %2)",
60506054
(const Decl *, DeclAttribute, DeclAttribute))
60516055
ERROR(actor_isolation_multiple_attr_3,none,
6052-
"%0 %1 has multiple actor-isolation attributes (%2, %3 and %4)",
6056+
"%0 %1 has multiple actor-isolation attributes (%2, %3, and %4)",
60536057
(const Decl *, DeclAttribute, DeclAttribute, DeclAttribute))
60546058
ERROR(actor_isolation_multiple_attr_4,none,
60556059
"%0 %1 has multiple actor-isolation attributes (%2, %3, %4, and %5)",

lib/Sema/TypeCheckNameLookup.cpp

Lines changed: 116 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "TypoCorrection.h"
2222
#include "swift/AST/ConformanceLookup.h"
2323
#include "swift/AST/ExistentialLayout.h"
24+
#include "swift/AST/ImportCache.h"
2425
#include "swift/AST/Initializer.h"
2526
#include "swift/AST/NameLookup.h"
2627
#include "swift/AST/NameLookupRequests.h"
@@ -798,7 +799,50 @@ TypoCorrectionResults::claimUniqueCorrection() {
798799
return SyntacticTypoCorrection(WrittenName, Loc, uniqueCorrectedName);
799800
}
800801

802+
/// Returns a sorted vector of modules that are not imported in the given
803+
/// `SourceFile` and must be in order to make declarations from \p owningModule
804+
/// visible.
805+
static SmallVector<ModuleDecl *, 2>
806+
missingImportsForDefiningModule(ModuleDecl *owningModule, SourceFile &sf) {
807+
SmallVector<ModuleDecl *, 2> result;
808+
auto &ctx = sf.getASTContext();
809+
810+
if (auto *declaringModule =
811+
owningModule->getDeclaringModuleIfCrossImportOverlay()) {
812+
// If the module that owns the declaration is a cross import overlay the
813+
// fix-its should suggest importing the declaring and bystanding modules,
814+
// not the overlay module.
815+
result.push_back(declaringModule);
816+
817+
SmallVector<Identifier, 2> bystanders;
818+
if (owningModule->getRequiredBystandersIfCrossImportOverlay(declaringModule,
819+
bystanders)) {
820+
for (auto bystander : bystanders) {
821+
if (auto bystanderModule = ctx.getModuleByIdentifier(bystander))
822+
result.push_back(bystanderModule);
823+
}
824+
}
825+
826+
// Remove the modules that are already imported by the source file.
827+
auto &importCache = ctx.getImportCache();
828+
const DeclContext *dc = &sf;
829+
llvm::erase_if(result, [&](ModuleDecl *candidate) {
830+
return importCache.isImportedBy(candidate, dc);
831+
});
832+
} else {
833+
// Just the module that owns the declaration is required.
834+
result.push_back(owningModule);
835+
}
836+
837+
std::sort(result.begin(), result.end(), [](ModuleDecl *LHS, ModuleDecl *RHS) {
838+
return LHS->getNameStr() < LHS->getNameStr();
839+
});
840+
841+
return result;
842+
}
843+
801844
struct MissingImportFixItInfo {
845+
const ModuleDecl *moduleToImport = nullptr;
802846
OptionSet<ImportFlags> flags;
803847
std::optional<AccessLevel> accessLevel;
804848
};
@@ -807,15 +851,13 @@ class MissingImportFixItCache {
807851
SourceFile &sf;
808852
llvm::DenseMap<const ModuleDecl *, MissingImportFixItInfo> infos;
809853

810-
public:
811-
MissingImportFixItCache(SourceFile &sf) : sf(sf){};
812-
813-
MissingImportFixItInfo getInfo(const ModuleDecl *mod) {
854+
MissingImportFixItInfo getFixItInfo(ModuleDecl *mod) {
814855
auto existing = infos.find(mod);
815856
if (existing != infos.end())
816857
return existing->getSecond();
817858

818859
MissingImportFixItInfo info;
860+
info.moduleToImport = mod;
819861

820862
// Find imports of the defining module in other source files and aggregate
821863
// the attributes and access level usage on those imports collectively. This
@@ -845,33 +887,49 @@ class MissingImportFixItCache {
845887
infos[mod] = info;
846888
return info;
847889
}
848-
};
849890

850-
static void diagnoseMissingImportForMember(const ValueDecl *decl,
851-
SourceFile *sf, SourceLoc loc) {
852-
auto &ctx = sf->getASTContext();
853-
auto definingModule = decl->getModuleContextForNameLookup();
854-
ctx.Diags.diagnose(loc, diag::candidate_from_missing_import, decl,
855-
definingModule);
856-
}
891+
public:
892+
MissingImportFixItCache(SourceFile &sf) : sf(sf) {};
857893

858-
static void
859-
diagnoseAndFixMissingImportForMember(const ValueDecl *decl, SourceFile *sf,
860-
SourceLoc loc,
861-
MissingImportFixItCache &fixItCache) {
894+
std::pair<SmallVector<ModuleDecl *, 2>,
895+
SmallVector<MissingImportFixItInfo, 2>>
896+
getModulesAndFixIts(ModuleDecl *mod) {
897+
auto modulesToImport = missingImportsForDefiningModule(mod, sf);
898+
SmallVector<MissingImportFixItInfo, 2> fixItInfos;
862899

863-
diagnoseMissingImportForMember(decl, sf, loc);
900+
for (auto *mod : modulesToImport) {
901+
fixItInfos.emplace_back(getFixItInfo(mod));
902+
}
864903

904+
return {modulesToImport, fixItInfos};
905+
}
906+
};
907+
908+
static void
909+
diagnoseMissingImportsForMember(const ValueDecl *decl,
910+
SmallVectorImpl<ModuleDecl *> &modulesToImport,
911+
SourceFile *sf, SourceLoc loc) {
865912
auto &ctx = sf->getASTContext();
866-
auto definingModule = decl->getModuleContextForNameLookup();
867-
SourceLoc bestLoc = ctx.Diags.getBestAddImportFixItLoc(decl, sf);
868-
if (!bestLoc.isValid())
869-
return;
913+
auto count = modulesToImport.size();
914+
ASSERT(count > 0);
915+
916+
if (count > 1) {
917+
ctx.Diags.diagnose(loc, diag::candidate_from_missing_imports_2_or_more,
918+
decl, bool(count > 2), modulesToImport[0],
919+
modulesToImport[1]);
920+
} else {
921+
ctx.Diags.diagnose(loc, diag::candidate_from_missing_import, decl,
922+
modulesToImport.front());
923+
}
924+
}
870925

926+
static void emitMissingImportFixIt(SourceLoc loc,
927+
const MissingImportFixItInfo &fixItInfo,
928+
const ValueDecl *decl) {
929+
ASTContext &ctx = decl->getASTContext();
871930
llvm::SmallString<64> importText;
872931

873932
// Add flags that must be used consistently on every import in every file.
874-
auto fixItInfo = fixItCache.getInfo(definingModule);
875933
if (fixItInfo.flags.contains(ImportFlags::ImplementationOnly))
876934
importText += "@_implementationOnly ";
877935
if (fixItInfo.flags.contains(ImportFlags::WeakLinked))
@@ -905,10 +963,36 @@ diagnoseAndFixMissingImportForMember(const ValueDecl *decl, SourceFile *sf,
905963
}
906964

907965
importText += "import ";
908-
importText += definingModule->getName().str();
966+
importText += fixItInfo.moduleToImport->getName().str();
909967
importText += "\n";
910-
ctx.Diags.diagnose(bestLoc, diag::candidate_add_import, definingModule)
911-
.fixItInsert(bestLoc, importText);
968+
ctx.Diags
969+
.diagnose(loc, diag::candidate_add_import, fixItInfo.moduleToImport)
970+
.fixItInsert(loc, importText);
971+
}
972+
973+
static void
974+
diagnoseAndFixMissingImportForMember(const ValueDecl *decl, SourceFile *sf,
975+
SourceLoc loc,
976+
MissingImportFixItCache &fixItCache) {
977+
978+
auto modulesAndFixits =
979+
fixItCache.getModulesAndFixIts(decl->getModuleContextForNameLookup());
980+
auto modulesToImport = modulesAndFixits.first;
981+
auto fixItInfos = modulesAndFixits.second;
982+
983+
if (modulesToImport.empty())
984+
return;
985+
986+
diagnoseMissingImportsForMember(decl, modulesToImport, sf, loc);
987+
988+
auto &ctx = sf->getASTContext();
989+
SourceLoc bestLoc = ctx.Diags.getBestAddImportFixItLoc(decl, sf);
990+
if (!bestLoc.isValid())
991+
return;
992+
993+
for (auto &fixItInfo : fixItInfos) {
994+
emitMissingImportFixIt(bestLoc, fixItInfo, decl);
995+
}
912996
}
913997

914998
bool swift::maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
@@ -917,12 +1001,13 @@ bool swift::maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
9171001
if (dc->isDeclImported(decl))
9181002
return false;
9191003

1004+
auto definingModule = decl->getModuleContextForNameLookup();
9201005
if (dc->getASTContext().LangOpts.EnableCXXInterop) {
9211006
// With Cxx interop enabled, there are some declarations that always belong
9221007
// to the Clang header import module which should always be implicitly
9231008
// visible. However, that module is not implicitly imported in source files
9241009
// so we need to special case it here and avoid diagnosing.
925-
if (decl->getModuleContextForNameLookup()->isClangHeaderImportModule())
1010+
if (definingModule->isClangHeaderImportModule())
9261011
return false;
9271012
}
9281013

@@ -935,7 +1020,11 @@ bool swift::maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
9351020
// In lazy typechecking mode just emit the diagnostic immediately without a
9361021
// fix-it since there won't be an opportunity to emit delayed diagnostics.
9371022
if (ctx.TypeCheckerOpts.EnableLazyTypecheck) {
938-
diagnoseMissingImportForMember(decl, sf, loc);
1023+
auto modulesToImport = missingImportsForDefiningModule(definingModule, *sf);
1024+
if (modulesToImport.empty())
1025+
return false;
1026+
1027+
diagnoseMissingImportsForMember(decl, modulesToImport, sf, loc);
9391028
return true;
9401029
}
9411030

test/CrossImport/Inputs/lib-templates/lib/swift/BystandingLibrary.swiftinterface

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33

44
import Swift
55

6-
public struct BystandingLibraryTy {}
6+
public struct BystandingLibraryTy {
7+
public init()
8+
}
79

test/CrossImport/Inputs/lib-templates/lib/swift/DeclaringLibrary.swiftinterface

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33

44
import Swift
55

6-
public struct DeclaringLibraryTy {}
6+
public struct DeclaringLibraryTy {
7+
public init()
8+
}
79
public struct ShadowTy {}

test/CrossImport/Inputs/lib-templates/lib/swift/_OverlayLibrary.swiftinterface

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@ public struct OverlayLibraryTy {
1111
}
1212

1313
public struct ShadowTy {}
14+
15+
extension DeclaringLibrary.DeclaringLibraryTy {
16+
public func overlayMember()
17+
}
18+
19+
extension BystandingLibrary.BystandingLibraryTy {
20+
public func overlayMember()
21+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: cp -r %S/Inputs/lib-templates/* %t/
3+
// RUN: split-file %s %t
4+
5+
// RUN: %target-swift-frontend -typecheck -verify -enable-cross-import-overlays \
6+
// RUN: %t/OnlyDeclaring.swift \
7+
// RUN: %t/OnlyBystanding.swift \
8+
// RUN: %t/NeitherDeclaringNorBystanding.swift \
9+
// RUN: %t/BothDeclaringAndBystanding.swift \
10+
// RUN: -I %t/include -I %t/lib/swift -F %t/Frameworks \
11+
// RUN: -enable-upcoming-feature MemberImportVisibility
12+
13+
// REQUIRES: swift_feature_MemberImportVisibility
14+
15+
//--- OnlyDeclaring.swift
16+
17+
import DeclaringLibrary
18+
// expected-note 2 {{add import of module 'BystandingLibrary'}}
19+
20+
private func test() {
21+
returnsDeclaringTy().overlayMember() // expected-error {{instance method 'overlayMember()' is not available due to missing import of defining module 'BystandingLibrary'}}
22+
returnsBystandingTy().overlayMember() // expected-error {{instance method 'overlayMember()' is not available due to missing import of defining module 'BystandingLibrary'}}
23+
}
24+
25+
//--- OnlyBystanding.swift
26+
27+
import BystandingLibrary
28+
// expected-note 2 {{add import of module 'DeclaringLibrary'}}
29+
30+
private func test() {
31+
returnsDeclaringTy().overlayMember() // expected-error {{instance method 'overlayMember()' is not available due to missing import of defining module 'DeclaringLibrary'}}
32+
returnsBystandingTy().overlayMember() // expected-error {{instance method 'overlayMember()' is not available due to missing import of defining module 'DeclaringLibrary'}}
33+
}
34+
35+
//--- NeitherDeclaringNorBystanding.swift
36+
37+
import Swift
38+
// expected-note 2 {{add import of module 'BystandingLibrary'}}
39+
// expected-note@-1 2 {{add import of module 'DeclaringLibrary'}}
40+
41+
private func test() {
42+
returnsDeclaringTy().overlayMember() // expected-error {{instance method 'overlayMember()' is not available due to missing imports of defining modules 'DeclaringLibrary' and 'BystandingLibrary'}}
43+
returnsBystandingTy().overlayMember() // expected-error {{instance method 'overlayMember()' is not available due to missing imports of defining modules 'DeclaringLibrary' and 'BystandingLibrary'}}
44+
}
45+
46+
//--- BothDeclaringAndBystanding.swift
47+
48+
import DeclaringLibrary
49+
import BystandingLibrary
50+
51+
func returnsDeclaringTy() -> DeclaringLibraryTy {
52+
return DeclaringLibraryTy()
53+
}
54+
55+
func returnsBystandingTy() -> BystandingLibraryTy {
56+
return BystandingLibraryTy()
57+
}

0 commit comments

Comments
 (0)