Skip to content

Commit 3ef1600

Browse files
committed
Sema: Add an access level to the fix-its for missing imports when appropriate.
When emitting fix-its for missing imports, include an access level when the module has been imported with an access level in other source files. For now, the suggested access level for will always be `internal`, even when uses of members in the file would actually require `public` or `package` visibility. In order to suggest the correct access level, name lookup will need to be refactored to repair references to inaccessible declarations, instead of leaving error nodes in the AST. In anticipation of that refactoring of name lookup, missing import diagnostics are now delayed until type checking a source file is finished so that a consistent access level can be suggested for each import fix-it for a given module. Partially resolves rdar://126637855.
1 parent 7d95914 commit 3ef1600

11 files changed

+283
-41
lines changed

include/swift/AST/SourceFile.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ class SourceFile final : public FileUnit {
163163
/// were not written in the source file.
164164
llvm::SmallDenseSet<ImportedModule> ImplicitImportsForModuleInterface;
165165

166+
/// Associates a list of source locations to the member declarations that must
167+
/// be diagnosed as being out of scope due to a missing import.
168+
using DelayedMissingImportForMemberDiags =
169+
llvm::SmallDenseMap<const ValueDecl *, std::vector<SourceLoc>>;
170+
DelayedMissingImportForMemberDiags MissingImportForMemberDiagnostics;
171+
166172
/// A unique identifier representing this file; used to mark private decls
167173
/// within the file to keep them from conflicting with other files in the
168174
/// same module.
@@ -467,8 +473,11 @@ class SourceFile final : public FileUnit {
467473
/// If not, we can fast-path module checks.
468474
bool hasImportsWithFlag(ImportFlags flag) const;
469475

470-
/// Returns the import flags that are active on imports of \p module.
471-
ImportFlags getImportFlags(const ModuleDecl *module) const;
476+
/// Enumerates each of the direct imports of \p module in the file.
477+
using AttributedImportCallback =
478+
llvm::function_ref<void(AttributedImport<ImportedModule> &)>;
479+
void forEachImportOfModule(const ModuleDecl *module,
480+
AttributedImportCallback callback);
472481

473482
/// Get the most permissive restriction applied to the imports of \p module.
474483
RestrictedImportKind getRestrictedImportKind(const ModuleDecl *module) const;
@@ -528,6 +537,20 @@ class SourceFile final : public FileUnit {
528537
void getImplicitImportsForModuleInterface(
529538
SmallVectorImpl<ImportedModule> &imports) const override;
530539

540+
/// Add a source location for which a delayed missing import for member
541+
/// diagnostic should be emited.
542+
void addDelayedMissingImportForMemberDiagnostic(const ValueDecl *decl,
543+
SourceLoc loc) {
544+
MissingImportForMemberDiagnostics[decl].push_back(loc);
545+
}
546+
547+
DelayedMissingImportForMemberDiags
548+
takeDelayedMissingImportForMemberDiagnostics() {
549+
DelayedMissingImportForMemberDiags diags;
550+
std::swap(diags, MissingImportForMemberDiagnostics);
551+
return diags;
552+
}
553+
531554
/// Record the source range info for a parsed \c #if clause.
532555
void recordIfConfigClauseRangeInfo(const IfConfigClauseRangeInfo &range);
533556

lib/AST/Module.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2817,13 +2817,13 @@ bool SourceFile::hasImportsWithFlag(ImportFlags flag) const {
28172817
ctx.evaluator, HasImportsMatchingFlagRequest{mutableThis, flag}, false);
28182818
}
28192819

2820-
ImportFlags SourceFile::getImportFlags(const ModuleDecl *module) const {
2821-
unsigned flags = 0x0;
2820+
void SourceFile::forEachImportOfModule(
2821+
const ModuleDecl *module,
2822+
llvm::function_ref<void(AttributedImport<ImportedModule> &)> callback) {
28222823
for (auto import : *Imports) {
28232824
if (import.module.importedModule == module)
2824-
flags |= import.options.toRaw();
2825+
callback(import);
28252826
}
2826-
return ImportFlags(flags);
28272827
}
28282828

28292829
bool SourceFile::hasTestableOrPrivateImport(

lib/Sema/CSDiagnostics.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6188,7 +6188,7 @@ bool InaccessibleMemberFailure::diagnoseAsError() {
61886188

61896189
auto loc = nameLoc.isValid() ? nameLoc.getStartLoc() : ::getLoc(anchor);
61906190
if (IsMissingImport) {
6191-
diagnoseMissingImportForMember(Member, getDC(), loc);
6191+
maybeDiagnoseMissingImportForMember(Member, getDC(), loc);
61926192
return true;
61936193
}
61946194

lib/Sema/PreCheckExpr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ Expr *TypeChecker::resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE,
629629
TypeChecker::lookupUnqualified(DC, LookupName, Loc, relookupOptions);
630630
if (nonImportedResults) {
631631
const ValueDecl *first = nonImportedResults.front().getValueDecl();
632-
diagnoseMissingImportForMember(first, DC, Loc);
632+
maybeDiagnoseMissingImportForMember(first, DC, Loc);
633633

634634
// Don't try to recover here; we'll get more access-related diagnostics
635635
// downstream if the type of the inaccessible decl is also inaccessible.

lib/Sema/TypeCheckNameLookup.cpp

Lines changed: 108 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
// declaration of members (such as constructors).
1616
//
1717
//===----------------------------------------------------------------------===//
18+
19+
#include "TypeCheckAvailability.h"
1820
#include "TypeChecker.h"
1921
#include "TypoCorrection.h"
2022
#include "swift/AST/ExistentialLayout.h"
@@ -755,43 +757,87 @@ TypoCorrectionResults::claimUniqueCorrection() {
755757
return SyntacticTypoCorrection(WrittenName, Loc, uniqueCorrectedName);
756758
}
757759

758-
bool swift::diagnoseMissingImportForMember(const ValueDecl *decl,
759-
const DeclContext *dc,
760-
SourceLoc loc) {
761-
if (decl->findImport(dc))
762-
return false;
760+
struct MissingImportFixItInfo {
761+
OptionSet<ImportFlags> flags;
762+
std::optional<AccessLevel> accessLevel;
763+
};
764+
765+
class MissingImportFixItCache {
766+
SourceFile &sf;
767+
llvm::DenseMap<const ModuleDecl *, MissingImportFixItInfo> infos;
768+
769+
public:
770+
MissingImportFixItCache(SourceFile &sf) : sf(sf){};
771+
772+
MissingImportFixItInfo getInfo(const ModuleDecl *mod) {
773+
auto existing = infos.find(mod);
774+
if (existing != infos.end())
775+
return existing->getSecond();
776+
777+
MissingImportFixItInfo info;
778+
779+
// Find imports of the defining module in other source files and aggregate
780+
// the attributes and access level usage on those imports collectively. This
781+
// information can be used to emit a fix-it that is consistent with
782+
// how the module is imported in the rest of the module.
783+
auto parentModule = sf.getParentModule();
784+
bool foundImport = false;
785+
bool anyImportHasAccessLevel = false;
786+
for (auto file : parentModule->getFiles()) {
787+
if (auto otherSF = dyn_cast<SourceFile>(file)) {
788+
unsigned flagsInSourceFile = 0x0;
789+
otherSF->forEachImportOfModule(
790+
mod, [&](AttributedImport<ImportedModule> &import) {
791+
foundImport = true;
792+
anyImportHasAccessLevel |= import.accessLevelRange.isValid();
793+
flagsInSourceFile |= import.options.toRaw();
794+
});
795+
info.flags |= ImportFlags(flagsInSourceFile);
796+
}
797+
}
763798

764-
auto &ctx = dc->getASTContext();
799+
// Add an appropriate access level as long as it would not conflict with
800+
// existing imports that lack access levels.
801+
if (!foundImport || anyImportHasAccessLevel)
802+
info.accessLevel = sf.getMaxAccessLevelUsingImport(mod);
803+
804+
infos[mod] = info;
805+
return info;
806+
}
807+
};
808+
809+
static void diagnoseMissingImportForMember(const ValueDecl *decl,
810+
SourceFile *sf, SourceLoc loc) {
811+
auto &ctx = sf->getASTContext();
765812
auto definingModule = decl->getModuleContext();
766813
ctx.Diags.diagnose(loc, diag::candidate_from_missing_import,
767814
decl->getDescriptiveKind(), decl->getName(),
768815
definingModule);
816+
}
817+
818+
static void
819+
diagnoseAndFixMissingImportForMember(const ValueDecl *decl, SourceFile *sf,
820+
SourceLoc loc,
821+
MissingImportFixItCache &fixItCache) {
822+
823+
diagnoseMissingImportForMember(decl, sf, loc);
769824

770-
SourceLoc bestLoc =
771-
ctx.Diags.getBestAddImportFixItLoc(decl, dc->getParentSourceFile());
825+
auto &ctx = sf->getASTContext();
826+
auto definingModule = decl->getModuleContext();
827+
SourceLoc bestLoc = ctx.Diags.getBestAddImportFixItLoc(decl, sf);
772828
if (!bestLoc.isValid())
773-
return false;
829+
return;
774830

775831
llvm::SmallString<64> importText;
776832

777-
// Check other source files for import flags that should be applied to the
778-
// fix-it for consistency with the rest of the imports in the module.
779-
auto parentModule = dc->getParentModule();
780-
OptionSet<ImportFlags> flags;
781-
for (auto file : parentModule->getFiles()) {
782-
if (auto sf = dyn_cast<SourceFile>(file))
783-
flags |= sf->getImportFlags(definingModule);
784-
}
785-
786-
if (flags.contains(ImportFlags::ImplementationOnly))
833+
auto fixItInfo = fixItCache.getInfo(definingModule);
834+
if (fixItInfo.flags.contains(ImportFlags::ImplementationOnly))
787835
importText += "@_implementationOnly ";
788-
if (flags.contains(ImportFlags::WeakLinked))
836+
if (fixItInfo.flags.contains(ImportFlags::WeakLinked))
789837
importText += "@_weakLinked ";
790-
if (flags.contains(ImportFlags::SPIOnly))
838+
if (fixItInfo.flags.contains(ImportFlags::SPIOnly))
791839
importText += "@_spiOnly ";
792840

793-
// FIXME: Access level should be considered, too.
794-
795841
// @_spi imports.
796842
if (decl->isSPI()) {
797843
auto spiGroups = decl->getSPIGroups();
@@ -802,11 +848,49 @@ bool swift::diagnoseMissingImportForMember(const ValueDecl *decl,
802848
}
803849
}
804850

851+
if (auto accessLevel = fixItInfo.accessLevel) {
852+
importText += getAccessLevelSpelling(*accessLevel);
853+
importText += " ";
854+
}
855+
805856
importText += "import ";
806857
importText += definingModule->getName().str();
807858
importText += "\n";
808859
ctx.Diags.diagnose(bestLoc, diag::candidate_add_import, definingModule)
809860
.fixItInsert(bestLoc, importText);
861+
}
810862

811-
return true;
863+
bool swift::maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
864+
const DeclContext *dc,
865+
SourceLoc loc) {
866+
if (decl->findImport(dc))
867+
return false;
868+
869+
auto sf = dc->getParentSourceFile();
870+
if (!sf)
871+
return false;
872+
873+
auto &ctx = dc->getASTContext();
874+
875+
// In lazy typechecking mode just emit the diagnostic immediately without a
876+
// fix-it since there won't be an opportunity to emit delayed diagnostics.
877+
if (ctx.TypeCheckerOpts.EnableLazyTypecheck) {
878+
diagnoseMissingImportForMember(decl, sf, loc);
879+
return true;
880+
}
881+
882+
sf->addDelayedMissingImportForMemberDiagnostic(decl, loc);
883+
return false;
884+
}
885+
886+
void swift::diagnoseMissingImports(SourceFile &sf) {
887+
auto delayedDiags = sf.takeDelayedMissingImportForMemberDiagnostics();
888+
auto fixItCache = MissingImportFixItCache(sf);
889+
890+
for (auto declAndLocs : delayedDiags) {
891+
for (auto loc : declAndLocs.second) {
892+
diagnoseAndFixMissingImportForMember(declAndLocs.first, &sf, loc,
893+
fixItCache);
894+
}
895+
}
812896
}

lib/Sema/TypeCheckType.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,7 @@ static Type diagnoseUnknownType(const TypeResolution &resolution,
13881388
if (!nonImportedResults.empty()) {
13891389
auto first = cast<TypeDecl>(nonImportedResults.front().getValueDecl());
13901390
auto nameLoc = repr->getNameLoc();
1391-
diagnoseMissingImportForMember(first, dc, nameLoc.getStartLoc());
1391+
maybeDiagnoseMissingImportForMember(first, dc, nameLoc.getStartLoc());
13921392

13931393
// Don't try to recover here; we'll get more access-related diagnostics
13941394
// downstream if we do.
@@ -1493,7 +1493,7 @@ static Type diagnoseUnknownType(const TypeResolution &resolution,
14931493
if (nonImportedMembers) {
14941494
const TypeDecl *first = nonImportedMembers.front().Member;
14951495
auto nameLoc = repr->getNameLoc();
1496-
diagnoseMissingImportForMember(first, dc, nameLoc.getStartLoc());
1496+
maybeDiagnoseMissingImportForMember(first, dc, nameLoc.getStartLoc());
14971497
// Don't try to recover here; we'll get more access-related diagnostics
14981498
// downstream if we do.
14991499
return ErrorType::get(ctx);

lib/Sema/TypeChecker.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ TypeCheckSourceFileRequest::evaluate(Evaluator &eval, SourceFile *SF) const {
322322
if (!Ctx.LangOpts.hasFeature(Feature::RegionBasedIsolation))
323323
diagnoseUnnecessaryPreconcurrencyImports(*SF);
324324
diagnoseUnnecessaryPublicImports(*SF);
325+
diagnoseMissingImports(*SF);
325326

326327
// Check to see if there are any inconsistent imports.
327328
// Whole-module @_implementationOnly imports.

lib/Sema/TypeChecker.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,10 +1535,17 @@ void recordRequiredImportAccessLevelForDecl(
15351535
/// Report imports that are marked public but are not used in API.
15361536
void diagnoseUnnecessaryPublicImports(SourceFile &SF);
15371537

1538-
/// If the import that would make the given declaration visibile is absent,
1539-
/// emit a diagnostic and a fix-it suggesting adding the missing import.
1540-
bool diagnoseMissingImportForMember(const ValueDecl *decl,
1541-
const DeclContext *dc, SourceLoc loc);
1538+
/// Emit or delay a diagnostic that suggests adding a missing import that is
1539+
/// necessary to bring \p decl into scope in the containing source file. If
1540+
/// delayed, the diagnostic will instead be emitted after type checking the
1541+
/// entire file and will include an appropriate fix-it. Returns true if a
1542+
/// diagnostic was emitted (and not delayed).
1543+
bool maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
1544+
const DeclContext *dc, SourceLoc loc);
1545+
1546+
/// Emit delayed diagnostics regarding imports that should be added to the
1547+
/// source file.
1548+
void diagnoseMissingImports(SourceFile &sf);
15421549
} // end namespace swift
15431550

15441551
#endif

test/NameLookup/members_transitive.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// RUN: %target-swift-frontend -typecheck %s -I %t -verify -swift-version 5 -package-name TestPackage -enable-experimental-feature MemberImportVisibility -verify-additional-prefix member-visibility-
88

99
import members_C
10-
// expected-member-visibility-note 11{{add import of module 'members_B'}}{{1-1=import members_B\n}}
10+
// expected-member-visibility-note 11{{add import of module 'members_B'}}{{1-1=internal import members_B\n}}
1111

1212

1313
func testExtensionMembers(x: X, y: Y<Z>) {

0 commit comments

Comments
 (0)