Skip to content

Commit d0b649a

Browse files
committed
RangeSpecificDiagnosticConsumer -> FileSpecificDiagnosticConsumer
It turns out that we need to have the diagnostic consumers set up before we've actually opened the input files, which makes sense because we might want to emit diagnostics about not being able to open an input file. Switch to using file names instead, and mapping those over to source ranges only once we actually need to handle a diagnostic with a valid source location.
1 parent c18c9f5 commit d0b649a

File tree

3 files changed

+306
-115
lines changed

3 files changed

+306
-115
lines changed

include/swift/AST/DiagnosticConsumer.h

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,24 +113,44 @@ class NullDiagnosticConsumer : public DiagnosticConsumer {
113113
const DiagnosticInfo &Info) override;
114114
};
115115

116-
/// \brief DiagnosticConsumer that funnels diagnostics in certain source ranges
117-
/// to particular sub-consumers.
116+
/// \brief DiagnosticConsumer that funnels diagnostics in certain files to
117+
/// particular sub-consumers.
118118
///
119-
/// Diagnostics that are not in one of the special ranges are emitted into every
120-
/// sub-consumer.
121-
class RangeSpecificDiagnosticConsumer : public DiagnosticConsumer {
119+
/// The intended use case for such a consumer is "batch mode" compilations,
120+
/// where we want to record diagnostics for each file as if they were compiled
121+
/// separately. This is important for incremental builds, so that if a file has
122+
/// warnings but doesn't get recompiled in the next build, the warnings persist.
123+
///
124+
/// Diagnostics that are not in one of the special files are emitted into every
125+
/// sub-consumer. This is necessary to deal with, for example, diagnostics in a
126+
/// bridging header imported from Objective-C, which isn't really about the
127+
/// current file.
128+
class FileSpecificDiagnosticConsumer : public DiagnosticConsumer {
122129
public:
130+
/// A diagnostic consumer, along with the name of the buffer that it should
131+
/// be associated with. An empty string means that a consumer is not
132+
/// associated with any particular buffer, and should only receive diagnostics
133+
/// that are not in any of the other consumers' files.
123134
using ConsumerPair =
124-
std::pair<CharSourceRange, std::unique_ptr<DiagnosticConsumer>>;
135+
std::pair<std::string, std::unique_ptr<DiagnosticConsumer>>;
125136

126137
private:
127-
SmallVector<std::unique_ptr<DiagnosticConsumer>, 4> SubConsumers;
138+
/// All consumers owned by this FileSpecificDiagnosticConsumer.
139+
const SmallVector<ConsumerPair, 4> SubConsumers;
140+
141+
using ConsumersOrderedByRangeEntry =
142+
std::pair<CharSourceRange, DiagnosticConsumer *>;
128143

129-
/// A "map" sorted by the end locations of the ranges, so that a lookup by
130-
/// position can be done using binary search.
144+
/// The consumers owned by this FileSpecificDiagnosticConsumer, sorted by
145+
/// the end locations of each file so that a lookup by position can be done
146+
/// using binary search.
147+
///
148+
/// Generated and cached when the first diagnostic with a location is emitted.
149+
/// This allows diagnostics to be emitted before files are actually opened,
150+
/// as long as they don't have source locations.
131151
///
132152
/// \see #consumerForLocation
133-
SmallVector<std::pair<CharSourceRange, unsigned>, 4> LocationToConsumerMap;
153+
SmallVector<ConsumersOrderedByRangeEntry, 4> ConsumersOrderedByRange;
134154

135155
/// Indicates which consumer to send Note diagnostics too.
136156
///
@@ -141,12 +161,12 @@ class RangeSpecificDiagnosticConsumer : public DiagnosticConsumer {
141161
DiagnosticConsumer *ConsumerForSubsequentNotes = nullptr;
142162

143163
public:
144-
/// Takes ownership of the DiagnosticConsumers specified in \p consumers and
145-
/// records their association with the CharSourceRanges.
164+
/// Takes ownership of the DiagnosticConsumers specified in \p consumers.
146165
///
147-
/// The ranges must not be overlapping.
148-
explicit RangeSpecificDiagnosticConsumer(
149-
MutableArrayRef<ConsumerPair> consumers);
166+
/// There must not be two consumers for the same file (i.e., having the same
167+
/// buffer name).
168+
explicit FileSpecificDiagnosticConsumer(
169+
SmallVectorImpl<ConsumerPair> &consumers);
150170

151171
void handleDiagnostic(SourceManager &SM, SourceLoc Loc,
152172
DiagnosticKind Kind,
@@ -157,7 +177,9 @@ class RangeSpecificDiagnosticConsumer : public DiagnosticConsumer {
157177
bool finishProcessing() override;
158178

159179
private:
160-
DiagnosticConsumer *consumerForLocation(SourceLoc loc) const;
180+
void computeConsumersOrderedByRange(SourceManager &SM);
181+
DiagnosticConsumer *consumerForLocation(SourceManager &SM,
182+
SourceLoc loc) const;
161183
};
162184

163185
} // end namespace swift

lib/AST/DiagnosticConsumer.cpp

Lines changed: 91 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
//
1515
//===----------------------------------------------------------------------===//
1616

17-
#define DEBUG_TYPE "swift-basic"
17+
#define DEBUG_TYPE "swift-ast"
1818
#include "swift/AST/DiagnosticConsumer.h"
1919
#include "swift/AST/DiagnosticEngine.h"
20+
#include "swift/Basic/Defer.h"
21+
#include "swift/Basic/SourceManager.h"
2022
#include "llvm/ADT/StringRef.h"
23+
#include "llvm/ADT/StringSet.h"
2124
#include "llvm/Support/Debug.h"
2225
#include "llvm/Support/raw_ostream.h"
2326
using namespace swift;
@@ -28,62 +31,119 @@ llvm::SMLoc DiagnosticConsumer::getRawLoc(SourceLoc loc) {
2831
return loc.Value;
2932
}
3033

31-
RangeSpecificDiagnosticConsumer::RangeSpecificDiagnosticConsumer(
32-
MutableArrayRef<ConsumerPair> consumers) {
33-
using MapEntry = std::pair<CharSourceRange, unsigned>;
34+
LLVM_ATTRIBUTE_UNUSED
35+
static bool hasDuplicateFileNames(
36+
ArrayRef<FileSpecificDiagnosticConsumer::ConsumerPair> consumers) {
37+
llvm::StringSet<> seenFiles;
38+
for (const auto &consumerPair : consumers) {
39+
if (consumerPair.first.empty()) {
40+
// We can handle multiple consumers that aren't associated with any file,
41+
// because they only collect diagnostics that aren't in any of the special
42+
// files. This isn't an important use case to support, but also SmallSet
43+
// doesn't handle empty strings anyway!
44+
continue;
45+
}
3446

35-
// Split up the ConsumerPairs into the "map" (to be sorted) and the actual
36-
// owning vector (preserving order).
37-
for (ConsumerPair &pair : consumers) {
38-
LocationToConsumerMap.emplace_back(pair.first, SubConsumers.size());
39-
SubConsumers.emplace_back(std::move(pair).second);
47+
bool isUnique = seenFiles.insert(consumerPair.first).second;
48+
if (!isUnique)
49+
return true;
50+
}
51+
return false;
52+
}
53+
54+
FileSpecificDiagnosticConsumer::FileSpecificDiagnosticConsumer(
55+
SmallVectorImpl<ConsumerPair> &consumers)
56+
: SubConsumers(std::move(consumers)) {
57+
assert(!SubConsumers.empty() &&
58+
"don't waste time handling diagnostics that will never get emitted");
59+
assert(!hasDuplicateFileNames(SubConsumers) &&
60+
"having multiple consumers for the same file is not implemented");
61+
}
62+
63+
void FileSpecificDiagnosticConsumer::computeConsumersOrderedByRange(
64+
SourceManager &SM) {
65+
// Look up each file's source range and add it to the "map" (to be sorted).
66+
for (const ConsumerPair &pair : SubConsumers) {
67+
if (pair.first.empty())
68+
continue;
69+
70+
Optional<unsigned> bufferID = SM.getIDForBufferIdentifier(pair.first);
71+
assert(bufferID.hasValue() && "consumer registered for unknown file");
72+
CharSourceRange range = SM.getRangeForBuffer(bufferID.getValue());
73+
ConsumersOrderedByRange.emplace_back(range, pair.second.get());
4074
}
4175

4276
// Sort the "map" by buffer /end/ location, for use with std::lower_bound
4377
// later. (Sorting by start location would produce the same sort, since the
4478
// ranges must not be overlapping, but since we need to check end locations
4579
// later it's consistent to sort by that here.)
46-
std::sort(LocationToConsumerMap.begin(), LocationToConsumerMap.end(),
47-
[](const MapEntry &left, const MapEntry &right) -> bool {
80+
std::sort(ConsumersOrderedByRange.begin(), ConsumersOrderedByRange.end(),
81+
[](const ConsumersOrderedByRangeEntry &left,
82+
const ConsumersOrderedByRangeEntry &right) -> bool {
4883
auto compare = std::less<const char *>();
4984
return compare(getRawLoc(left.first.getEnd()).getPointer(),
5085
getRawLoc(right.first.getEnd()).getPointer());
5186
});
5287

53-
// Check that the ranges are non-overlapping.
54-
assert(LocationToConsumerMap.end() ==
55-
std::adjacent_find(LocationToConsumerMap.begin(),
56-
LocationToConsumerMap.end(),
57-
[](const MapEntry &left, const MapEntry &right) {
88+
// Check that the ranges are non-overlapping. If the files really are all
89+
// distinct, this should be trivially true, but if it's ever not we might end
90+
// up mis-filing diagnostics.
91+
assert(ConsumersOrderedByRange.end() ==
92+
std::adjacent_find(ConsumersOrderedByRange.begin(),
93+
ConsumersOrderedByRange.end(),
94+
[](const ConsumersOrderedByRangeEntry &left,
95+
const ConsumersOrderedByRangeEntry &right) {
5896
return left.first.overlaps(right.first);
5997
}) &&
60-
"overlapping ranges given to RangeSpecificDiagnosticConsumer");
98+
"overlapping ranges despite having distinct files");
6199
}
62100

63101
DiagnosticConsumer *
64-
RangeSpecificDiagnosticConsumer::consumerForLocation(SourceLoc loc) const {
65-
// "Find the first range whose end location is greater than or equal to
66-
// 'loc'."
102+
FileSpecificDiagnosticConsumer::consumerForLocation(SourceManager &SM,
103+
SourceLoc loc) const {
104+
// If there's only one consumer, we'll use it no matter what, because...
105+
// - ...all diagnostics within the file will go to that consumer.
106+
// - ...all diagnostics not within the file will not be claimed by any
107+
// consumer, and so will go to all (one) consumers.
108+
if (SubConsumers.size() == 1)
109+
return SubConsumers.front().second.get();
110+
111+
// Diagnostics with invalid locations always go to every consumer.
112+
if (loc.isInvalid())
113+
return nullptr;
114+
115+
// This map is generated on first use and cached, to allow the
116+
// FileSpecificDiagnosticConsumer to be set up before the source files are
117+
// actually loaded.
118+
if (ConsumersOrderedByRange.empty()) {
119+
auto *mutableThis = const_cast<FileSpecificDiagnosticConsumer*>(this);
120+
mutableThis->computeConsumersOrderedByRange(SM);
121+
}
122+
123+
// This std::lower_bound call is doing a binary search for the first range
124+
// that /might/ contain 'loc'. Specifically, since the ranges are sorted
125+
// by end location, it's looking for the first range where the end location
126+
// is greater than or equal to 'loc'.
67127
auto possiblyContainingRangeIter =
68-
std::lower_bound(LocationToConsumerMap.begin(),
69-
LocationToConsumerMap.end(),
128+
std::lower_bound(ConsumersOrderedByRange.begin(),
129+
ConsumersOrderedByRange.end(),
70130
loc,
71-
[](const std::pair<CharSourceRange, unsigned> &entry,
131+
[](const ConsumersOrderedByRangeEntry &entry,
72132
SourceLoc loc) -> bool {
73133
auto compare = std::less<const char *>();
74134
return compare(getRawLoc(entry.first.getEnd()).getPointer(),
75135
getRawLoc(loc).getPointer());
76136
});
77137

78-
if (possiblyContainingRangeIter != LocationToConsumerMap.end() &&
138+
if (possiblyContainingRangeIter != ConsumersOrderedByRange.end() &&
79139
possiblyContainingRangeIter->first.contains(loc)) {
80-
return SubConsumers[possiblyContainingRangeIter->second].get();
140+
return possiblyContainingRangeIter->second;
81141
}
82142

83143
return nullptr;
84144
}
85145

86-
void RangeSpecificDiagnosticConsumer::handleDiagnostic(
146+
void FileSpecificDiagnosticConsumer::handleDiagnostic(
87147
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,
88148
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,
89149
const DiagnosticInfo &Info) {
@@ -93,7 +153,7 @@ void RangeSpecificDiagnosticConsumer::handleDiagnostic(
93153
case DiagnosticKind::Error:
94154
case DiagnosticKind::Warning:
95155
case DiagnosticKind::Remark:
96-
specificConsumer = consumerForLocation(Loc);
156+
specificConsumer = consumerForLocation(SM, Loc);
97157
ConsumerForSubsequentNotes = specificConsumer;
98158
break;
99159
case DiagnosticKind::Note:
@@ -106,18 +166,18 @@ void RangeSpecificDiagnosticConsumer::handleDiagnostic(
106166
Info);
107167
} else {
108168
for (auto &subConsumer : SubConsumers) {
109-
subConsumer->handleDiagnostic(SM, Loc, Kind, FormatString, FormatArgs,
110-
Info);
169+
subConsumer.second->handleDiagnostic(SM, Loc, Kind, FormatString,
170+
FormatArgs, Info);
111171
}
112172
}
113173
}
114174

115-
bool RangeSpecificDiagnosticConsumer::finishProcessing() {
175+
bool FileSpecificDiagnosticConsumer::finishProcessing() {
116176
// Deliberately don't use std::any_of here because we don't want early-exit
117177
// behavior.
118178
bool hadError = false;
119179
for (auto &subConsumer : SubConsumers)
120-
hadError |= subConsumer->finishProcessing();
180+
hadError |= subConsumer.second->finishProcessing();
121181
return hadError;
122182
}
123183

0 commit comments

Comments
 (0)