Skip to content

Commit 14f51d7

Browse files
zygoloidbricknerbjonmeow
authored
Emit diagnostics produced by Clang after the ASTUnit is constructed. (#5876)
Previously we would drop these diagnostics; now periodically flush them to Carbon's diagnostics emitter. We flush them at the end of checking, and also immediately before changing the set of diagnostic annotation scopes, so that Clang diagnostics get properly annotated. This exposes some double diagnostics being produced in situations where Clang's name lookup logic would produce a diagnostic and we also produced one. For access control issues, use the Carbon diagnostic, in order to properly handle protected access. For ambiguity issues, use the Clang diagnostic that produces helpful notes. Also fix rendering of note diagnostics produced by Clang, by attaching them to the prior error / warning diagnostic. --------- Co-authored-by: Boaz Brickner <[email protected]> Co-authored-by: Jon Ross-Perkins <[email protected]>
1 parent 19a7fb0 commit 14f51d7

File tree

6 files changed

+169
-25
lines changed

6 files changed

+169
-25
lines changed

toolchain/check/import_cpp.cpp

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,29 @@ static auto AddImportIRInst(Context& context,
105105
namespace {
106106

107107
// Used to convert Clang diagnostics to Carbon diagnostics.
108+
//
109+
// Handling of Clang notes is a little subtle: as far as Clang is concerned,
110+
// notes are separate diagnostics, not connected to the error or warning that
111+
// precedes them. But in Carbon's diagnostics system, notes are part of the
112+
// enclosing diagnostic. To handle this, we buffer Clang diagnostics until we
113+
// reach a point where we know we're not in the middle of a diagnostic, and then
114+
// emit a diagnostic along with all of its notes. This is triggered when adding
115+
// or removing a Carbon context note, which could otherwise get attached to the
116+
// wrong C++ diagnostics, and at the end of the Carbon program.
108117
class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
109118
public:
110119
// Creates an instance with the location that triggers calling Clang.
111120
// `context` must not be null.
112121
explicit CarbonClangDiagnosticConsumer(
113122
Context* context, std::shared_ptr<clang::CompilerInvocation> invocation)
114-
: context_(context), invocation_(std::move(invocation)) {}
123+
: context_(context), invocation_(std::move(invocation)) {
124+
context->emitter().AddFlushFn([this] { EmitDiagnostics(); });
125+
}
126+
127+
~CarbonClangDiagnosticConsumer() override {
128+
CARBON_CHECK(diagnostic_infos_.empty(),
129+
"Missing flush before destroying diagnostic consumer");
130+
}
115131

116132
// Generates a Carbon warning for each Clang warning and a Carbon error for
117133
// each Clang error or fatal.
@@ -136,6 +152,8 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
136152
return;
137153
}
138154

155+
// TODO: This includes the full clang diagnostic, including the source
156+
// location, resulting in the location appearing twice in the output.
139157
RawStringOstream diagnostics_stream;
140158
clang::TextDiagnostic text_diagnostic(diagnostics_stream,
141159
invocation_->getLangOpts(),
@@ -154,7 +172,11 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
154172
// Outputs Carbon diagnostics based on the collected Clang diagnostics. Must
155173
// be called after the AST is set in the context.
156174
auto EmitDiagnostics() -> void {
157-
for (const ClangDiagnosticInfo& info : diagnostic_infos_) {
175+
CARBON_CHECK(context_->sem_ir().cpp_ast(),
176+
"Attempted to emit diagnostics before the AST Unit is loaded");
177+
178+
for (size_t i = 0; i != diagnostic_infos_.size(); ++i) {
179+
const ClangDiagnosticInfo& info = diagnostic_infos_[i];
158180
switch (info.level) {
159181
case clang::DiagnosticsEngine::Ignored:
160182
case clang::DiagnosticsEngine::Note:
@@ -172,16 +194,27 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
172194
CARBON_DIAGNOSTIC(CppInteropParseWarning, Warning, "{0}",
173195
std::string);
174196
CARBON_DIAGNOSTIC(CppInteropParseError, Error, "{0}", std::string);
175-
context_->emitter().Emit(
197+
auto builder = context_->emitter().Build(
176198
SemIR::LocId(info.import_ir_inst_id),
177199
info.level == clang::DiagnosticsEngine::Warning
178200
? CppInteropParseWarning
179201
: CppInteropParseError,
180202
info.message);
203+
for (;
204+
i + 1 < diagnostic_infos_.size() &&
205+
diagnostic_infos_[i + 1].level == clang::DiagnosticsEngine::Note;
206+
++i) {
207+
const ClangDiagnosticInfo& note_info = diagnostic_infos_[i + 1];
208+
CARBON_DIAGNOSTIC(CppInteropParseNote, Note, "{0}", std::string);
209+
builder.Note(SemIR::LocId(note_info.import_ir_inst_id),
210+
CppInteropParseNote, note_info.message);
211+
}
212+
builder.Emit();
181213
break;
182214
}
183215
}
184216
}
217+
diagnostic_infos_.clear();
185218
}
186219

187220
private:
@@ -223,12 +256,11 @@ static auto GenerateAst(Context& context,
223256
std::shared_ptr<clang::CompilerInvocation> invocation)
224257
-> std::pair<std::unique_ptr<clang::ASTUnit>, bool> {
225258
// Build a diagnostics engine.
226-
auto diagnostics_consumer =
227-
std::make_unique<CarbonClangDiagnosticConsumer>(&context, invocation);
228259
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
229260
clang::CompilerInstance::createDiagnostics(
230-
*fs, invocation->getDiagnosticOpts(), diagnostics_consumer.get(),
231-
/*ShouldOwnClient=*/false));
261+
*fs, invocation->getDiagnosticOpts(),
262+
new CarbonClangDiagnosticConsumer(&context, invocation),
263+
/*ShouldOwnClient=*/true));
232264

233265
// Extract the input from the frontend invocation and make sure it makes
234266
// sense.
@@ -246,6 +278,8 @@ static auto GenerateAst(Context& context,
246278
invocation->getPreprocessorOpts().addRemappedFile(file_name,
247279
includes_buffer.get());
248280

281+
clang::DiagnosticErrorTrap trap(*diags);
282+
249283
// Create the AST unit.
250284
auto ast = clang::ASTUnit::LoadFromCompilerInvocation(
251285
invocation, std::make_shared<clang::PCHContainerOperations>(), nullptr,
@@ -260,15 +294,9 @@ static auto GenerateAst(Context& context,
260294
context.sem_ir().set_cpp_ast(ast.get());
261295

262296
// Emit any diagnostics we queued up while building the AST.
263-
diagnostics_consumer->EmitDiagnostics();
264-
bool any_errors = diagnostics_consumer->getNumErrors() > 0;
297+
context.emitter().Flush();
265298

266-
// Transfer ownership of the consumer to the AST unit, in case more
267-
// diagnostics are produced by AST queries.
268-
ast->getDiagnostics().setClient(diagnostics_consumer.release(),
269-
/*ShouldOwnClient=*/true);
270-
271-
return {std::move(ast), !ast || any_errors};
299+
return {std::move(ast), !ast || trap.hasErrorOccurred()};
272300
}
273301

274302
// Adds a namespace for the `Cpp` import and returns its `NameScopeId`.
@@ -1436,12 +1464,18 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
14361464
return SemIR::ScopeLookupResult::MakeNotFound();
14371465
}
14381466

1467+
// Access checks are performed separately by the Carbon name lookup logic.
1468+
lookup->suppressAccessDiagnostics();
1469+
14391470
if (!lookup->isSingleResult()) {
1440-
context.TODO(loc_id,
1441-
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
1442-
"find a single result; LookupResultKind: {0}",
1443-
static_cast<int>(lookup->getResultKind()))
1444-
.str());
1471+
// Clang will diagnose ambiguous lookup results for us.
1472+
if (!lookup->isAmbiguous()) {
1473+
context.TODO(loc_id,
1474+
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
1475+
"find a single result; LookupResultKind: {0}",
1476+
static_cast<int>(lookup->getResultKind()))
1477+
.str());
1478+
}
14451479
context.name_scopes().AddRequiredName(scope_id, name_id,
14461480
SemIR::ErrorInst::InstId);
14471481
return SemIR::ScopeLookupResult::MakeError();

toolchain/check/testdata/interop/cpp/cpp_diagnostics.carbon

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,40 @@ fn F() {
403403
Cpp.foo();
404404
}
405405

406+
// ============================================================================
407+
// Diagnostic with notes
408+
// ============================================================================
409+
410+
// --- with_notes.h
411+
412+
void foobar(int);
413+
414+
inline void call_foobar() {
415+
foobar(1, 2);
416+
}
417+
418+
// --- fail_with_notes.carbon
419+
420+
library "[[@TEST_NAME]]";
421+
422+
// CHECK:STDERR: fail_with_notes.carbon:[[@LINE+12]]:1: in import [InImport]
423+
// CHECK:STDERR: ./with_notes.h:5: error: In file included from fail_with_notes.carbon:[[@LINE+11]]:
424+
// CHECK:STDERR: ./with_notes.h:5:3: error: no matching function for call to 'foobar'
425+
// CHECK:STDERR: 5 | foobar(1, 2);
426+
// CHECK:STDERR: | ^~~~~~
427+
// CHECK:STDERR: [CppInteropParseError]
428+
// CHECK:STDERR: fail_with_notes.carbon:[[@LINE+6]]:1: in import [InImport]
429+
// CHECK:STDERR: ./with_notes.h:2: note: ./with_notes.h:2:6: note: candidate function not viable: requires 1 argument, but 2 were provided
430+
// CHECK:STDERR: 2 | void foobar(int);
431+
// CHECK:STDERR: | ^ ~~~
432+
// CHECK:STDERR: [CppInteropParseNote]
433+
// CHECK:STDERR:
434+
import Cpp library "with_notes.h";
435+
436+
fn F() {
437+
Cpp.call_foobar();
438+
}
439+
406440
// CHECK:STDOUT: --- import_cpp_file_with_one_warning.carbon
407441
// CHECK:STDOUT:
408442
// CHECK:STDOUT: imports {

toolchain/check/testdata/interop/cpp/namespace.carbon

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,22 @@ inline namespace { namespace N {} }
121121

122122
library "[[@TEST_NAME]]";
123123

124+
// CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+13]]:1: in import [InImport]
125+
// CHECK:STDERR: error: error: reference to 'N' is ambiguous
126+
// CHECK:STDERR: [CppInteropParseError]
127+
// CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+10]]:1: in import [InImport]
128+
// CHECK:STDERR: ./inline_ambiguity.h:2: note: ./inline_ambiguity.h:2:11: note: candidate found by name lookup is 'N'
129+
// CHECK:STDERR: 2 | namespace N { void foo(); }
130+
// CHECK:STDERR: | ^
131+
// CHECK:STDERR: [CppInteropParseNote]
132+
// CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+5]]:1: in import [InImport]
133+
// CHECK:STDERR: ./inline_ambiguity.h:3: note: ./inline_ambiguity.h:3:30: note: candidate found by name lookup is '(anonymous namespace)::N'
134+
// CHECK:STDERR: 3 | inline namespace { namespace N {} }
135+
// CHECK:STDERR: | ^
136+
// CHECK:STDERR: [CppInteropParseNote]
124137
import Cpp library "inline_ambiguity.h";
125138

126139
fn MyF() {
127-
// CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Lookup succeeded but couldn't find a single result; LookupResultKind: 5` [SemanticsTodo]
128-
// CHECK:STDERR: Cpp.N.foo();
129-
// CHECK:STDERR: ^~~~~
130140
// CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `N` [InCppNameLookup]
131141
// CHECK:STDERR: Cpp.N.foo();
132142
// CHECK:STDERR: ^~~~~

toolchain/diagnostics/diagnostic_emitter.h

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class Emitter {
137137
// `consumer` is required to outlive the diagnostic emitter.
138138
explicit Emitter(Consumer* consumer) : consumer_(consumer) {}
139139

140-
virtual ~Emitter() = default;
140+
virtual ~Emitter() { Flush(); }
141141

142142
// Emits an error.
143143
//
@@ -161,6 +161,32 @@ class Emitter {
161161
// be silently ignored.
162162
auto BuildSuppressed() -> Builder { return Builder(); }
163163

164+
// Adds a flush function to flush pending diagnostics that might be enqueued
165+
// and not yet emitted. The flush function will be called whenever `Flush` is
166+
// called.
167+
//
168+
// No mechanism is provided to unregister a flush function, so the function
169+
// must ensure that it remains callable until the emitter is destroyed.
170+
//
171+
// This is used to register a handler to flush diagnostics from Clang.
172+
auto AddFlushFn(std::function<auto()->void> flush_fn) -> void {
173+
flush_fns_.push_back(std::move(flush_fn));
174+
}
175+
176+
// Flush all pending diagnostics that are queued externally, such as Clang
177+
// diagnostics. This should not be called when the external source might be in
178+
// the middle of producing a diagnostic, such as between Clang producing an
179+
// error and producing the attached notes.
180+
//
181+
// This is called automatically before any diagnostic annotator is added or
182+
// removed, to flush any pending diagnostics with suitable notes attached, and
183+
// when the emitter is destroyed.
184+
auto Flush() -> void {
185+
for (auto& flush_fn : flush_fns_) {
186+
flush_fn();
187+
}
188+
}
189+
164190
protected:
165191
// Callback type used to report context messages from ConvertLoc.
166192
// Note that the first parameter type is Loc rather than
@@ -189,6 +215,7 @@ class Emitter {
189215
friend class NoLocEmitter;
190216

191217
Consumer* consumer_;
218+
llvm::SmallVector<std::function<auto()->void>, 1> flush_fns_;
192219
llvm::SmallVector<llvm::function_ref<auto(Builder& builder)->void>>
193220
annotate_fns_;
194221
};
@@ -235,9 +262,13 @@ class AnnotationScope {
235262
public:
236263
AnnotationScope(Emitter<LocT>* emitter, AnnotateFn annotate)
237264
: emitter_(emitter), annotate_(std::move(annotate)) {
265+
emitter_->Flush();
238266
emitter_->annotate_fns_.push_back(annotate_);
239267
}
240-
~AnnotationScope() { emitter_->annotate_fns_.pop_back(); }
268+
~AnnotationScope() {
269+
emitter_->Flush();
270+
emitter_->annotate_fns_.pop_back();
271+
}
241272

242273
private:
243274
Emitter<LocT>* emitter_;

toolchain/diagnostics/diagnostic_emitter_test.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,39 @@ TEST_F(EmitterTest, EmitNote) {
8080
emitter_.Build(1, TestDiagnostic).Note(2, TestDiagnosticNote).Emit();
8181
}
8282

83+
TEST_F(EmitterTest, Flush) {
84+
bool flushed = false;
85+
auto flush_fn = [&]() { flushed = true; };
86+
87+
{
88+
FakeEmitter emitter(&consumer_);
89+
emitter.AddFlushFn(flush_fn);
90+
91+
// Registering the function does not flush.
92+
EXPECT_FALSE(flushed);
93+
94+
// Explicit calls to `Flush` should flush.
95+
emitter.Flush();
96+
EXPECT_TRUE(flushed);
97+
flushed = false;
98+
99+
{
100+
Diagnostics::AnnotationScope annot(&emitter, [](auto&) {});
101+
102+
// Registering an annotation scope should flush.
103+
EXPECT_TRUE(flushed);
104+
flushed = false;
105+
}
106+
107+
// Unregistering an annotation scope should flush.
108+
EXPECT_TRUE(flushed);
109+
flushed = false;
110+
}
111+
112+
// Destroying the emitter should flush.
113+
EXPECT_TRUE(flushed);
114+
flushed = false;
115+
}
116+
83117
} // namespace
84118
} // namespace Carbon::Testing

toolchain/diagnostics/diagnostic_kind.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ CARBON_DIAGNOSTIC_KIND(CppInteropDriverError)
171171
CARBON_DIAGNOSTIC_KIND(CppInteropDriverWarning)
172172
CARBON_DIAGNOSTIC_KIND(CppInteropParseError)
173173
CARBON_DIAGNOSTIC_KIND(CppInteropParseWarning)
174+
CARBON_DIAGNOSTIC_KIND(CppInteropParseNote)
174175
CARBON_DIAGNOSTIC_KIND(IncorrectExtension)
175176
CARBON_DIAGNOSTIC_KIND(IncorrectExtensionImplNote)
176177
CARBON_DIAGNOSTIC_KIND(DuplicateLibraryApi)

0 commit comments

Comments
 (0)