Skip to content

Commit e820854

Browse files
authored
Merge pull request #68831 from xymus/report-superfluously-public-imports
Sema: warn on imports that are too public and remark source of entities in API
2 parents 9e40d11 + c15a576 commit e820854

24 files changed

+400
-39
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,20 @@ REMARK(module_loaded,none,
11261126
"; source: '%2', loaded: '%3'",
11271127
(Identifier, unsigned, StringRef, StringRef))
11281128

1129+
REMARK(module_api_import,none,
1130+
"%select{|implicitly used }4"
1131+
"%kind0 is imported via %1"
1132+
"%select{, which reexports definition from %2|}3",
1133+
(const Decl *, ModuleDecl *, ModuleDecl *, bool, bool))
1134+
REMARK(module_api_import_conformance,none,
1135+
"conformance of %0 to %kind1 used here is imported via %2"
1136+
"%select{ which reexports the definition from %3|}4",
1137+
(const Type, const Decl *, ModuleDecl *, ModuleDecl *, bool))
1138+
REMARK(module_api_import_aliases,none,
1139+
"typealias underlying type %kind0 is imported via %1"
1140+
"%select{, which reexports definition from %2|}3",
1141+
(const Decl *, ModuleDecl *, ModuleDecl *, bool))
1142+
11291143
REMARK(macro_loaded,none,
11301144
"loaded macro implementation module %0 from "
11311145
"%select{shared library '%2'|executable '%2'|"
@@ -2454,6 +2468,12 @@ REMARK(add_predates_concurrency_import,none,
24542468
"%select{| as warnings}0", (bool, Identifier))
24552469
REMARK(remove_predates_concurrency_import,none,
24562470
"'@preconcurrency' attribute on module %0 is unused", (Identifier))
2471+
WARNING(remove_public_import,none,
2472+
"public import of %0 was not used in public declarations or inlinable code",
2473+
(const ModuleDecl *))
2474+
WARNING(remove_package_import,none,
2475+
"package import of %0 was not used in package declarations",
2476+
(const ModuleDecl *))
24572477
WARNING(public_decl_needs_sendable,none,
24582478
"public %kind0 does not specify whether it is 'Sendable' or not",
24592479
(const ValueDecl *))

include/swift/AST/Import.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -591,19 +591,19 @@ struct AttributedImport {
591591

592592
/// Location of the attribute that defined \c accessLevel. Also indicates
593593
/// if the access level was implicit or explicit.
594-
SourceLoc accessLevelLoc;
594+
SourceRange accessLevelRange;
595595

596596
AttributedImport(ModuleInfo module, SourceLoc importLoc = SourceLoc(),
597597
ImportOptions options = ImportOptions(),
598598
StringRef filename = {}, ArrayRef<Identifier> spiGroups = {},
599599
SourceRange preconcurrencyRange = {},
600600
llvm::Optional<AccessLevel> docVisibility = llvm::None,
601601
AccessLevel accessLevel = AccessLevel::Public,
602-
SourceLoc accessLevelLoc = SourceLoc())
602+
SourceRange accessLevelRange = SourceRange())
603603
: module(module), importLoc(importLoc), options(options),
604604
sourceFileArg(filename), spiGroups(spiGroups),
605605
preconcurrencyRange(preconcurrencyRange), docVisibility(docVisibility),
606-
accessLevel(accessLevel), accessLevelLoc(accessLevelLoc) {
606+
accessLevel(accessLevel), accessLevelRange(accessLevelRange) {
607607
assert(!(options.contains(ImportFlags::Exported) &&
608608
options.contains(ImportFlags::ImplementationOnly)) ||
609609
options.contains(ImportFlags::Reserved));
@@ -614,7 +614,7 @@ struct AttributedImport {
614614
: AttributedImport(module, other.importLoc, other.options,
615615
other.sourceFileArg, other.spiGroups,
616616
other.preconcurrencyRange, other.docVisibility,
617-
other.accessLevel, other.accessLevelLoc) { }
617+
other.accessLevel, other.accessLevelRange) { }
618618

619619
friend bool operator==(const AttributedImport<ModuleInfo> &lhs,
620620
const AttributedImport<ModuleInfo> &rhs) {
@@ -624,7 +624,7 @@ struct AttributedImport {
624624
lhs.spiGroups == rhs.spiGroups &&
625625
lhs.docVisibility == rhs.docVisibility &&
626626
lhs.accessLevel == rhs.accessLevel &&
627-
lhs.accessLevelLoc == rhs.accessLevelLoc;
627+
lhs.accessLevelRange == rhs.accessLevelRange;
628628
}
629629

630630
AttributedImport<ImportedModule> getLoaded(ModuleDecl *loadedModule) const {
@@ -802,7 +802,7 @@ struct DenseMapInfo<swift::AttributedImport<ModuleInfo>> {
802802
a.spiGroups == b.spiGroups &&
803803
a.docVisibility == b.docVisibility &&
804804
a.accessLevel == b.accessLevel &&
805-
a.accessLevelLoc == b.accessLevelLoc;
805+
a.accessLevelRange == b.accessLevelRange;
806806
}
807807
};
808808
}

include/swift/AST/SourceFile.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ class SourceFile final : public FileUnit {
115115
llvm::SmallDenseSet<AttributedImport<ImportedModule>>
116116
PreconcurrencyImportsUsed;
117117

118+
/// The highest access level of declarations referencing each import.
119+
llvm::DenseMap<AttributedImport<ImportedModule>, AccessLevel>
120+
ImportsUseAccessLevel;
121+
118122
/// A unique identifier representing this file; used to mark private decls
119123
/// within the file to keep them from conflicting with other files in the
120124
/// same module.
@@ -365,6 +369,15 @@ class SourceFile final : public FileUnit {
365369
void setImportUsedPreconcurrency(
366370
AttributedImport<ImportedModule> import);
367371

372+
/// Return the highest access level of the declarations referencing
373+
/// this import in signature or inlinable code.
374+
AccessLevel
375+
getMaxAccessLevelUsingImport(AttributedImport<ImportedModule> import) const;
376+
377+
/// Register the use of \p import from an API with \p accessLevel.
378+
void registerAccessLevelUsingImport(AttributedImport<ImportedModule> import,
379+
AccessLevel accessLevel);
380+
368381
enum ImportQueryKind {
369382
/// Return the results for testable or private imports.
370383
TestableAndPrivate,

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,9 @@ namespace swift {
246246
/// Emit remarks about contextual inconsistencies in loaded modules.
247247
bool EnableModuleRecoveryRemarks = false;
248248

249+
/// Emit remarks about the source of each element exposed by the module API.
250+
bool EnableModuleApiImportRemarks = false;
251+
249252
/// Emit a remark after loading a macro implementation.
250253
bool EnableMacroLoadingRemarks = false;
251254

include/swift/Option/Options.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ def remark_loading_module : Flag<["-"], "Rmodule-loading">,
397397
def remark_module_recovery : Flag<["-"], "Rmodule-recovery">,
398398
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
399399
HelpText<"Emit remarks about contextual inconsistencies in loaded modules">;
400+
def remark_module_api_import : Flag<["-"], "Rmodule-api-import">,
401+
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
402+
HelpText<"Emit remarks about the import briging in each element composing the API">;
400403

401404
def remark_macro_loading : Flag<["-"], "Rmacro-loading">,
402405
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,

lib/AST/Decl.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1481,7 +1481,11 @@ ArrayRef<ValueDecl *> ImportDecl::getDecls() const {
14811481

14821482
AccessLevel ImportDecl::getAccessLevel() const {
14831483
if (auto attr = getAttrs().getAttribute<AccessControlAttr>()) {
1484-
return attr->getAccess();
1484+
if (attr->getAccess() == AccessLevel::Open) {
1485+
// Use a conservative import if the level given is invalid.
1486+
return AccessLevel::Internal;
1487+
} else
1488+
return attr->getAccess();
14851489
}
14861490

14871491
auto &LangOpts = getASTContext().LangOpts;

lib/AST/Module.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3087,6 +3087,25 @@ void SourceFile::setImportUsedPreconcurrency(
30873087
PreconcurrencyImportsUsed.insert(import);
30883088
}
30893089

3090+
AccessLevel
3091+
SourceFile::getMaxAccessLevelUsingImport(
3092+
AttributedImport<ImportedModule> import) const {
3093+
auto known = ImportsUseAccessLevel.find(import);
3094+
if (known == ImportsUseAccessLevel.end())
3095+
return AccessLevel::Internal;
3096+
return known->second;
3097+
}
3098+
3099+
void SourceFile::registerAccessLevelUsingImport(
3100+
AttributedImport<ImportedModule> import,
3101+
AccessLevel accessLevel) {
3102+
auto known = ImportsUseAccessLevel.find(import);
3103+
if (known == ImportsUseAccessLevel.end())
3104+
ImportsUseAccessLevel[import] = accessLevel;
3105+
else
3106+
ImportsUseAccessLevel[import] = std::max(accessLevel, known->second);
3107+
}
3108+
30903109
bool HasImportsMatchingFlagRequest::evaluate(Evaluator &evaluator,
30913110
SourceFile *SF,
30923111
ImportFlags flag) const {

lib/Frontend/CompilerInvocation.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
10211021

10221022
Opts.EnableModuleLoadingRemarks = Args.hasArg(OPT_remark_loading_module);
10231023
Opts.EnableModuleRecoveryRemarks = Args.hasArg(OPT_remark_module_recovery);
1024+
Opts.EnableModuleApiImportRemarks = Args.hasArg(OPT_remark_module_api_import);
10241025
Opts.EnableMacroLoadingRemarks = Args.hasArg(OPT_remark_macro_loading);
10251026
Opts.EnableIndexingSystemModuleRemarks = Args.hasArg(OPT_remark_indexing_system_module);
10261027

lib/Sema/ImportResolution.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ UnboundImport::UnboundImport(ImportDecl *ID)
570570

571571
import.accessLevel = ID->getAccessLevel();
572572
if (auto attr = ID->getAttrs().getAttribute<AccessControlAttr>()) {
573-
import.accessLevelLoc = attr->getLocation();
573+
import.accessLevelRange = attr->getLocation();
574574
}
575575

576576
if (ID->getAttrs().hasAttribute<SPIOnlyAttr>())
@@ -822,9 +822,9 @@ void UnboundImport::validateResilience(NullablePtr<ModuleDecl> topLevelModule,
822822
SF.getParentModule()->getName());
823823

824824
if (ctx.LangOpts.hasFeature(Feature::AccessLevelOnImport)) {
825-
SourceLoc attrLoc = import.accessLevelLoc;
826-
if (attrLoc.isValid())
827-
inFlight.fixItReplace(attrLoc, "internal");
825+
SourceRange attrRange = import.accessLevelRange;
826+
if (attrRange.isValid())
827+
inFlight.fixItReplace(attrRange, "internal");
828828
else
829829
inFlight.fixItInsert(import.importLoc, "internal ");
830830
} else {

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,27 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
6060
if (D->getDeclContext()->isLocalContext())
6161
return false;
6262

63-
// General check on access-level of the decl.
6463
auto *DC = where.getDeclContext();
64+
auto &Context = DC->getASTContext();
65+
66+
ImportAccessLevel problematicImport = D->getImportAccessFrom(DC);
67+
if (problematicImport.has_value()) {
68+
auto SF = DC->getParentSourceFile();
69+
if (SF)
70+
SF->registerAccessLevelUsingImport(problematicImport.value(),
71+
AccessLevel::Public);
72+
73+
if (Context.LangOpts.EnableModuleApiImportRemarks) {
74+
ModuleDecl *importedVia = problematicImport->module.importedModule,
75+
*sourceModule = D->getModuleContext();
76+
Context.Diags.diagnose(loc, diag::module_api_import,
77+
D, importedVia, sourceModule,
78+
importedVia == sourceModule,
79+
/*isImplicit*/false);
80+
}
81+
}
82+
83+
// General check on access-level of the decl.
6584
auto declAccessScope =
6685
D->getFormalAccessScope(/*useDC=*/DC,
6786
/*allowUsableFromInline=*/true);
@@ -72,8 +91,6 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
7291
if (declAccessScope.isPublic())
7392
return false;
7493

75-
auto &Context = DC->getASTContext();
76-
7794
// Dynamic declarations were mistakenly not checked in Swift 4.2.
7895
// Do enforce the restriction even in pre-Swift-5 modes if the module we're
7996
// building is resilient, though.
@@ -112,10 +129,9 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
112129

113130
Context.Diags.diagnose(D, diag::resilience_decl_declared_here, D);
114131

115-
ImportAccessLevel problematicImport = D->getImportAccessFrom(DC);
116132
if (problematicImport.has_value() &&
117133
problematicImport->accessLevel < D->getFormalAccess()) {
118-
Context.Diags.diagnose(problematicImport->accessLevelLoc,
134+
Context.Diags.diagnose(problematicImport->importLoc,
119135
diag::decl_import_via_here, D,
120136
problematicImport->accessLevel,
121137
problematicImport->module.importedModule);
@@ -133,15 +149,32 @@ static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc,
133149
if (!D)
134150
return false;
135151

152+
auto exportingModule = where.getDeclContext()->getParentModule();
153+
ASTContext &ctx = exportingModule->getASTContext();
154+
155+
ImportAccessLevel problematicImport = D->getImportAccessFrom(
156+
where.getDeclContext());
157+
if (problematicImport.has_value()) {
158+
auto SF = where.getDeclContext()->getParentSourceFile();
159+
if (SF)
160+
SF->registerAccessLevelUsingImport(problematicImport.value(),
161+
AccessLevel::Public);
162+
163+
if (ctx.LangOpts.EnableModuleApiImportRemarks) {
164+
ModuleDecl *importedVia = problematicImport->module.importedModule,
165+
*sourceModule = D->getModuleContext();
166+
ctx.Diags.diagnose(loc, diag::module_api_import_aliases,
167+
D, importedVia, sourceModule,
168+
importedVia == sourceModule);
169+
}
170+
}
171+
136172
auto ignoredDowngradeToWarning = DowngradeToWarning::No;
137173
auto originKind =
138174
getDisallowedOriginKind(D, where, ignoredDowngradeToWarning);
139175
if (originKind == DisallowedOriginKind::None)
140176
return false;
141177

142-
auto exportingModule = where.getDeclContext()->getParentModule();
143-
ASTContext &ctx = exportingModule->getASTContext();
144-
145178
// As an exception, if the import of the module that defines the desugared
146179
// decl is just missing (as opposed to imported explicitly with reduced
147180
// visibility) then we should only diagnose if we're building a resilient
@@ -184,7 +217,7 @@ static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc,
184217
assert(limitImport.has_value() &&
185218
limitImport->accessLevel < AccessLevel::Public &&
186219
"The import should still be non-public");
187-
ctx.Diags.diagnose(limitImport->accessLevelLoc,
220+
ctx.Diags.diagnose(limitImport->importLoc,
188221
diag::decl_import_via_here, D,
189222
limitImport->accessLevel,
190223
limitImport->module.importedModule);
@@ -288,13 +321,30 @@ TypeChecker::diagnoseConformanceExportability(SourceLoc loc,
288321
if (ext->getParentModule()->isBuiltinModule())
289322
return false;
290323

324+
ModuleDecl *M = ext->getParentModule();
325+
ASTContext &ctx = M->getASTContext();
326+
327+
ImportAccessLevel problematicImport = ext->getImportAccessFrom(where.getDeclContext());
328+
if (problematicImport.has_value()) {
329+
auto SF = where.getDeclContext()->getParentSourceFile();
330+
if (SF)
331+
SF->registerAccessLevelUsingImport(problematicImport.value(),
332+
AccessLevel::Public);
333+
334+
if (ctx.LangOpts.EnableModuleApiImportRemarks) {
335+
ModuleDecl *importedVia = problematicImport->module.importedModule,
336+
*sourceModule = ext->getModuleContext();
337+
ctx.Diags.diagnose(loc, diag::module_api_import_conformance,
338+
rootConf->getType(), rootConf->getProtocol(),
339+
importedVia, sourceModule,
340+
importedVia == sourceModule);
341+
}
342+
}
343+
291344
auto originKind = getDisallowedOriginKind(ext, where);
292345
if (originKind == DisallowedOriginKind::None)
293346
return false;
294347

295-
ModuleDecl *M = ext->getParentModule();
296-
ASTContext &ctx = M->getASTContext();
297-
298348
auto reason = where.getExportabilityReason();
299349
if (!reason.has_value())
300350
reason = ExportabilityReason::General;
@@ -323,7 +373,7 @@ TypeChecker::diagnoseConformanceExportability(SourceLoc loc,
323373
assert(limitImport.has_value() &&
324374
limitImport->accessLevel < AccessLevel::Public &&
325375
"The import should still be non-public");
326-
ctx.Diags.diagnose(limitImport->accessLevelLoc,
376+
ctx.Diags.diagnose(limitImport->importLoc,
327377
diag::decl_import_via_here, ext,
328378
limitImport->accessLevel,
329379
limitImport->module.importedModule);

0 commit comments

Comments
 (0)