Skip to content

Commit 43fd131

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 9775681 commit 43fd131

File tree

6 files changed

+195
-34
lines changed

6 files changed

+195
-34
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,14 @@ ERROR(init_candidate_inaccessible,none,
166166
(Type, AccessLevel))
167167

168168
ERROR(candidate_from_missing_import,none,
169-
"%0 %1 is not available due to missing import of defining module %2",
170-
(DescriptiveDeclKind, DeclName, ModuleDecl *))
169+
"%kind0 is not available due to missing import of defining module %1",
170+
(const ValueDecl *, const ModuleDecl *))
171+
ERROR(candidate_from_missing_imports_2_or_more,none,
172+
"%kind0 is not available due to missing imports of defining modules "
173+
"%2%select{ and|, }1 %3%select{|, and others}1",
174+
(const ValueDecl *, bool, const ModuleDecl *, const ModuleDecl *))
171175
NOTE(candidate_add_import,none,
172-
"add import of module %0", (ModuleDecl *))
176+
"add import of module %0", (const ModuleDecl *))
173177

174178
ERROR(cannot_pass_rvalue_mutating_subelement,none,
175179
"cannot use mutating member on immutable value: %0",
@@ -6019,7 +6023,7 @@ ERROR(actor_isolation_multiple_attr_2,none,
60196023
"%kind0 has multiple actor-isolation attributes (%1 and %2)",
60206024
(const Decl *, DeclAttribute, DeclAttribute))
60216025
ERROR(actor_isolation_multiple_attr_3,none,
6022-
"%0 %1 has multiple actor-isolation attributes (%2, %3 and %4)",
6026+
"%0 %1 has multiple actor-isolation attributes (%2, %3, and %4)",
60236027
(const Decl *, DeclAttribute, DeclAttribute, DeclAttribute))
60246028
ERROR(actor_isolation_multiple_attr_4,none,
60256029
"%0 %1 has multiple actor-isolation attributes (%2, %3, %4, and %5)",

lib/Sema/TypeCheckNameLookup.cpp

Lines changed: 116 additions & 28 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,34 +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,
855-
decl->getDescriptiveKind(), decl->getName(),
856-
definingModule);
857-
}
891+
public:
892+
MissingImportFixItCache(SourceFile &sf) : sf(sf) {};
858893

859-
static void
860-
diagnoseAndFixMissingImportForMember(const ValueDecl *decl, SourceFile *sf,
861-
SourceLoc loc,
862-
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;
863899

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

904+
return {modulesToImport, fixItInfos};
905+
}
906+
};
907+
908+
static void
909+
diagnoseMissingImportsForMember(const ValueDecl *decl,
910+
SmallVectorImpl<ModuleDecl *> &modulesToImport,
911+
SourceFile *sf, SourceLoc loc) {
866912
auto &ctx = sf->getASTContext();
867-
auto definingModule = decl->getModuleContextForNameLookup();
868-
SourceLoc bestLoc = ctx.Diags.getBestAddImportFixItLoc(decl, sf);
869-
if (!bestLoc.isValid())
870-
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+
}
871925

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

874932
// Add flags that must be used consistently on every import in every file.
875-
auto fixItInfo = fixItCache.getInfo(definingModule);
876933
if (fixItInfo.flags.contains(ImportFlags::ImplementationOnly))
877934
importText += "@_implementationOnly ";
878935
if (fixItInfo.flags.contains(ImportFlags::WeakLinked))
@@ -906,10 +963,36 @@ diagnoseAndFixMissingImportForMember(const ValueDecl *decl, SourceFile *sf,
906963
}
907964

908965
importText += "import ";
909-
importText += definingModule->getName().str();
966+
importText += fixItInfo.moduleToImport->getName().str();
910967
importText += "\n";
911-
ctx.Diags.diagnose(bestLoc, diag::candidate_add_import, definingModule)
912-
.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+
}
913996
}
914997

915998
bool swift::maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
@@ -918,12 +1001,13 @@ bool swift::maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
9181001
if (dc->isDeclImported(decl))
9191002
return false;
9201003

1004+
auto definingModule = decl->getModuleContextForNameLookup();
9211005
if (dc->getASTContext().LangOpts.EnableCXXInterop) {
9221006
// With Cxx interop enabled, there are some declarations that always belong
9231007
// to the Clang header import module which should always be implicitly
9241008
// visible. However, that module is not implicitly imported in source files
9251009
// so we need to special case it here and avoid diagnosing.
926-
if (decl->getModuleContextForNameLookup()->isClangHeaderImportModule())
1010+
if (definingModule->isClangHeaderImportModule())
9271011
return false;
9281012
}
9291013

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

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)