Skip to content

Commit e6aee25

Browse files
authored
Merge pull request #35904 from beccadax/go-back-to-the-shadow
Warn about module name shadowing in interfaces
2 parents 1f0225b + ba3b261 commit e6aee25

File tree

6 files changed

+165
-17
lines changed

6 files changed

+165
-17
lines changed

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,13 @@ ERROR(error_opening_explicit_module_file,none,
353353
"failed to open explicit Swift module: %0", (StringRef))
354354
ERROR(error_extracting_flags_from_module_interface,none,
355355
"error extracting flags from module interface", ())
356+
WARNING(warning_module_shadowing_may_break_module_interface,none,
357+
"public %0 %1 shadows module %2, which may cause failures when "
358+
"importing %3 or its clients in some configurations; please rename "
359+
"either the %0 %1 or the module %2, or see "
360+
"https://bugs.swift.org/browse/SR-14195 for workarounds",
361+
(DescriptiveDeclKind, FullyQualified<Type>,
362+
/*shadowedModule=*/ModuleDecl *, /*interfaceModule*/ModuleDecl *))
356363
REMARK(rebuilding_module_from_interface,none,
357364
"rebuilding module '%0' from interface '%1'", (StringRef, StringRef))
358365
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: 111 additions & 17 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"
@@ -36,24 +37,9 @@
3637

3738
using namespace swift;
3839

39-
version::Version swift::InterfaceFormatVersion({1, 0});
40+
// MARK: Module interface header comments
4041

41-
/// Diagnose any scoped imports in \p imports, i.e. those with a non-empty
42-
/// access path. These are not yet supported by module interfaces, since the
43-
/// information about the declaration kind is not preserved through the binary
44-
/// serialization that happens as an intermediate step in non-whole-module
45-
/// builds.
46-
///
47-
/// These come from declarations like `import class FooKit.MainFooController`.
48-
static void diagnoseScopedImports(DiagnosticEngine &diags,
49-
ArrayRef<ImportedModule> imports){
50-
for (const ImportedModule &importPair : imports) {
51-
if (importPair.accessPath.empty())
52-
continue;
53-
diags.diagnose(importPair.accessPath.front().Loc,
54-
diag::module_interface_scoped_import_unsupported);
55-
}
56-
}
42+
version::Version swift::InterfaceFormatVersion({1, 0});
5743

5844
/// Prints to \p out a comment containing a format version number, tool version
5945
/// string as well as any relevant command-line flags in \p Opts used to
@@ -93,6 +79,106 @@ llvm::Regex swift::getSwiftInterfaceCompilerVersionRegex() {
9379
": (.+)$", llvm::Regex::Newline);
9480
}
9581

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+
163+
// MARK: Import statements
164+
165+
/// Diagnose any scoped imports in \p imports, i.e. those with a non-empty
166+
/// access path. These are not yet supported by module interfaces, since the
167+
/// information about the declaration kind is not preserved through the binary
168+
/// serialization that happens as an intermediate step in non-whole-module
169+
/// builds.
170+
///
171+
/// These come from declarations like `import class FooKit.MainFooController`.
172+
static void diagnoseScopedImports(DiagnosticEngine &diags,
173+
ArrayRef<ImportedModule> imports){
174+
for (const ImportedModule &importPair : imports) {
175+
if (importPair.accessPath.empty())
176+
continue;
177+
diags.diagnose(importPair.accessPath.front().Loc,
178+
diag::module_interface_scoped_import_unsupported);
179+
}
180+
}
181+
96182
/// Prints the imported modules in \p M to \p out in the form of \c import
97183
/// source declarations.
98184
static void printImports(raw_ostream &out,
@@ -171,9 +257,13 @@ static void printImports(raw_ostream &out,
171257
}
172258

173259
out << "\n";
260+
261+
diagnoseIfModuleImportsShadowingDecl(Opts, importedModule, M);
174262
}
175263
}
176264

265+
// MARK: Dummy protocol conformances
266+
177267
// FIXME: Copied from ASTPrinter.cpp...
178268
static bool isPublicOrUsableFromInline(const ValueDecl *VD) {
179269
AccessScope scope =
@@ -553,6 +643,8 @@ const StringLiteral InheritedProtocolCollector::DummyProtocolName =
553643
"_ConstraintThatIsNotPartOfTheAPIOfThisLibrary";
554644
} // end anonymous namespace
555645

646+
// MARK: Interface emission
647+
556648
bool swift::emitSwiftInterface(raw_ostream &out,
557649
ModuleInterfaceOptions const &Opts,
558650
ModuleDecl *M) {
@@ -580,6 +672,8 @@ bool swift::emitSwiftInterface(raw_ostream &out,
580672

581673
D->print(out, printOptions);
582674
out << "\n";
675+
676+
diagnoseIfDeclShadowsKnownModule(Opts, const_cast<Decl *>(D), M);
583677
}
584678

585679
// 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 may cause failures when importing 'module_shadowing' or its clients in some configurations; please rename either the class 'ShadowyHorror.module_shadowing' or the module 'module_shadowing', or see https://bugs.swift.org/browse/SR-14195 for workarounds{{$}}
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 may cause failures when importing 'module_shadowing' or its clients in some configurations; please rename either the struct 'module_shadowing.ShadowyHorror' or the module 'ShadowyHorror', or see https://bugs.swift.org/browse/SR-14195 for workarounds{{$}}
34+
// SELF: module_shadowing.swift:[[@LINE-2]]:15: warning: public struct 'ShadowyHorror.ShadowyHorror' shadows module 'ShadowyHorror', which may cause failures when importing 'ShadowyHorror' or its clients in some configurations; please rename either the struct 'ShadowyHorror.ShadowyHorror' or the module 'ShadowyHorror', or see https://bugs.swift.org/browse/SR-14195 for workarounds{{$}}
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)