mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-15 17:48:11 +00:00
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
77 lines
2.9 KiB
C++
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_
|