Skip to content

Commit 9d08b8c

Browse files
author
David Ungar
authored
Merge pull request #16526 from davidungar/compilation-failed
[Batch Mode] Emit a non-specific error for all primaries lacking specific errors when a batch has errors.
2 parents c9b42dc + 167c8dc commit 9d08b8c

11 files changed

+173
-107
lines changed

include/swift/AST/DiagnosticConsumer.h

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class DiagnosticConsumer {
100100
const DiagnosticInfo &Info) = 0;
101101

102102
/// \returns true if an error occurred while finishing-up.
103-
virtual bool finishProcessing() { return false; }
103+
virtual bool finishProcessing(SourceManager &) { return false; }
104104
};
105105

106106
/// \brief DiagnosticConsumer that discards all diagnostics.
@@ -140,11 +140,22 @@ class FileSpecificDiagnosticConsumer : public DiagnosticConsumer {
140140
/// All consumers owned by this FileSpecificDiagnosticConsumer.
141141
const SmallVector<ConsumerPair, 4> SubConsumers;
142142

143-
/// The DiagnosticConsumer may be empty if those diagnostics are not to be
144-
/// emitted.
145-
using ConsumersOrderedByRangeEntry =
146-
std::pair<CharSourceRange, DiagnosticConsumer *>;
143+
public:
144+
// The commented-out consts are there because the data does not change
145+
// but the swap method gets called on this structure.
146+
struct ConsumerSpecificInformation {
147+
/*const*/ CharSourceRange range;
148+
/// The DiagnosticConsumer may be empty if those diagnostics are not to be
149+
/// emitted.
150+
DiagnosticConsumer * /*const*/ consumer;
151+
bool hasAnErrorBeenEmitted = false;
152+
153+
ConsumerSpecificInformation(const CharSourceRange range,
154+
DiagnosticConsumer *const consumer)
155+
: range(range), consumer(consumer) {}
156+
};
147157

158+
private:
148159
/// The consumers owned by this FileSpecificDiagnosticConsumer, sorted by
149160
/// the end locations of each file so that a lookup by position can be done
150161
/// using binary search.
@@ -153,8 +164,8 @@ class FileSpecificDiagnosticConsumer : public DiagnosticConsumer {
153164
/// This allows diagnostics to be emitted before files are actually opened,
154165
/// as long as they don't have source locations.
155166
///
156-
/// \see #consumerForLocation
157-
SmallVector<ConsumersOrderedByRangeEntry, 4> ConsumersOrderedByRange;
167+
/// \see #consumerSpecificInformationForLocation
168+
SmallVector<ConsumerSpecificInformation, 4> ConsumersOrderedByRange;
158169

159170
/// Indicates which consumer to send Note diagnostics too.
160171
///
@@ -163,7 +174,10 @@ class FileSpecificDiagnosticConsumer : public DiagnosticConsumer {
163174
///
164175
/// If None, Note diagnostics are sent to every consumer.
165176
/// If null, diagnostics are suppressed.
166-
Optional<DiagnosticConsumer *> ConsumerForSubsequentNotes = None;
177+
Optional<ConsumerSpecificInformation *>
178+
ConsumerSpecificInfoForSubsequentNotes = None;
179+
180+
bool HasAnErrorBeenConsumed = false;
167181

168182
public:
169183
/// Takes ownership of the DiagnosticConsumers specified in \p consumers.
@@ -179,16 +193,22 @@ class FileSpecificDiagnosticConsumer : public DiagnosticConsumer {
179193
ArrayRef<DiagnosticArgument> FormatArgs,
180194
const DiagnosticInfo &Info) override;
181195

182-
bool finishProcessing() override;
196+
bool finishProcessing(SourceManager &) override;
183197

184198
private:
199+
/// In batch mode, any error causes failure for all primary files, but
200+
/// Xcode will only see an error for a particular primary in that primary's
201+
/// serialized diagnostics file. So, emit errors for all other primaries here.
202+
void addNonSpecificErrors(SourceManager &SM);
203+
185204
void computeConsumersOrderedByRange(SourceManager &SM);
186205

187206
/// Returns nullptr if diagnostic is to be suppressed,
188207
/// None if diagnostic is to be distributed to every consumer,
189208
/// a particular consumer if diagnostic goes there.
190-
Optional<DiagnosticConsumer *> consumerForLocation(SourceManager &SM,
191-
SourceLoc loc) const;
209+
Optional<ConsumerSpecificInformation *>
210+
consumerSpecificInformationForLocation(SourceManager &SM,
211+
SourceLoc loc) const;
192212
};
193213

194214
} // end namespace swift

include/swift/AST/DiagnosticEngine.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -759,8 +759,8 @@ namespace swift {
759759

760760
/// \returns true if any diagnostic consumer gave an error while invoking
761761
//// \c finishProcessing.
762-
bool finishProcessing();
763-
762+
bool finishProcessing(SourceManager &);
763+
764764
/// \brief Format the given diagnostic text and place the result in the given
765765
/// buffer.
766766
static void formatDiagnosticText(
@@ -781,6 +781,9 @@ namespace swift {
781781
/// \brief Send all tentative diagnostics to all diagnostic consumers and
782782
/// delete them.
783783
void emitTentativeDiagnostics();
784+
785+
public:
786+
static const char *diagnosticStringFor(const DiagID id);
784787
};
785788

786789
/// \brief Represents a diagnostic transaction. While a transaction is

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@ WARNING(cannot_assign_value_to_conditional_compilation_flag,none,
235235
ERROR(error_optimization_remark_pattern, none, "%0 in '%1'",
236236
(StringRef, StringRef))
237237

238+
ERROR(error_compilation_stopped_by_errors_in_other_files,none, "compilation stopped by errors in other files", ())
239+
238240
#ifndef DIAG_NO_UNDEF
239241
# if defined(DIAG)
240242
# undef DIAG

lib/AST/DiagnosticConsumer.cpp

Lines changed: 76 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#define DEBUG_TYPE "swift-ast"
1818
#include "swift/AST/DiagnosticConsumer.h"
1919
#include "swift/AST/DiagnosticEngine.h"
20+
#include "swift/AST/DiagnosticsFrontend.h"
2021
#include "swift/Basic/Defer.h"
2122
#include "swift/Basic/SourceManager.h"
2223
#include "llvm/ADT/STLExtras.h"
@@ -71,44 +72,38 @@ void FileSpecificDiagnosticConsumer::computeConsumersOrderedByRange(
7172
Optional<unsigned> bufferID = SM.getIDForBufferIdentifier(pair.first);
7273
assert(bufferID.hasValue() && "consumer registered for unknown file");
7374
CharSourceRange range = SM.getRangeForBuffer(bufferID.getValue());
74-
ConsumersOrderedByRange.emplace_back(range, pair.second.get());
75+
ConsumersOrderedByRange.emplace_back(
76+
ConsumerSpecificInformation(range, pair.second.get()));
7577
}
7678

7779
// Sort the "map" by buffer /end/ location, for use with std::lower_bound
7880
// later. (Sorting by start location would produce the same sort, since the
7981
// ranges must not be overlapping, but since we need to check end locations
8082
// later it's consistent to sort by that here.)
8183
std::sort(ConsumersOrderedByRange.begin(), ConsumersOrderedByRange.end(),
82-
[](const ConsumersOrderedByRangeEntry &left,
83-
const ConsumersOrderedByRangeEntry &right) -> bool {
84-
auto compare = std::less<const char *>();
85-
return compare(getRawLoc(left.first.getEnd()).getPointer(),
86-
getRawLoc(right.first.getEnd()).getPointer());
87-
});
84+
[](const ConsumerSpecificInformation &left,
85+
const ConsumerSpecificInformation &right) -> bool {
86+
auto compare = std::less<const char *>();
87+
return compare(getRawLoc(left.range.getEnd()).getPointer(),
88+
getRawLoc(right.range.getEnd()).getPointer());
89+
});
8890

8991
// Check that the ranges are non-overlapping. If the files really are all
9092
// distinct, this should be trivially true, but if it's ever not we might end
9193
// up mis-filing diagnostics.
9294
assert(ConsumersOrderedByRange.end() ==
93-
std::adjacent_find(ConsumersOrderedByRange.begin(),
94-
ConsumersOrderedByRange.end(),
95-
[](const ConsumersOrderedByRangeEntry &left,
96-
const ConsumersOrderedByRangeEntry &right) {
97-
return left.first.overlaps(right.first);
98-
}) &&
95+
std::adjacent_find(ConsumersOrderedByRange.begin(),
96+
ConsumersOrderedByRange.end(),
97+
[](const ConsumerSpecificInformation &left,
98+
const ConsumerSpecificInformation &right) {
99+
return left.range.overlaps(right.range);
100+
}) &&
99101
"overlapping ranges despite having distinct files");
100102
}
101103

102-
Optional<DiagnosticConsumer *>
103-
FileSpecificDiagnosticConsumer::consumerForLocation(SourceManager &SM,
104-
SourceLoc loc) const {
105-
// If there's only one consumer, we'll use it no matter what, because...
106-
// - ...all diagnostics within the file will go to that consumer.
107-
// - ...all diagnostics not within the file will not be claimed by any
108-
// consumer, and so will go to all (one) consumers.
109-
if (SubConsumers.size() == 1)
110-
return SubConsumers.front().second.get();
111-
104+
Optional<FileSpecificDiagnosticConsumer::ConsumerSpecificInformation *>
105+
FileSpecificDiagnosticConsumer::consumerSpecificInformationForLocation(
106+
SourceManager &SM, SourceLoc loc) const {
112107
// Diagnostics with invalid locations always go to every consumer.
113108
if (loc.isInvalid())
114109
return None;
@@ -139,20 +134,19 @@ FileSpecificDiagnosticConsumer::consumerForLocation(SourceManager &SM,
139134
// that /might/ contain 'loc'. Specifically, since the ranges are sorted
140135
// by end location, it's looking for the first range where the end location
141136
// is greater than or equal to 'loc'.
142-
auto possiblyContainingRangeIter =
143-
std::lower_bound(ConsumersOrderedByRange.begin(),
144-
ConsumersOrderedByRange.end(),
145-
loc,
146-
[](const ConsumersOrderedByRangeEntry &entry,
147-
SourceLoc loc) -> bool {
148-
auto compare = std::less<const char *>();
149-
return compare(getRawLoc(entry.first.getEnd()).getPointer(),
150-
getRawLoc(loc).getPointer());
151-
});
137+
const ConsumerSpecificInformation *possiblyContainingRangeIter =
138+
std::lower_bound(
139+
ConsumersOrderedByRange.begin(), ConsumersOrderedByRange.end(), loc,
140+
[](const ConsumerSpecificInformation &entry, SourceLoc loc) -> bool {
141+
auto compare = std::less<const char *>();
142+
return compare(getRawLoc(entry.range.getEnd()).getPointer(),
143+
getRawLoc(loc).getPointer());
144+
});
152145

153146
if (possiblyContainingRangeIter != ConsumersOrderedByRange.end() &&
154-
possiblyContainingRangeIter->first.contains(loc)) {
155-
return possiblyContainingRangeIter->second;
147+
possiblyContainingRangeIter->range.contains(loc)) {
148+
return const_cast<ConsumerSpecificInformation *>(
149+
possiblyContainingRangeIter);
156150
}
157151

158152
return None;
@@ -163,41 +157,76 @@ void FileSpecificDiagnosticConsumer::handleDiagnostic(
163157
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,
164158
const DiagnosticInfo &Info) {
165159

166-
Optional<DiagnosticConsumer *> specificConsumer;
160+
HasAnErrorBeenConsumed |= Kind == DiagnosticKind::Error;
161+
162+
Optional<ConsumerSpecificInformation *> consumerSpecificInfo;
167163
switch (Kind) {
168164
case DiagnosticKind::Error:
169165
case DiagnosticKind::Warning:
170166
case DiagnosticKind::Remark:
171-
specificConsumer = consumerForLocation(SM, Loc);
172-
ConsumerForSubsequentNotes = specificConsumer;
167+
consumerSpecificInfo = consumerSpecificInformationForLocation(SM, Loc);
168+
ConsumerSpecificInfoForSubsequentNotes = consumerSpecificInfo;
173169
break;
174170
case DiagnosticKind::Note:
175-
specificConsumer = ConsumerForSubsequentNotes;
171+
consumerSpecificInfo = ConsumerSpecificInfoForSubsequentNotes;
176172
break;
177173
}
178-
179-
if (!specificConsumer.hasValue()) {
174+
if (!consumerSpecificInfo.hasValue()) {
180175
for (auto &subConsumer : SubConsumers) {
181176
if (subConsumer.second) {
182177
subConsumer.second->handleDiagnostic(SM, Loc, Kind, FormatString,
183178
FormatArgs, Info);
184179
}
185180
}
186-
} else if (DiagnosticConsumer *c = specificConsumer.getValue())
187-
c->handleDiagnostic(SM, Loc, Kind, FormatString, FormatArgs, Info);
188-
else
189-
; // Suppress non-primary diagnostic in batch mode.
181+
return;
182+
}
183+
if (!consumerSpecificInfo.getValue()->consumer)
184+
return; // Suppress non-primary diagnostic in batch mode.
185+
186+
consumerSpecificInfo.getValue()->consumer->handleDiagnostic(
187+
SM, Loc, Kind, FormatString, FormatArgs, Info);
188+
consumerSpecificInfo.getValue()->hasAnErrorBeenEmitted |=
189+
Kind == DiagnosticKind::Error;
190190
}
191191

192-
bool FileSpecificDiagnosticConsumer::finishProcessing() {
192+
bool FileSpecificDiagnosticConsumer::finishProcessing(SourceManager &SM) {
193+
addNonSpecificErrors(SM);
194+
193195
// Deliberately don't use std::any_of here because we don't want early-exit
194196
// behavior.
197+
195198
bool hadError = false;
196199
for (auto &subConsumer : SubConsumers)
197-
hadError |= subConsumer.second && subConsumer.second->finishProcessing();
200+
hadError |= subConsumer.second && subConsumer.second->finishProcessing(SM);
198201
return hadError;
199202
}
200203

204+
static void produceNonSpecificError(
205+
FileSpecificDiagnosticConsumer::ConsumerSpecificInformation &info,
206+
SourceManager &SM) {
207+
Diagnostic diagnostic(
208+
diag::error_compilation_stopped_by_errors_in_other_files);
209+
210+
// Stolen from DiagnosticEngine::emitDiagnostic
211+
DiagnosticInfo Info;
212+
Info.ID = diagnostic.getID();
213+
214+
info.consumer->handleDiagnostic(
215+
SM, info.range.getStart(), DiagnosticKind::Error,
216+
DiagnosticEngine::diagnosticStringFor(diagnostic.getID()), {}, Info);
217+
}
218+
219+
void FileSpecificDiagnosticConsumer::addNonSpecificErrors(SourceManager &SM) {
220+
if (!HasAnErrorBeenConsumed)
221+
return;
222+
for (auto &info : ConsumersOrderedByRange) {
223+
if (!info.hasAnErrorBeenEmitted && info.consumer) {
224+
produceNonSpecificError(info, SM);
225+
info.hasAnErrorBeenEmitted = true;
226+
}
227+
}
228+
}
229+
201230
void NullDiagnosticConsumer::handleDiagnostic(
202231
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,
203232
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,

lib/AST/DiagnosticEngine.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,10 @@ bool DiagnosticEngine::isDiagnosticPointsToFirstBadToken(DiagID ID) const {
251251
return storedDiagnosticInfos[(unsigned) ID].pointsToFirstBadToken;
252252
}
253253

254-
bool DiagnosticEngine::finishProcessing() {
254+
bool DiagnosticEngine::finishProcessing(SourceManager &SM) {
255255
bool hadError = false;
256256
for (auto &Consumer : Consumers) {
257-
hadError |= Consumer->finishProcessing();
257+
hadError |= Consumer->finishProcessing(SM);
258258
}
259259
return hadError;
260260
}
@@ -822,9 +822,11 @@ void DiagnosticEngine::emitDiagnostic(const Diagnostic &diagnostic) {
822822
Info.FixIts = diagnostic.getFixIts();
823823
for (auto &Consumer : Consumers) {
824824
Consumer->handleDiagnostic(SourceMgr, loc, toDiagnosticKind(behavior),
825-
diagnosticStrings[(unsigned)Info.ID],
826-
diagnostic.getArgs(),
827-
Info);
825+
diagnosticStringFor(Info.ID),
826+
diagnostic.getArgs(), Info);
828827
}
829828
}
830829

830+
const char *DiagnosticEngine::diagnosticStringFor(const DiagID id) {
831+
return diagnosticStrings[(unsigned)id];
832+
}

lib/Frontend/SerializedDiagnosticConsumer.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class SerializedDiagnosticConsumer : public DiagnosticConsumer {
140140
assert(CalledFinishProcessing && "did not call finishProcessing()");
141141
}
142142

143-
bool finishProcessing() override {
143+
bool finishProcessing(SourceManager &SM) override {
144144
assert(!CalledFinishProcessing &&
145145
"called finishProcessing() multiple times");
146146
CalledFinishProcessing = true;
@@ -160,8 +160,7 @@ class SerializedDiagnosticConsumer : public DiagnosticConsumer {
160160
llvm::sys::fs::F_None));
161161
if (EC) {
162162
// Create a temporary diagnostics engine to print the error to stderr.
163-
SourceManager dummyMgr;
164-
DiagnosticEngine DE(dummyMgr);
163+
DiagnosticEngine DE(SM);
165164
PrintingDiagnosticConsumer PDC;
166165
DE.addConsumer(PDC);
167166
DE.diagnose(SourceLoc(), diag::cannot_open_serialized_file,

lib/FrontendTool/FrontendTool.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ class JSONFixitWriter
404404
}
405405
}
406406

407-
bool finishProcessing() override {
407+
bool finishProcessing(SourceManager &) override {
408408
std::error_code EC;
409409
std::unique_ptr<llvm::raw_fd_ostream> OS;
410410
OS.reset(new llvm::raw_fd_ostream(FixitsOutputPath,
@@ -1651,7 +1651,7 @@ int swift::performFrontend(ArrayRef<const char *> Args,
16511651

16521652
auto finishDiagProcessing = [&](int retValue) -> int {
16531653
FinishDiagProcessingCheckRAII.CalledFinishDiagProcessing = true;
1654-
bool err = Instance->getDiags().finishProcessing();
1654+
bool err = Instance->getDiags().finishProcessing(Instance->getSourceMgr());
16551655
return retValue ? retValue : err;
16561656
};
16571657

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Ensure that an error in a non-primary causes an error in the errorless primary.
2+
//
3+
// RUN: rm -f %t.*
4+
5+
// RUN: not %target-swift-frontend -typecheck -primary-file %s -serialize-diagnostics-path %t.main.dia -primary-file %S/../Inputs/empty.swift -serialize-diagnostics-path %t.empty.dia %S/Inputs/serialized-diagnostics-batch-mode-suppression-helper.swift 2> %t.stderr.txt
6+
// RUN: c-index-test -read-diagnostics %t.main.dia 2> %t.main.txt
7+
// RUN: c-index-test -read-diagnostics %t.empty.dia 2> %t.empty.txt
8+
9+
// RUN: %FileCheck -check-prefix=NO-GENERAL-ERROR-OCCURRED %s <%t.main.txt
10+
// RUN: %FileCheck -check-prefix=GENERAL-ERROR-OCCURRED %s <%t.empty.txt
11+
// GENERAL-ERROR-OCCURRED: compilation stopped by errors in other files
12+
// NO-GENERAL-ERROR-OCCURRED-NOT: compilation stopped by errors in other files
13+
14+
15+
func test(x: SomeType) {
16+
nonexistant()
17+
}

0 commit comments

Comments
 (0)