Files
carbon-lang/toolchain/diagnostics/sorting_consumer.h
T
Chandler Carruth 2c9739799b Render diagnostics through the terminal library (#7683)
Diagnostic rendering was part of the data model: `Loc::FormatLocation`
and
`Loc::FormatSnippet` wrote plain text to a stream, so no capability,
color, or
terminal width could reach them.

A diagnostic is now one message with labels, contexts, and location
information
attached, and `Diagnostics::Renderer` draws it into a
`Terminal::Buffer`. The
design is in `toolchain/docs/diagnostics_rendering.md` (#7668),
authoring in
`toolchain/docs/diagnostics.md`.

Before:

```
example.carbon:6:3: error: 1 argument passed to function expecting 0 arguments
  Run0(1);
  ^~~~~~~
example.carbon:3:1: note: calling function declared here
fn Run0() {}
^~~~~~~~~~~
```

After:

```
error: 1 argument passed to function expecting 0 arguments
       ╭─┤ example.carbon:6:3
       ┆
     3 │ fn Run0() {}
       · ─────┬─────
       ·      ╰──┤ calling function declared here
       ┆
->   6 │   Run0(1);
       ·   ───────
       ╰────────────
```

`--no-diagnostic-snippets` gives a compact form, `example.carbon:6:3-9`.
File
tests use it; the rendering tests under `toolchain/diagnostics/testdata`
ask for
snippets.

C++ interop diagnostics are data now rather than text Clang drew:
`SemIR::ClangSourceLocId` names a `clang::SourceRange`, and a Clang
location is
marked as the point it names.

`StreamConsumer` moves to a `:renderer` target that `:emitter` does not
depend
on, keeping `common/terminal` out of `consumer.h`. Capabilities come
from the
file behind the error stream, so tests get plain ASCII.

`renderer_fuzzer` draws diagnostics built from fuzzer bytes with every
capability combination, since a shape the renderer doesn't expect must
never be
why the compiler dies while reporting a problem.

TODOs mark the follow-ups: line weights and crossing lines, wrapping
that keeps
a quoted type whole, and ordering diagnostics that share a byte offset.

Also: `IncorrectExtension`'s hint moves into its message, since its note
had no
location of its own, and `strip_noise` in `check_diagnostics.py` blanks
multi-line `#define`s instead of deleting them.

Assisted-by: Claude Code
2026-10-15 06:08:57 +00:00

77 lines
2.9 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_TOOLCHAIN_DIAGNOSTICS_SORTING_CONSUMER_H_
#define CARBON_TOOLCHAIN_DIAGNOSTICS_SORTING_CONSUMER_H_
#include "common/check.h"
#include "llvm/ADT/STLExtras.h"
#include "toolchain/diagnostics/emitter.h"
namespace Carbon::Diagnostics {
// Buffers incoming diagnostics for printing and sorting.
//
// Sorting is based on `last_byte_offset` without taking the filename into
// account. When processing multiple files, it's expected that separate
// consumers will be used in order to keep diagnostics distinct. Typically
// `Diagnostic::messages[0]` will always be a location in the consumer's primary
// file, but if it needs to correspond to a different file, the
// `last_byte_offset` must still indicate an offset within the primary file.
class SortingConsumer : public Consumer {
public:
explicit SortingConsumer(Consumer& next_consumer)
: next_consumer_(&next_consumer) {}
~SortingConsumer() override {
// We choose not to automatically flush diagnostics here, because they are
// likely to refer to data that gets destroyed before the diagnostics
// consumer is destroyed, because the diagnostics consumer is typically
// created before the objects that diagnostics refer into are created.
CARBON_CHECK(diagnostics_.empty(),
"Must flush diagnostics consumer before destroying it");
}
// Buffers the diagnostic.
auto HandleDiagnostic(Diagnostic diagnostic) -> void override {
diagnostics_.push_back(std::move(diagnostic));
}
// Sorts and flushes buffered diagnostics.
auto Flush() -> void override {
llvm::stable_sort(
diagnostics_, [](const Diagnostic& lhs, const Diagnostic& rhs) {
if (lhs.last_byte_offset != rhs.last_byte_offset) {
return lhs.last_byte_offset < rhs.last_byte_offset;
}
if (lhs.is_on_scope && rhs.is_on_scope) {
// When both are on-scope, we need to compare the locations.
const auto& lhs_loc = lhs.message.loc;
const auto& rhs_loc = rhs.message.loc;
return std::tie(lhs_loc.line_number, lhs_loc.column_number) <
std::tie(rhs_loc.line_number, rhs_loc.column_number);
} else {
// Order non-on-scope before on-scope diagnostics.
return !lhs.is_on_scope && rhs.is_on_scope;
}
});
for (auto& diag : diagnostics_) {
next_consumer_->HandleDiagnostic(std::move(diag));
}
diagnostics_.clear();
}
private:
// A Diagnostic is undesirably large for inline storage by SmallVector, so we
// specify 0.
llvm::SmallVector<Diagnostic, 0> diagnostics_;
Consumer* next_consumer_;
};
} // namespace Carbon::Diagnostics
#endif // CARBON_TOOLCHAIN_DIAGNOSTICS_SORTING_CONSUMER_H_