Skip to content

Commit 9f09361

Browse files
authored
Merge pull request #16529 from jrose-apple/4.2-batch-mode-diagnostics
[4.2] Fixes for batch mode diagnostics in Xcode rdar://problem/40032762
2 parents bf2dca3 + 79b7535 commit 9f09361

35 files changed

+536
-231
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/AST/DiagnosticsSema.def

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,7 +1498,7 @@ ERROR(witness_argument_name_mismatch,none,
14981498
"%select{method|initializer}0 %1 has different argument labels from those "
14991499
"required by protocol %2 (%3)", (bool, DeclName, Type, DeclName))
15001500
ERROR(witness_initializer_not_required,none,
1501-
"initializer requirement %0 can only be satisfied by a `required` "
1501+
"initializer requirement %0 can only be satisfied by a 'required' "
15021502
"initializer in%select{| the definition of}1 non-final class %2",
15031503
(DeclName, bool, Type))
15041504
ERROR(witness_initializer_failability,none,
@@ -1520,12 +1520,12 @@ NOTE(witness_self_weaken_same_type,none,
15201520
"consider weakening the same-type requirement %0 == %1 to a superclass "
15211521
"requirement", (Type, Type))
15221522
ERROR(witness_requires_dynamic_self,none,
1523-
"method %0 in non-final class %1 must return `Self` to conform to "
1523+
"method %0 in non-final class %1 must return 'Self' to conform to "
15241524
"protocol %2",
15251525
(DeclName, Type, Type))
15261526
ERROR(witness_requires_class_implementation,none,
15271527
"method %0 in non-final class %1 cannot be implemented in a "
1528-
"protocol extension because it returns `Self` and has associated type "
1528+
"protocol extension because it returns 'Self' and has associated type "
15291529
"requirements",
15301530
(DeclName, Type))
15311531
ERROR(witness_not_accessible_proto,none,
@@ -1553,6 +1553,9 @@ ERROR(type_witness_objc_generic_parameter,none,
15531553
"type %0 involving Objective-C type parameter%select{| %1}2 cannot be "
15541554
"used for associated type %3 of protocol %4",
15551555
(Type, Type, bool, DeclName, DeclName))
1556+
NOTE(witness_fix_access,none,
1557+
"mark the %0 as '%select{%error|fileprivate|internal|public|%error}1' to "
1558+
"satisfy the requirement", (DescriptiveDeclKind, AccessLevel))
15561559

15571560
ERROR(protocol_refine_access,none,
15581561
"%select{protocol must be declared %select{"
@@ -3380,10 +3383,10 @@ WARNING(objc_inference_swift3_objc_derived,none,
33803383
"deprecated", ())
33813384

33823385
NOTE(objc_inference_swift3_addobjc,none,
3383-
"add `@objc` to continue exposing an Objective-C entry point (Swift 3 "
3386+
"add '@objc' to continue exposing an Objective-C entry point (Swift 3 "
33843387
"behavior)", ())
33853388
NOTE(objc_inference_swift3_addnonobjc,none,
3386-
"add `@nonobjc` to suppress the Objective-C entry point (Swift 4 "
3389+
"add '@nonobjc' to suppress the Objective-C entry point (Swift 4 "
33873390
"behavior)", ())
33883391

33893392
ERROR(objc_for_generic_class,none,
@@ -3778,9 +3781,6 @@ ERROR(availability_protocol_requires_version,
37783781
NOTE(availability_protocol_requirement_here, none,
37793782
"protocol requirement here", ())
37803783

3781-
NOTE(availability_conformance_introduced_here, none,
3782-
"conformance introduced here", ())
3783-
37843784
// This doesn't display as an availability diagnostic, but it's
37853785
// implemented there and fires when these subscripts are marked
37863786
// unavailable, so it seems appropriate to put it here.

include/swift/Frontend/FrontendInputsAndOutputs.h

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

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

108112
// Primary count readers:

lib/AST/ASTContext.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2268,6 +2268,9 @@ bool swift::fixDeclarationName(InFlightDiagnostic &diag, ValueDecl *decl,
22682268
bool swift::fixDeclarationObjCName(InFlightDiagnostic &diag, ValueDecl *decl,
22692269
Optional<ObjCSelector> targetNameOpt,
22702270
bool ignoreImpliedName) {
2271+
if (decl->isImplicit())
2272+
return false;
2273+
22712274
// Subscripts cannot be renamed, so handle them directly.
22722275
if (isa<SubscriptDecl>(decl)) {
22732276
diag.fixItInsert(decl->getAttributeInsertionLoc(/*forModifier=*/false),

lib/AST/Decl.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,6 +2095,9 @@ bool ValueDecl::canInferObjCFromRequirement(ValueDecl *requirement) {
20952095
}
20962096

20972097
SourceLoc ValueDecl::getAttributeInsertionLoc(bool forModifier) const {
2098+
if (isImplicit())
2099+
return SourceLoc();
2100+
20982101
if (auto var = dyn_cast<VarDecl>(this)) {
20992102
// [attrs] var ...
21002103
// The attributes are part of the VarDecl, but the 'var' is part of the PBD.

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/Driver/Compilation.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,11 @@ namespace driver {
528528
ReturnCode);
529529
}
530530

531+
// See how ContinueBuildingAfterErrors gets set up in Driver.cpp for
532+
// more info.
533+
assert((Comp.ContinueBuildingAfterErrors || !Comp.EnableBatchMode) &&
534+
"batch mode diagnostics require ContinueBuildingAfterErrors");
535+
531536
return Comp.ContinueBuildingAfterErrors ?
532537
TaskFinishedResponse::ContinueExecution :
533538
TaskFinishedResponse::StopExecution;

lib/Driver/Driver.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -667,8 +667,6 @@ Driver::buildCompilation(const ToolChain &TC,
667667
}
668668

669669
bool SaveTemps = ArgList->hasArg(options::OPT_save_temps);
670-
bool ContinueBuildingAfterErrors =
671-
ArgList->hasArg(options::OPT_continue_building_after_errors);
672670
bool ShowDriverTimeCompilation =
673671
ArgList->hasArg(options::OPT_driver_time_compilation);
674672

@@ -703,6 +701,21 @@ Driver::buildCompilation(const ToolChain &TC,
703701
OI.CompilerMode = computeCompilerMode(*TranslatedArgList, Inputs, BatchMode);
704702
buildOutputInfo(TC, *TranslatedArgList, BatchMode, Inputs, OI);
705703

704+
// Note: Batch mode handling of serialized diagnostics requires that all
705+
// batches get to run, in order to make sure that all diagnostics emitted
706+
// during the compilation end up in at least one serialized diagnostic file.
707+
// Therefore, treat batch mode as implying -continue-building-after-errors.
708+
// (This behavior could be limited to only when serialized diagnostics are
709+
// being emitted, but this seems more consistent and less surprising for
710+
// users.)
711+
// FIXME: We don't really need (or want) a full ContinueBuildingAfterErrors.
712+
// If we fail to precompile a bridging header, for example, there's no need
713+
// to go on to compilation of source files, and if compilation of source files
714+
// fails, we shouldn't try to link. Instead, we'd want to let all jobs finish
715+
// but not schedule any new ones.
716+
const bool ContinueBuildingAfterErrors =
717+
BatchMode || ArgList->hasArg(options::OPT_continue_building_after_errors);
718+
706719
if (Diags.hadAnyError())
707720
return nullptr;
708721

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;

0 commit comments

Comments
 (0)