Skip to content

Commit a0b848e

Browse files
committed
[Sema] Report imports marked as public that are not used in API
1 parent 8dcfadd commit a0b848e

17 files changed

+167
-25
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2451,6 +2451,12 @@ REMARK(add_predates_concurrency_import,none,
24512451
"%select{| as warnings}0", (bool, Identifier))
24522452
REMARK(remove_predates_concurrency_import,none,
24532453
"'@preconcurrency' attribute on module %0 is unused", (Identifier))
2454+
WARNING(remove_public_import,none,
2455+
"public import of %0 was not used in public declarations or inlinable code",
2456+
(const ModuleDecl *))
2457+
WARNING(remove_package_import,none,
2458+
"package import of %0 was not used in package declarations",
2459+
(const ModuleDecl *))
24542460
WARNING(public_decl_needs_sendable,none,
24552461
"public %kind0 does not specify whether it is 'Sendable' or not",
24562462
(const ValueDecl *))

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,

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/Sema/ResilienceDiagnostics.cpp

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,18 @@ 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+
74+
// General check on access-level of the decl.
6575
auto declAccessScope =
6676
D->getFormalAccessScope(/*useDC=*/DC,
6777
/*allowUsableFromInline=*/true);
@@ -72,8 +82,6 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
7282
if (declAccessScope.isPublic())
7383
return false;
7484

75-
auto &Context = DC->getASTContext();
76-
7785
// Dynamic declarations were mistakenly not checked in Swift 4.2.
7886
// Do enforce the restriction even in pre-Swift-5 modes if the module we're
7987
// building is resilient, though.
@@ -112,7 +120,6 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
112120

113121
Context.Diags.diagnose(D, diag::resilience_decl_declared_here, D);
114122

115-
ImportAccessLevel problematicImport = D->getImportAccessFrom(DC);
116123
if (problematicImport.has_value() &&
117124
problematicImport->accessLevel < D->getFormalAccess()) {
118125
Context.Diags.diagnose(problematicImport->importLoc,
@@ -133,15 +140,24 @@ static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc,
133140
if (!D)
134141
return false;
135142

143+
auto exportingModule = where.getDeclContext()->getParentModule();
144+
ASTContext &ctx = exportingModule->getASTContext();
145+
146+
ImportAccessLevel problematicImport = D->getImportAccessFrom(
147+
where.getDeclContext());
148+
if (problematicImport.has_value()) {
149+
auto SF = where.getDeclContext()->getParentSourceFile();
150+
if (SF)
151+
SF->registerAccessLevelUsingImport(problematicImport.value(),
152+
AccessLevel::Public);
153+
}
154+
136155
auto ignoredDowngradeToWarning = DowngradeToWarning::No;
137156
auto originKind =
138157
getDisallowedOriginKind(D, where, ignoredDowngradeToWarning);
139158
if (originKind == DisallowedOriginKind::None)
140159
return false;
141160

142-
auto exportingModule = where.getDeclContext()->getParentModule();
143-
ASTContext &ctx = exportingModule->getASTContext();
144-
145161
// As an exception, if the import of the module that defines the desugared
146162
// decl is just missing (as opposed to imported explicitly with reduced
147163
// visibility) then we should only diagnose if we're building a resilient
@@ -184,7 +200,7 @@ static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc,
184200
assert(limitImport.has_value() &&
185201
limitImport->accessLevel < AccessLevel::Public &&
186202
"The import should still be non-public");
187-
ctx.Diags.diagnose(limitImport->accessLevelLoc,
203+
ctx.Diags.diagnose(limitImport->importLoc,
188204
diag::decl_import_via_here, D,
189205
limitImport->accessLevel,
190206
limitImport->module.importedModule);
@@ -288,13 +304,21 @@ TypeChecker::diagnoseConformanceExportability(SourceLoc loc,
288304
if (ext->getParentModule()->isBuiltinModule())
289305
return false;
290306

307+
ModuleDecl *M = ext->getParentModule();
308+
ASTContext &ctx = M->getASTContext();
309+
310+
ImportAccessLevel problematicImport = ext->getImportAccessFrom(where.getDeclContext());
311+
if (problematicImport.has_value()) {
312+
auto SF = where.getDeclContext()->getParentSourceFile();
313+
if (SF)
314+
SF->registerAccessLevelUsingImport(problematicImport.value(),
315+
AccessLevel::Public);
316+
}
317+
291318
auto originKind = getDisallowedOriginKind(ext, where);
292319
if (originKind == DisallowedOriginKind::None)
293320
return false;
294321

295-
ModuleDecl *M = ext->getParentModule();
296-
ASTContext &ctx = M->getASTContext();
297-
298322
auto reason = where.getExportabilityReason();
299323
if (!reason.has_value())
300324
reason = ExportabilityReason::General;
@@ -323,7 +347,7 @@ TypeChecker::diagnoseConformanceExportability(SourceLoc loc,
323347
assert(limitImport.has_value() &&
324348
limitImport->accessLevel < AccessLevel::Public &&
325349
"The import should still be non-public");
326-
ctx.Diags.diagnose(limitImport->accessLevelLoc,
350+
ctx.Diags.diagnose(limitImport->importLoc,
327351
diag::decl_import_via_here, ext,
328352
limitImport->accessLevel,
329353
limitImport->module.importedModule);

lib/Sema/TypeCheckAccess.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,35 @@ void AccessControlCheckerBase::checkTypeAccessImpl(
191191
contextAccessScope.getDeclContext()->isLocalContext())
192192
return;
193193

194+
// Report where it was imported from.
195+
if (contextAccessScope.isPublic() ||
196+
contextAccessScope.isPackage()) {
197+
auto SF = useDC->getParentSourceFile();
198+
auto report = [&](const IdentTypeRepr *typeRepr, const ValueDecl *VD) {
199+
ImportAccessLevel import = VD->getImportAccessFrom(useDC);
200+
if (import.has_value()) {
201+
if (SF) {
202+
auto useLevel = contextAccessScope.isPublic() ? AccessLevel::Public
203+
: AccessLevel::Package;
204+
SF->registerAccessLevelUsingImport(import.value(), useLevel);
205+
}
206+
}
207+
};
208+
209+
if (typeRepr) {
210+
typeRepr->walk(TypeReprIdentFinder([&](const IdentTypeRepr *TR) {
211+
const ValueDecl *VD = TR->getBoundDecl();
212+
report(TR, VD);
213+
return true;
214+
}));
215+
} else if (type) {
216+
type.walk(SimpleTypeDeclFinder([&](const ValueDecl *VD) {
217+
report(/*typeRepr=*/nullptr, VD);
218+
return TypeWalker::Action::Continue;
219+
}));
220+
}
221+
};
222+
194223
AccessScope problematicAccessScope = AccessScope::getPublic();
195224

196225
if (type) {
@@ -2358,6 +2387,39 @@ DisallowedOriginKind swift::getDisallowedOriginKind(const Decl *decl,
23582387
return getDisallowedOriginKind(decl, where, downgradeToWarning);
23592388
}
23602389

2390+
void swift::diagnoseUnnecessaryPublicImports(SourceFile &SF) {
2391+
ASTContext &ctx = SF.getASTContext();
2392+
if (ctx.TypeCheckerOpts.SkipFunctionBodies != FunctionBodySkipping::None)
2393+
return;
2394+
2395+
for (const auto &import : SF.getImports()) {
2396+
// Only check imports with an explicit access level above internal.
2397+
if (!import.accessLevelRange.isValid() ||
2398+
import.accessLevel <= AccessLevel::Internal)
2399+
continue;
2400+
2401+
AccessLevel levelUsed = SF.getMaxAccessLevelUsingImport(import);
2402+
if (import.accessLevel > levelUsed) {
2403+
auto diagId = import.accessLevel == AccessLevel::Public ?
2404+
diag::remove_public_import :
2405+
diag::remove_package_import;
2406+
2407+
auto inFlight = ctx.Diags.diagnose(import.importLoc,
2408+
diagId,
2409+
import.module.importedModule);
2410+
2411+
if (levelUsed == AccessLevel::Package) {
2412+
inFlight.fixItReplace(import.accessLevelRange, "package");
2413+
} else if (ctx.isSwiftVersionAtLeast(6)) {
2414+
// Let it default to internal.
2415+
inFlight.fixItRemove(import.accessLevelRange);
2416+
} else {
2417+
inFlight.fixItReplace(import.accessLevelRange, "internal");
2418+
}
2419+
}
2420+
}
2421+
}
2422+
23612423
void swift::checkAccessControl(Decl *D) {
23622424
if (isa<ValueDecl>(D) || isa<PatternBindingDecl>(D)) {
23632425
bool allowInlineable =

lib/Sema/TypeChecker.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ TypeCheckSourceFileRequest::evaluate(Evaluator &eval, SourceFile *SF) const {
297297
}
298298

299299
diagnoseUnnecessaryPreconcurrencyImports(*SF);
300+
diagnoseUnnecessaryPublicImports(*SF);
300301

301302
// Check to see if there are any inconsistent imports.
302303
evaluateOrDefault(

lib/Sema/TypeChecker.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,9 @@ class DiscriminatorFinder : public ASTWalker {
15471547
}
15481548
};
15491549

1550+
/// Report imports that are marked public but are not used in API.
1551+
void diagnoseUnnecessaryPublicImports(SourceFile &SF);
1552+
15501553
} // end namespace swift
15511554

15521555
#endif

test/Sema/access-level-and-non-resilient-import.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545

4646
import DefaultLib // expected-error {{module 'DefaultLib' was not compiled with library evolution support; using it means binary compatibility for 'Client_Swift5' can't be guaranteed}} {{1-1=internal }}
4747
public import PublicLib // expected-error {{module 'PublicLib' was not compiled with library evolution support; using it means binary compatibility for 'Client_Swift5' can't be guaranteed}} {{1-7=internal}}
48+
// expected-warning @-1 {{public import of 'PublicLib' was not used in public declarations or inlinable code}}
4849
package import PackageLib
50+
// expected-warning @-1 {{package import of 'PackageLib' was not used in package declarations}}
4951
internal import InternalLib
5052
fileprivate import FileprivateLib
5153
private import PrivateLib
@@ -54,7 +56,9 @@ private import PrivateLib
5456

5557
import DefaultLib
5658
public import PublicLib // expected-error {{module 'PublicLib' was not compiled with library evolution support; using it means binary compatibility for 'Client_Swift6' can't be guaranteed}} {{1-7=internal}}
59+
// expected-warning @-1 {{public import of 'PublicLib' was not used in public declarations or inlinable code}}
5760
package import PackageLib
61+
// expected-warning @-1 {{package import of 'PackageLib' was not used in package declarations}}
5862
internal import InternalLib
5963
fileprivate import FileprivateLib
6064
private import PrivateLib

test/Sema/access-level-import-diag-priority.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ public func publicFuncUsesPackageLevelPackageImportedType(_ a: PackageLevelPacka
9191

9292
/// Local vs imports
9393
//--- LocalVsImportClient.swift
94-
public import PublicLib
9594
internal import InternalLib
9695
fileprivate import FileprivateLib // expected-note {{struct 'FileprivateImportType' imported as 'fileprivate' from 'FileprivateLib' here}}
9796

test/Sema/access-level-import-exportability.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,10 @@ public struct PrivateLibWrapper<T> {
119119

120120
/// Short test mostly to count the notes.
121121
//--- MinimalClient.swift
122-
public import PublicLib
123-
package import PackageLib
124-
internal import InternalLib // expected-note@:1 {{struct 'InternalImportType' imported as 'internal' from 'InternalLib' here}}
125-
fileprivate import FileprivateLib // expected-note@:1 {{class 'FileprivateImportClass' imported as 'fileprivate' from 'FileprivateLib' here}}
122+
public import PublicLib // expected-warning {{public import of 'PublicLib' was not used in public declarations or inlinable code}}
123+
package import PackageLib // expected-warning {{package import of 'PackageLib' was not used in package declarations}}
124+
internal import InternalLib // expected-note {{struct 'InternalImportType' imported as 'internal' from 'InternalLib' here}}
125+
fileprivate import FileprivateLib // expected-note {{class 'FileprivateImportClass' imported as 'fileprivate' from 'FileprivateLib' here}}
126126
private import PrivateLib
127127

128128
public func PublicFuncUsesInternal(_: InternalImportType) { // expected-error {{function cannot be declared public because its parameter uses an internal type}}

0 commit comments

Comments
 (0)