Skip to content

Commit 46b8ad3

Browse files
author
David Ungar
authored
Merge pull request #16485 from davidungar/oddball-diags
[Batch Mode] Suppressing diagnostics for non-primaries - rdar://40032762
2 parents 6a48b2d + aa2f2eb commit 46b8ad3

File tree

7 files changed

+87
-21
lines changed

7 files changed

+87
-21
lines changed

include/swift/AST/DiagnosticConsumer.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,17 @@ class FileSpecificDiagnosticConsumer : public DiagnosticConsumer {
131131
/// be associated with. An empty string means that a consumer is not
132132
/// associated with any particular buffer, and should only receive diagnostics
133133
/// that are not in any of the other consumers' files.
134+
/// A null pointer for the DiagnosticConsumer means that diagnostics for this
135+
/// file should not be emitted.
134136
using ConsumerPair =
135137
std::pair<std::string, std::unique_ptr<DiagnosticConsumer>>;
136138

137139
private:
138140
/// All consumers owned by this FileSpecificDiagnosticConsumer.
139141
const SmallVector<ConsumerPair, 4> SubConsumers;
140142

143+
/// The DiagnosticConsumer may be empty if those diagnostics are not to be
144+
/// emitted.
141145
using ConsumersOrderedByRangeEntry =
142146
std::pair<CharSourceRange, DiagnosticConsumer *>;
143147

@@ -157,8 +161,9 @@ class FileSpecificDiagnosticConsumer : public DiagnosticConsumer {
157161
/// Notes are always considered attached to the error, warning, or remark
158162
/// that was most recently emitted.
159163
///
160-
/// If null, Note diagnostics are sent to every consumer.
161-
DiagnosticConsumer *ConsumerForSubsequentNotes = nullptr;
164+
/// If None, Note diagnostics are sent to every consumer.
165+
/// If null, diagnostics are suppressed.
166+
Optional<DiagnosticConsumer *> ConsumerForSubsequentNotes = None;
162167

163168
public:
164169
/// Takes ownership of the DiagnosticConsumers specified in \p consumers.
@@ -178,8 +183,12 @@ class FileSpecificDiagnosticConsumer : public DiagnosticConsumer {
178183

179184
private:
180185
void computeConsumersOrderedByRange(SourceManager &SM);
181-
DiagnosticConsumer *consumerForLocation(SourceManager &SM,
182-
SourceLoc loc) const;
186+
187+
/// Returns nullptr if diagnostic is to be suppressed,
188+
/// None if diagnostic is to be distributed to every consumer,
189+
/// a particular consumer if diagnostic goes there.
190+
Optional<DiagnosticConsumer *> consumerForLocation(SourceManager &SM,
191+
SourceLoc loc) const;
183192
};
184193

185194
} // end namespace swift

include/swift/Frontend/FrontendInputsAndOutputs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ class FrontendInputsAndOutputs {
104104
bool
105105
forEachPrimaryInput(llvm::function_ref<bool(const InputFile &)> fn) const;
106106

107+
/// If \p fn returns true, exit early and return true.
108+
bool
109+
forEachNonPrimaryInput(llvm::function_ref<bool(const InputFile &)> fn) const;
110+
107111
unsigned primaryInputCount() const { return PrimaryInputsInOrder.size(); }
108112

109113
// Primary count readers:

lib/AST/DiagnosticConsumer.cpp

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ void FileSpecificDiagnosticConsumer::computeConsumersOrderedByRange(
9999
"overlapping ranges despite having distinct files");
100100
}
101101

102-
DiagnosticConsumer *
102+
Optional<DiagnosticConsumer *>
103103
FileSpecificDiagnosticConsumer::consumerForLocation(SourceManager &SM,
104104
SourceLoc loc) const {
105105
// If there's only one consumer, we'll use it no matter what, because...
@@ -111,7 +111,7 @@ FileSpecificDiagnosticConsumer::consumerForLocation(SourceManager &SM,
111111

112112
// Diagnostics with invalid locations always go to every consumer.
113113
if (loc.isInvalid())
114-
return nullptr;
114+
return None;
115115

116116
// This map is generated on first use and cached, to allow the
117117
// FileSpecificDiagnosticConsumer to be set up before the source files are
@@ -121,15 +121,15 @@ FileSpecificDiagnosticConsumer::consumerForLocation(SourceManager &SM,
121121
// It's possible to get here while a bridging header PCH is being
122122
// attached-to, if there's some sort of AST-reader warning or error, which
123123
// happens before CompilerInstance::setUpInputs(), at which point _no_
124-
// source buffers are loaded in yet. In that case we return nullptr, rather
124+
// source buffers are loaded in yet. In that case we return None, rather
125125
// than trying to build a nonsensical map (and actually crashing since we
126126
// can't find buffers for the inputs).
127127
assert(!SubConsumers.empty());
128128
if (!SM.getIDForBufferIdentifier(SubConsumers.begin()->first).hasValue()) {
129129
assert(llvm::none_of(SubConsumers, [&](const ConsumerPair &pair) {
130130
return SM.getIDForBufferIdentifier(pair.first).hasValue();
131131
}));
132-
return nullptr;
132+
return None;
133133
}
134134
auto *mutableThis = const_cast<FileSpecificDiagnosticConsumer*>(this);
135135
mutableThis->computeConsumersOrderedByRange(SM);
@@ -155,15 +155,15 @@ FileSpecificDiagnosticConsumer::consumerForLocation(SourceManager &SM,
155155
return possiblyContainingRangeIter->second;
156156
}
157157

158-
return nullptr;
158+
return None;
159159
}
160160

161161
void FileSpecificDiagnosticConsumer::handleDiagnostic(
162162
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,
163163
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,
164164
const DiagnosticInfo &Info) {
165165

166-
DiagnosticConsumer *specificConsumer;
166+
Optional<DiagnosticConsumer *> specificConsumer;
167167
switch (Kind) {
168168
case DiagnosticKind::Error:
169169
case DiagnosticKind::Warning:
@@ -176,23 +176,25 @@ void FileSpecificDiagnosticConsumer::handleDiagnostic(
176176
break;
177177
}
178178

179-
if (specificConsumer) {
180-
specificConsumer->handleDiagnostic(SM, Loc, Kind, FormatString, FormatArgs,
181-
Info);
182-
} else {
179+
if (!specificConsumer.hasValue()) {
183180
for (auto &subConsumer : SubConsumers) {
184-
subConsumer.second->handleDiagnostic(SM, Loc, Kind, FormatString,
185-
FormatArgs, Info);
181+
if (subConsumer.second) {
182+
subConsumer.second->handleDiagnostic(SM, Loc, Kind, FormatString,
183+
FormatArgs, Info);
184+
}
186185
}
187-
}
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.
188190
}
189191

190192
bool FileSpecificDiagnosticConsumer::finishProcessing() {
191193
// Deliberately don't use std::any_of here because we don't want early-exit
192194
// behavior.
193195
bool hadError = false;
194196
for (auto &subConsumer : SubConsumers)
195-
hadError |= subConsumer.second->finishProcessing();
197+
hadError |= subConsumer.second && subConsumer.second->finishProcessing();
196198
return hadError;
197199
}
198200

lib/Frontend/FrontendInputsAndOutputs.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ bool FrontendInputsAndOutputs::forEachPrimaryInput(
9797
return false;
9898
}
9999

100+
bool FrontendInputsAndOutputs::forEachNonPrimaryInput(
101+
llvm::function_ref<bool(const InputFile &)> fn) const {
102+
return forEachInput([&](const InputFile &f) -> bool {
103+
return f.isPrimary() ? false : fn(f);
104+
});
105+
}
106+
100107
void FrontendInputsAndOutputs::assertMustNotBeMoreThanOnePrimaryInput() const {
101108
assert(!hasMultiplePrimaryInputs() &&
102109
"have not implemented >1 primary input yet");

lib/FrontendTool/FrontendTool.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -948,9 +948,13 @@ static bool performCompile(CompilerInstance &Instance,
948948

949949
// We've just been told to perform a typecheck, so we can return now.
950950
if (Action == FrontendOptions::ActionType::Typecheck) {
951-
const bool hadPrintAsObjCError = printAsObjCIfNeeded(
952-
Invocation.getObjCHeaderOutputPathForAtMostOnePrimary(),
953-
Instance.getMainModule(), opts.ImplicitObjCHeaderPath, moduleIsPublic);
951+
const bool hadPrintAsObjCError =
952+
Invocation.getFrontendOptions()
953+
.InputsAndOutputs.hasObjCHeaderOutputPath() &&
954+
printAsObjCIfNeeded(
955+
Invocation.getObjCHeaderOutputPathForAtMostOnePrimary(),
956+
Instance.getMainModule(), opts.ImplicitObjCHeaderPath,
957+
moduleIsPublic);
954958

955959
const bool hadEmitIndexDataError = emitIndexData(Invocation, Instance);
956960

@@ -1527,6 +1531,19 @@ createDispatchingDiagnosticConsumerIfNeeded(
15271531
subConsumers.emplace_back(input.file(), std::move(subConsumer));
15281532
return false;
15291533
});
1534+
// For batch mode, the compiler must swallow diagnostics pertaining to
1535+
// non-primary files in order to avoid Xcode showing the same diagnostic
1536+
// multiple times. So, create a diagnostic "eater" for those non-primary
1537+
// files.
1538+
// To avoid introducing bugs into WMO or single-file modes, test for multiple
1539+
// primaries.
1540+
if (inputsAndOutputs.hasMultiplePrimaryInputs()) {
1541+
inputsAndOutputs.forEachNonPrimaryInput(
1542+
[&](const InputFile &input) -> bool {
1543+
subConsumers.emplace_back(input.file(), nullptr);
1544+
return false;
1545+
});
1546+
}
15301547

15311548
if (subConsumers.empty())
15321549
return nullptr;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
typealias SomeType = NonExistentType
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// To avoid redundant diagnostics showing up in Xcode, batch-mode must suppress diagnostics in
2+
// non-primary files.
3+
//
4+
// RUN: rm -f %t.*
5+
6+
// 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
7+
// RUN: c-index-test -read-diagnostics %t.main.dia 2> %t.main.txt
8+
// RUN: c-index-test -read-diagnostics %t.empty.dia 2> %t.empty.txt
9+
10+
// Ensure there was an error:
11+
12+
// RUN: %FileCheck -check-prefix=ERROR %s <%t.stderr.txt
13+
// ERROR: error:
14+
15+
// Ensure the error is not in the serialized diagnostics:
16+
17+
// RUN: %FileCheck -check-prefix=NO-DIAGNOSTICS %s <%t.main.txt
18+
// RUN: %FileCheck -check-prefix=NO-DIAGNOSTICS %s <%t.empty.txt
19+
// NO-DIAGNOSTICS: Number of diagnostics: 0
20+
21+
// RUN: %FileCheck -check-prefix=NO-ERROR %s <%t.main.txt
22+
// RUN: %FileCheck -check-prefix=NO-ERROR %s <%t.empty.txt
23+
// NO-ERROR-NOT: error:
24+
25+
func test(x: SomeType) {
26+
}

0 commit comments

Comments
 (0)