Skip to content

Commit c2f3725

Browse files
committed
Warn about module name shadowing in interfaces
There is a known issue with module interfaces where a type with the same name as a module will disrupt references to types in that module. Fully fixing it will require a new language feature (SR-898) which is not yet available. In the meantime, module interfaces support a workaround flag (“-Xfrontend -module-interface-preserve-types-as-written”) which prints an alternate form that usually works. However, you have to know to add this flag, and it’s not obvious because nothing breaks until a compiler tries to consume the affected module interface (or sometimes even one of its clients). This commit emits a warning during module interface emission whenever the module interface either imports a type with the same name as the module being built, or declares a type with the same name as a visible module. This lets the user know that the type may cause problems and they might need to implement a workaround.
1 parent cfa2983 commit c2f3725

File tree

6 files changed

+142
-0
lines changed

6 files changed

+142
-0
lines changed

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,15 @@ ERROR(error_opening_explicit_module_file,none,
350350
"failed to open explicit Swift module: %0", (StringRef))
351351
ERROR(error_extracting_flags_from_module_interface,none,
352352
"error extracting flags from module interface", ())
353+
WARNING(warning_module_shadowing_may_break_module_interface,none,
354+
"public %0 %1 shadows module %2, which is permitted by Swift but may "
355+
"stop other compiler versions from importing module %3 or its clients; "
356+
"please work around this language bug (SR-898) by renaming either the "
357+
"%0 %1 or the module %2, or by passing the '-Xfrontend "
358+
"-module-interface-preserve-types-as-written' flags to the Swift "
359+
"compiler",
360+
(DescriptiveDeclKind, FullyQualified<Type>,
361+
/*shadowedModule=*/ModuleDecl *, /*interfaceModule*/ModuleDecl *))
353362
REMARK(rebuilding_module_from_interface,none,
354363
"rebuilding module '%0' from interface '%1'", (StringRef, StringRef))
355364
NOTE(sdk_version_pbm_version,none,

lib/AST/Module.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,6 +2051,8 @@ bool SourceFile::isImportedImplementationOnly(const ModuleDecl *module) const {
20512051
}
20522052

20532053
bool ModuleDecl::isImportedImplementationOnly(const ModuleDecl *module) const {
2054+
if (module == this) return false;
2055+
20542056
auto &imports = getASTContext().getImportCache();
20552057

20562058
// Look through non-implementation-only imports to see if module is imported

lib/Frontend/ModuleInterfaceSupport.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "swift/AST/ExistentialLayout.h"
1919
#include "swift/AST/FileSystem.h"
2020
#include "swift/AST/Module.h"
21+
#include "swift/AST/ModuleNameLookup.h"
2122
#include "swift/AST/ProtocolConformance.h"
2223
#include "swift/Basic/STLExtras.h"
2324
#include "swift/Frontend/Frontend.h"
@@ -78,6 +79,87 @@ llvm::Regex swift::getSwiftInterfaceCompilerVersionRegex() {
7879
": (.+)$", llvm::Regex::Newline);
7980
}
8081

82+
// MARK: Module name shadowing warnings (SR-898)
83+
//
84+
// When swiftc emits a module interface, it qualifies most types with their
85+
// module name. This usually makes the interface less ambiguous, but if a type
86+
// exists with the same name as a module, then references to that module will
87+
// incorrectly look inside the type instead. This breakage is not obvious until
88+
// someone tries to load the module interface, and may sometimes only occur in
89+
// clients' module interfaces.
90+
//
91+
// Truly fixing this will require a new module-qualification syntax which
92+
// completely ignores shadowing. In lieu of that, we detect and warn about three
93+
// common examples which are relatively actionable:
94+
//
95+
// 1. An `import` statement written into the module interface will
96+
// (transitively) import a type with the module interface's name.
97+
//
98+
// 2. The module interface declares a type with the same name as the module the
99+
// interface is for.
100+
//
101+
// 3. The module interface declares a type with the same name as a module it has
102+
// (transitively) imported without `@_implementationOnly`.
103+
//
104+
// We do not check for shadowing between imported module names and imported
105+
// declarations; this is both much rarer and much more difficult to solve.
106+
// We silence these warnings if you use the temporary workaround flag,
107+
// '-module-interface-preserve-types-as-written'.
108+
109+
/// Emit a warning explaining that \p shadowingDecl will interfere with
110+
/// references to types in \p shadowedModule in the module interfaces of
111+
/// \p brokenModule and its clients.
112+
static void
113+
diagnoseDeclShadowsModule(ModuleInterfaceOptions const &Opts,
114+
TypeDecl *shadowingDecl, ModuleDecl *shadowedModule,
115+
ModuleDecl *brokenModule) {
116+
if (Opts.PreserveTypesAsWritten || shadowingDecl == shadowedModule)
117+
return;
118+
119+
shadowingDecl->diagnose(
120+
diag::warning_module_shadowing_may_break_module_interface,
121+
shadowingDecl->getDescriptiveKind(),
122+
FullyQualified<Type>(shadowingDecl->getDeclaredInterfaceType()),
123+
shadowedModule, brokenModule);
124+
}
125+
126+
/// Check whether importing \p importedModule will bring in any declarations
127+
/// that will shadow \p importingModule, and diagnose them if so.
128+
static void
129+
diagnoseIfModuleImportsShadowingDecl(ModuleInterfaceOptions const &Opts,
130+
ModuleDecl *importedModule,
131+
ModuleDecl *importingModule) {
132+
using namespace namelookup;
133+
134+
SmallVector<ValueDecl *, 4> decls;
135+
lookupInModule(importedModule, importingModule->getName(), decls,
136+
NLKind::UnqualifiedLookup, ResolutionKind::TypesOnly,
137+
importedModule,
138+
NL_UnqualifiedDefault | NL_IncludeUsableFromInline);
139+
for (auto decl : decls)
140+
diagnoseDeclShadowsModule(Opts, cast<TypeDecl>(decl), importingModule,
141+
importingModule);
142+
}
143+
144+
/// Check whether \p D will shadow any modules imported by \p M, and diagnose
145+
/// them if so.
146+
static void diagnoseIfDeclShadowsKnownModule(ModuleInterfaceOptions const &Opts,
147+
Decl *D, ModuleDecl *M) {
148+
ASTContext &ctx = M->getASTContext();
149+
150+
// We only care about types (and modules, which are a subclass of TypeDecl);
151+
// when the grammar expects a type name, it ignores non-types during lookup.
152+
TypeDecl *TD = dyn_cast<TypeDecl>(D);
153+
if (!TD)
154+
return;
155+
156+
ModuleDecl *shadowedModule = ctx.getLoadedModule(TD->getName());
157+
if (!shadowedModule || M->isImportedImplementationOnly(shadowedModule))
158+
return;
159+
160+
diagnoseDeclShadowsModule(Opts, TD, shadowedModule, M);
161+
}
162+
81163
// MARK: Import statements
82164

83165
/// Diagnose any scoped imports in \p imports, i.e. those with a non-empty
@@ -175,6 +257,8 @@ static void printImports(raw_ostream &out,
175257
}
176258

177259
out << "\n";
260+
261+
diagnoseIfModuleImportsShadowingDecl(Opts, importedModule, M);
178262
}
179263
}
180264

@@ -596,6 +680,8 @@ bool swift::emitSwiftInterface(raw_ostream &out,
596680

597681
D->print(out, printOptions);
598682
out << "\n";
683+
684+
diagnoseIfDeclShadowsKnownModule(Opts, const_cast<Decl *>(D), M);
599685
}
600686

601687
// Print dummy extensions for any protocols that were indirectly conformed to.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
public class module_shadowing {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
public struct TestStruct {
22
public init() {}
33
}
4+
5+
public enum module_shadowing {}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// RUN: %empty-directory(%t/mcp)
2+
3+
// Build modules imported by this file.
4+
// RUN: %empty-directory(%t/lib)
5+
// RUN: %target-swift-frontend -typecheck -parse-stdlib -module-cache-path %t/mcp -emit-module-interface-path %t/lib/ShadowyHorror.swiftinterface %S/Inputs/ShadowyHorror.swift -enable-library-evolution -module-name ShadowyHorror -swift-version 5
6+
// RUN: %target-swift-frontend -typecheck -parse-stdlib -emit-module-interface-path %t/lib/TestModule.swiftinterface %S/Inputs/TestModule.swift -enable-library-evolution -module-name TestModule -swift-version 5
7+
8+
// Build this file as a module and check that it emits the expected warnings.
9+
// RUN: %empty-directory(%t/subtest-1)
10+
// RUN: %target-swift-frontend -typecheck -parse-stdlib -module-cache-path %t/mcp -emit-module-interface-path %t/subtest-1/module_shadowing.swiftinterface %s -enable-library-evolution -module-name module_shadowing -I %t/lib -swift-version 5 2>&1 | %FileCheck --check-prefix OTHER --implicit-check-not TestModule %s
11+
12+
// Make sure that preserve-types-as-written disables this warning.
13+
// RUN: %empty-directory(%t/subtest-2)
14+
// RUN: %target-swift-frontend -typecheck -parse-stdlib -module-cache-path %t/mcp -emit-module-interface-path %t/subtest-2/module_shadowing.swiftinterface %s -enable-library-evolution -module-name module_shadowing -I %t/lib -module-interface-preserve-types-as-written -swift-version 5 -verify
15+
16+
// Build this module in a different configuration where it will shadow itself.
17+
// RUN: %empty-directory(%t/subtest-3)
18+
// RUN: %target-swift-frontend -typecheck -parse-stdlib -module-cache-path %t/mcp -emit-module-interface-path %t/subtest-3/ShadowyHorror.swiftinterface %s -enable-library-evolution -module-name ShadowyHorror -DSELF_SHADOW -swift-version 5 2>&1 | %FileCheck --check-prefix SELF --implicit-check-not TestModule %s
19+
20+
// Verify that the module-shadowing bugs we're trying to address haven't been fixed. (SR-898)
21+
// RUN: not %target-swift-frontend -typecheck-module-from-interface %t/subtest-1/module_shadowing.swiftinterface -module-cache-path %t/mcp -I %t/lib -module-name module_shadowing 2>&1 | %FileCheck --check-prefix VERIFICATION %s
22+
// RUN: %target-swift-frontend -typecheck-module-from-interface %t/subtest-2/module_shadowing.swiftinterface -module-cache-path %t/mcp -I %t/lib -module-name module_shadowing
23+
// RUN: not %target-swift-frontend -typecheck-module-from-interface %t/subtest-3/ShadowyHorror.swiftinterface -module-cache-path %t/mcp -I %t/lib -module-name ShadowyHorror 2>&1 | %FileCheck --check-prefix VERIFICATION %s
24+
25+
#if !SELF_SHADOW
26+
import ShadowyHorror
27+
// OTHER-DAG: ShadowyHorror.module_shadowing:{{[0-9]+:[0-9]+}}: warning: public class 'ShadowyHorror.module_shadowing' shadows module 'module_shadowing', which is permitted by Swift but may stop other compiler versions from importing module 'module_shadowing' or its clients; please work around this language bug (SR-898) by renaming either the class 'ShadowyHorror.module_shadowing' or the module 'module_shadowing', or by passing the '-Xfrontend -module-interface-preserve-types-as-written' flags to the Swift compiler{{$}}
28+
29+
@_implementationOnly import TestModule
30+
#endif
31+
32+
public struct ShadowyHorror {
33+
// OTHER-DAG: module_shadowing.swift:[[@LINE-1]]:15: warning: public struct 'module_shadowing.ShadowyHorror' shadows module 'ShadowyHorror', which is permitted by Swift but may stop other compiler versions from importing module 'module_shadowing' or its clients; please work around this language bug (SR-898) by renaming either the struct 'module_shadowing.ShadowyHorror' or the module 'ShadowyHorror', or by passing the '-Xfrontend -module-interface-preserve-types-as-written' flags to the Swift compiler{{$}}
34+
// SELF: module_shadowing.swift:[[@LINE-2]]:15: warning: public struct 'ShadowyHorror.ShadowyHorror' shadows module 'ShadowyHorror', which is permitted by Swift but may stop other compiler versions from importing module 'ShadowyHorror' or its clients; please work around this language bug (SR-898) by renaming either the struct 'ShadowyHorror.ShadowyHorror' or the module 'ShadowyHorror', or by passing the '-Xfrontend -module-interface-preserve-types-as-written' flags to the Swift compiler{{$}}
35+
36+
init() {}
37+
public var property: ShadowyHorror { ShadowyHorror() }
38+
// VERIFICATION: error: 'ShadowyHorror' is not a member type of {{class|struct}} 'ShadowyHorror.{{ShadowyHorror|module_shadowing}}'
39+
// VERIFICATION: error: failed to verify module interface of '{{ShadowyHorror|module_shadowing}}' due to the errors above;
40+
}
41+
42+
public struct TestModule {}

0 commit comments

Comments
 (0)