Skip to content

Commit 2cc9446

Browse files
authored
Merge pull request swiftlang#29412 from brentdax/two-revisions-for-slashing
[SE-0274] Update concise #file implementation in response to first round of review
2 parents c4ed7bd + 62ab3df commit 2cc9446

13 files changed

+392
-17
lines changed

include/swift/AST/DiagnosticsCommon.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ ERROR(sdk_node_unrecognized_decl_kind,none,
139139
ERROR(sdk_node_unrecognized_accessor_kind,none,
140140
"unrecognized accessor kind '%0' in SDK node", (StringRef))
141141

142+
// Emitted from ModuleDecl::computeMagicFileStringMap()
143+
WARNING(pound_source_location_creates_pound_file_conflicts,none,
144+
"'#sourceLocation' directive produces '#file' string of '%0', which "
145+
"conflicts with '#file' strings produced by other paths in the module",
146+
(StringRef))
147+
NOTE(fixit_correct_source_location_file,none,
148+
"change file in '#sourceLocation' to '%0'", (StringRef))
149+
142150
//------------------------------------------------------------------------------
143151
// MARK: Circular reference diagnostics
144152
//------------------------------------------------------------------------------

include/swift/AST/Module.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,38 @@ enum class SourceFileKind {
109109
Interface ///< Came from a .swiftinterface file, representing another module.
110110
};
111111

112+
/// Contains information about where a particular path is used in
113+
/// \c SourceFiles.
114+
struct SourceFilePathInfo {
115+
struct Comparator {
116+
bool operator () (SourceLoc lhs, SourceLoc rhs) const {
117+
return lhs.getOpaquePointerValue() <
118+
rhs.getOpaquePointerValue();
119+
}
120+
};
121+
122+
SourceLoc physicalFileLoc{};
123+
std::set<SourceLoc, Comparator> virtualFileLocs{}; // std::set for sorting
124+
125+
SourceFilePathInfo() = default;
126+
127+
void merge(const SourceFilePathInfo &other) {
128+
if (other.physicalFileLoc.isValid()) {
129+
assert(!physicalFileLoc.isValid());
130+
physicalFileLoc = other.physicalFileLoc;
131+
}
132+
133+
for (auto &elem : other.virtualFileLocs) {
134+
virtualFileLocs.insert(elem);
135+
}
136+
}
137+
138+
bool operator == (const SourceFilePathInfo &other) const {
139+
return physicalFileLoc == other.physicalFileLoc &&
140+
virtualFileLocs == other.virtualFileLocs;
141+
}
142+
};
143+
112144
/// Discriminator for resilience strategy.
113145
enum class ResilienceStrategy : unsigned {
114146
/// Public nominal types: fragile
@@ -257,6 +289,24 @@ class ModuleDecl : public DeclContext, public TypeDecl {
257289
void addFile(FileUnit &newFile);
258290
void removeFile(FileUnit &existingFile);
259291

292+
/// Creates a map from \c #filePath strings to corresponding \c #file
293+
/// strings, diagnosing any conflicts.
294+
///
295+
/// A given \c #filePath string always maps to exactly one \c #file string,
296+
/// but it is possible for \c #sourceLocation directives to introduce
297+
/// duplicates in the opposite direction. If there are such conflicts, this
298+
/// method will diagnose the conflict and choose a "winner" among the paths
299+
/// in a reproducible way. The \c bool paired with the \c #file string is
300+
/// \c true for paths which did not have a conflict or won a conflict, and
301+
/// \c false for paths which lost a conflict. Thus, if you want to generate a
302+
/// reverse mapping, you should drop or special-case the \c #file strings that
303+
/// are paired with \c false.
304+
///
305+
/// Note that this returns an empty StringMap if concise \c #file strings are
306+
/// disabled. Users should fall back to using the file path in this case.
307+
llvm::StringMap<std::pair<std::string, /*isWinner=*/bool>>
308+
computeMagicFileStringMap(bool shouldDiagnose) const;
309+
260310
/// Add a file declaring a cross-import overlay.
261311
void addCrossImportOverlayFile(StringRef file);
262312

include/swift/AST/SourceFile.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@ class SourceFile final : public FileUnit {
321321
/// forwarded on to IRGen.
322322
ASTStage_t ASTStage = Unprocessed;
323323

324+
/// Virtual filenames declared by #sourceLocation(file:) directives in this
325+
/// file.
326+
llvm::SmallVector<Located<StringRef>, 0> VirtualFilenames;
327+
328+
llvm::StringMap<SourceFilePathInfo> getInfoForUsedFilePaths() const;
329+
324330
SourceFile(ModuleDecl &M, SourceFileKind K, Optional<unsigned> bufferID,
325331
ImplicitModuleImportKind ModImpKind, bool KeepParsedTokens = false,
326332
bool KeepSyntaxTree = false, ParsingOptions parsingOpts = {});

lib/AST/Module.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,6 +1955,144 @@ static void performAutoImport(
19551955
SF.addImports(Imports);
19561956
}
19571957

1958+
llvm::StringMap<SourceFilePathInfo>
1959+
SourceFile::getInfoForUsedFilePaths() const {
1960+
llvm::StringMap<SourceFilePathInfo> result;
1961+
1962+
if (BufferID != -1) {
1963+
result[getFilename()].physicalFileLoc =
1964+
getASTContext().SourceMgr.getLocForBufferStart(BufferID);
1965+
}
1966+
1967+
for (auto &vpath : VirtualFilenames) {
1968+
result[vpath.Item].virtualFileLocs.insert(vpath.Loc);
1969+
}
1970+
1971+
return result;
1972+
}
1973+
1974+
/// Returns a map of filenames to a map of file paths to SourceFilePathInfo
1975+
/// instances, for all SourceFiles in the module.
1976+
static llvm::StringMap<llvm::StringMap<SourceFilePathInfo>>
1977+
getInfoForUsedFileNames(const ModuleDecl *module) {
1978+
llvm::StringMap<llvm::StringMap<SourceFilePathInfo>> result;
1979+
1980+
for (auto *file : module->getFiles()) {
1981+
auto *sourceFile = dyn_cast<SourceFile>(file);
1982+
if (!sourceFile) continue;
1983+
1984+
for (auto &pair : sourceFile->getInfoForUsedFilePaths()) {
1985+
StringRef fullPath = pair.first();
1986+
StringRef fileName = llvm::sys::path::filename(fullPath);
1987+
auto &info = pair.second;
1988+
1989+
result[fileName][fullPath].merge(info);
1990+
}
1991+
}
1992+
1993+
return result;
1994+
}
1995+
1996+
static void
1997+
computeMagicFileString(const ModuleDecl *module, StringRef name,
1998+
SmallVectorImpl<char> &result) {
1999+
result.assign(module->getNameStr().begin(), module->getNameStr().end());
2000+
result.push_back('/');
2001+
result.append(name.begin(), name.end());
2002+
}
2003+
2004+
static StringRef
2005+
resolveMagicNameConflicts(const ModuleDecl *module, StringRef fileString,
2006+
const llvm::StringMap<SourceFilePathInfo> &paths,
2007+
bool shouldDiagnose) {
2008+
assert(paths.size() > 1);
2009+
assert(module->getASTContext().LangOpts.EnableConcisePoundFile);
2010+
2011+
/// The path we consider to be "correct"; we will emit fix-its changing the
2012+
/// other paths to match this one.
2013+
StringRef winner = "";
2014+
2015+
// First, select a winner.
2016+
for (const auto &pathPair : paths) {
2017+
// If there is a physical file with this name, we use its path and stop
2018+
// looking.
2019+
if (pathPair.second.physicalFileLoc.isValid()) {
2020+
winner = pathPair.first();
2021+
break;
2022+
}
2023+
2024+
// Otherwise, we favor the lexicographically "smaller" path.
2025+
if (winner.empty() || winner > pathPair.first()) {
2026+
winner = pathPair.first();
2027+
}
2028+
}
2029+
2030+
// If we're not diagnosing, that's all we need to do.
2031+
if (!shouldDiagnose)
2032+
return winner;
2033+
2034+
SmallString<64> winnerLiteral;
2035+
llvm::raw_svector_ostream winnerLiteralStream{winnerLiteral};
2036+
swift::printAsQuotedString(winnerLiteralStream, winner);
2037+
2038+
auto &diags = module->getASTContext().Diags;
2039+
2040+
// Diagnose the conflict at each #sourceLocation that specifies it.
2041+
for (const auto &pathPair : paths) {
2042+
bool isWinner = (pathPair.first() == winner);
2043+
2044+
// Don't diagnose #sourceLocations that match the physical file.
2045+
if (pathPair.second.physicalFileLoc.isValid()) {
2046+
assert(isWinner && "physical files should always win; duplicate name?");
2047+
continue;
2048+
}
2049+
2050+
for (auto loc : pathPair.second.virtualFileLocs) {
2051+
diags.diagnose(loc,
2052+
diag::pound_source_location_creates_pound_file_conflicts,
2053+
fileString);
2054+
2055+
// Offer a fix-it unless it would be tautological.
2056+
if (!isWinner)
2057+
diags.diagnose(loc, diag::fixit_correct_source_location_file, winner)
2058+
.fixItReplace(loc, winnerLiteral);
2059+
}
2060+
}
2061+
2062+
return winner;
2063+
}
2064+
2065+
llvm::StringMap<std::pair<std::string, bool>>
2066+
ModuleDecl::computeMagicFileStringMap(bool shouldDiagnose) const {
2067+
llvm::StringMap<std::pair<std::string, bool>> result;
2068+
SmallString<64> scratch;
2069+
2070+
if (!getASTContext().LangOpts.EnableConcisePoundFile)
2071+
return result;
2072+
2073+
for (auto &namePair : getInfoForUsedFileNames(this)) {
2074+
computeMagicFileString(this, namePair.first(), scratch);
2075+
auto &infoForPaths = namePair.second;
2076+
2077+
assert(!infoForPaths.empty());
2078+
2079+
// TODO: In the future, we'd like to handle these conflicts gracefully by
2080+
// generating a unique `#file` string for each conflicting name. For now, we
2081+
// will simply warn about conflicts.
2082+
StringRef winner = infoForPaths.begin()->first();
2083+
if (infoForPaths.size() > 1)
2084+
winner = resolveMagicNameConflicts(this, scratch, infoForPaths,
2085+
shouldDiagnose);
2086+
2087+
for (auto &pathPair : infoForPaths) {
2088+
result[pathPair.first()] =
2089+
std::make_pair(scratch.str().str(), pathPair.first() == winner);
2090+
}
2091+
}
2092+
2093+
return result;
2094+
}
2095+
19582096
SourceFile::SourceFile(ModuleDecl &M, SourceFileKind K,
19592097
Optional<unsigned> bufferID,
19602098
ImplicitModuleImportKind ModImpKind,

lib/Parse/ParseDecl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4822,7 +4822,8 @@ ParserStatus Parser::parseLineDirective(bool isLine) {
48224822
getStringLiteralIfNotInterpolated(Loc, "'#sourceLocation'");
48234823
if (!Filename.hasValue())
48244824
return makeParserError();
4825-
consumeToken(tok::string_literal);
4825+
SourceLoc filenameLoc = consumeToken(tok::string_literal);
4826+
SF.VirtualFilenames.emplace_back(*Filename, filenameLoc);
48264827

48274828
if (parseToken(tok::comma, diag::sourceLocation_expected, ",") ||
48284829
parseSpecificIdentifier("line", diag::sourceLocation_expected,

lib/SIL/SILPrinter.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include "llvm/Support/CommandLine.h"
4848
#include "llvm/Support/FormattedStream.h"
4949
#include "llvm/Support/FileSystem.h"
50+
#include <set>
5051

5152

5253
using namespace swift;
@@ -2750,6 +2751,57 @@ printSILCoverageMaps(SILPrintContext &Ctx,
27502751
M->print(Ctx);
27512752
}
27522753

2754+
using MagicFileStringMap =
2755+
llvm::StringMap<std::pair<std::string, /*isWinner=*/bool>>;
2756+
2757+
static void
2758+
printMagicFileStringMapEntry(SILPrintContext &Ctx,
2759+
const MagicFileStringMap::MapEntryTy &entry) {
2760+
auto &OS = Ctx.OS();
2761+
OS << "// '" << std::get<0>(entry.second)
2762+
<< "' => '" << entry.first() << "'";
2763+
2764+
if (!std::get<1>(entry.second))
2765+
OS << " (alternate)";
2766+
2767+
OS << "\n";
2768+
}
2769+
2770+
static void printMagicFileStringMap(SILPrintContext &Ctx,
2771+
const MagicFileStringMap map) {
2772+
if (map.empty())
2773+
return;
2774+
2775+
Ctx.OS() << "\n\n// Mappings from '#file' to '#filePath':\n";
2776+
2777+
if (Ctx.sortSIL()) {
2778+
llvm::SmallVector<llvm::StringRef, 16> keys;
2779+
llvm::copy(map.keys(), std::back_inserter(keys));
2780+
2781+
llvm::sort(keys, [&](StringRef leftKey, StringRef rightKey) -> bool {
2782+
const auto &leftValue = map.find(leftKey)->second;
2783+
const auto &rightValue = map.find(rightKey)->second;
2784+
2785+
// Lexicographically earlier #file strings sort earlier.
2786+
if (std::get<0>(leftValue) != std::get<0>(rightValue))
2787+
return std::get<0>(leftValue) < std::get<0>(rightValue);
2788+
2789+
// Conflict winners sort before losers.
2790+
if (std::get<1>(leftValue) != std::get<1>(rightValue))
2791+
return std::get<1>(leftValue);
2792+
2793+
// Finally, lexicographically earlier #filePath strings sort earlier.
2794+
return leftKey < rightKey;
2795+
});
2796+
2797+
for (auto key : keys)
2798+
printMagicFileStringMapEntry(Ctx, *map.find(key));
2799+
} else {
2800+
for (const auto &entry : map)
2801+
printMagicFileStringMapEntry(Ctx, entry);
2802+
}
2803+
}
2804+
27532805
void SILProperty::print(SILPrintContext &Ctx) const {
27542806
PrintOptions Options = PrintOptions::printSIL();
27552807

@@ -2864,6 +2916,10 @@ void SILModule::print(SILPrintContext &PrintCtx, ModuleDecl *M,
28642916
printSILProperties(PrintCtx, getPropertyList());
28652917
printExternallyVisibleDecls(PrintCtx, externallyVisible.getArrayRef());
28662918

2919+
if (M)
2920+
printMagicFileStringMap(
2921+
PrintCtx, M->computeMagicFileStringMap(/*shouldDiagnose=*/false));
2922+
28672923
OS << "\n\n";
28682924
}
28692925

lib/SILGen/SILGen.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ using namespace Lowering;
5050
//===----------------------------------------------------------------------===//
5151

5252
SILGenModule::SILGenModule(SILModule &M, ModuleDecl *SM)
53-
: M(M), Types(M.Types), SwiftModule(SM), TopLevelSGF(nullptr) {
53+
: M(M), Types(M.Types), SwiftModule(SM), TopLevelSGF(nullptr),
54+
MagicFileStringsByFilePath(
55+
SM->computeMagicFileStringMap(/*shouldDiagnose=*/true)) {
5456
const SILOptions &Opts = M.getOptions();
5557
if (!Opts.UseProfile.empty()) {
5658
auto ReaderOrErr = llvm::IndexedInstrProfReader::create(Opts.UseProfile);

lib/SILGen/SILGen.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
131131

132132
ASTContext &getASTContext() { return M.getASTContext(); }
133133

134+
llvm::StringMap<std::pair<std::string, /*isWinner=*/bool>>
135+
MagicFileStringsByFilePath;
136+
134137
static DeclName getMagicFunctionName(SILDeclRef ref);
135138
static DeclName getMagicFunctionName(DeclContext *dc);
136139

lib/SILGen/SILGenApply.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4892,14 +4892,11 @@ StringRef SILGenFunction::getMagicFilePathString(SourceLoc loc) {
48924892
std::string SILGenFunction::getMagicFileString(SourceLoc loc) {
48934893
auto path = getMagicFilePathString(loc);
48944894

4895-
if (!getASTContext().LangOpts.EnableConcisePoundFile)
4896-
return path;
4895+
auto result = SGM.MagicFileStringsByFilePath.find(path);
4896+
if (result != SGM.MagicFileStringsByFilePath.end())
4897+
return std::get<0>(result->second);
48974898

4898-
auto value = llvm::sys::path::filename(path).str();
4899-
value += " (";
4900-
value += getModule().getSwiftModule()->getNameStr();
4901-
value += ")";
4902-
return value;
4899+
return path;
49034900
}
49044901

49054902
/// Emit an application of the given allocating initializer.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// This file matches test/SILGen/magic_identifier_file_conflicting.swift.
2+
// It should be compiled with -verify.
3+
4+
// We should diagnose cross-file conflicts.
5+
// expected-warning@+2 {{'#sourceLocation' directive produces '#file' string of 'Foo/other_file_c.swift', which conflicts with '#file' strings produced by other paths in the module}}
6+
// expected-note@+1 {{change file in '#sourceLocation' to 'first/other_file_c.swift'}} {{23-50="first/other_file_c.swift"}}
7+
#sourceLocation(file: "second/other_file_c.swift", line: 1)
8+
#sourceLocation()

0 commit comments

Comments
 (0)