mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-10 12:28:25 +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
164 lines
5.8 KiB
C++
164 lines
5.8 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_CHECK_DIAGNOSTIC_HELPERS_H_
|
|
#define CARBON_TOOLCHAIN_CHECK_DIAGNOSTIC_HELPERS_H_
|
|
|
|
#include <concepts>
|
|
|
|
#include "clang/AST/TypeBase.h"
|
|
#include "llvm/ADT/APSInt.h"
|
|
#include "toolchain/parse/node_ids.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
// The `DiagnosticEmitterBase` is templated on this type so that
|
|
// diagnostics can be passed an `InstId` as a location, without having to
|
|
// explicitly construct a `LocId` from it first.
|
|
class LocIdForDiagnostics {
|
|
public:
|
|
// Constructs a token-only location for a diagnostic.
|
|
//
|
|
// This means the displayed location will include only the location's specific
|
|
// parse node, instead of also including its descendants.
|
|
static auto TokenOnly(Parse::NodeId node_id) -> LocIdForDiagnostics {
|
|
return LocIdForDiagnostics(SemIR::LocId(node_id), true);
|
|
}
|
|
|
|
template <typename LocT>
|
|
requires std::constructible_from<SemIR::LocId, LocT>
|
|
explicit(false) LocIdForDiagnostics(LocT loc_id)
|
|
: LocIdForDiagnostics(SemIR::LocId(loc_id), false) {}
|
|
|
|
auto loc_id() const -> SemIR::LocId { return loc_id_; }
|
|
|
|
auto is_token_only() const -> bool { return is_token_only_; }
|
|
|
|
private:
|
|
explicit LocIdForDiagnostics(SemIR::LocId loc_id, bool is_token_only)
|
|
: loc_id_(loc_id), is_token_only_(is_token_only) {}
|
|
|
|
SemIR::LocId loc_id_;
|
|
bool is_token_only_;
|
|
};
|
|
|
|
// We define the emitter separately for dependencies, so only provide a base
|
|
// here.
|
|
using DiagnosticEmitterBase = Diagnostics::Emitter<LocIdForDiagnostics>;
|
|
|
|
using DiagnosticBuilder = DiagnosticEmitterBase::Builder;
|
|
using DiagnosticContextBuilder = DiagnosticEmitterBase::ContextBuilder;
|
|
|
|
// A function that adds a context label to a diagnostic.
|
|
using DiagnosticContextFn =
|
|
llvm::function_ref<auto(DiagnosticContextBuilder&)->void>;
|
|
|
|
// An expression with a constant value, for rendering in a diagnostic. The
|
|
// diagnostic rendering will include enclosing "`"s.
|
|
struct InstIdAsConstant {
|
|
using DiagnosticType = Diagnostics::TypeInfo<std::string>;
|
|
|
|
explicit(false) InstIdAsConstant(SemIR::InstId inst_id) : inst_id(inst_id) {}
|
|
|
|
SemIR::InstId inst_id;
|
|
};
|
|
|
|
// An expression whose type should be rendered in a diagnostic. The diagnostic
|
|
// rendering will include enclosing "`"s, and may also include extra information
|
|
// about the type if it might otherwise be ambiguous or context-dependent, such
|
|
// as the targets of aliases used in the type.
|
|
//
|
|
// TODO: Include such additional information where relevant. For example:
|
|
// "`StdString` (aka `Cpp.std.basic_string(Char)`)".
|
|
//
|
|
// This should be used instead of `TypeId` as a diagnostic argument wherever
|
|
// possible, because we should eventually be able to produce a sugared type name
|
|
// in this case, whereas a `TypeId` will render as a canonical type.
|
|
struct TypeOfInstId {
|
|
using DiagnosticType = Diagnostics::TypeInfo<std::string>;
|
|
|
|
explicit(false) TypeOfInstId(SemIR::InstId inst_id) : inst_id(inst_id) {}
|
|
|
|
SemIR::InstId inst_id;
|
|
};
|
|
|
|
// A type expression, for rendering in a diagnostic. The diagnostic rendering
|
|
// will include enclosing "`"s, and may also include extra information about the
|
|
// type if it would otherwise be ambiguous.
|
|
//
|
|
// TODO: Include such additional information where relevant.
|
|
//
|
|
// This should be used when the source expression used to construct a type is
|
|
// available.
|
|
//
|
|
// Note that this is currently an alias for InstIdAsConstant. However, using
|
|
// InstIdAsType is clearer when defining CARBON_DIAGNOSTICs, and we may wish to
|
|
// distinguish type arguments in diagnostics from more general constants in some
|
|
// way in the future.
|
|
using InstIdAsType = InstIdAsConstant;
|
|
|
|
// A type expression, for rendering in a diagnostic as a raw type. When
|
|
// formatting as a raw type in a diagnostic, the type will be formatted as a
|
|
// simple Carbon expression, without enclosing "`"s. Once we start including
|
|
// extra information about types, such annotations will also not be included for
|
|
// raw types.
|
|
//
|
|
// This is intended for cases where the type is part of a larger syntactic
|
|
// construct in a diagnostic, such as "redefinition of `impl {0} as {1}`".
|
|
struct InstIdAsRawType {
|
|
using DiagnosticType = Diagnostics::TypeInfo<std::string>;
|
|
|
|
explicit(false) InstIdAsRawType(SemIR::InstId inst_id) : inst_id(inst_id) {}
|
|
|
|
SemIR::InstId inst_id;
|
|
};
|
|
|
|
// A type value for rendering in a diagnostic without enclosing "`"s. See
|
|
// `InstIdAsRawType` for details on raw type formatting.
|
|
//
|
|
// As with `TypeId`, this should be avoided as a diagnostic argument where
|
|
// possible, because it can't be formatted with syntactic sugar such as aliases
|
|
// that describe how the type was written.
|
|
struct TypeIdAsRawType {
|
|
using DiagnosticType = Diagnostics::TypeInfo<std::string>;
|
|
|
|
explicit(false) TypeIdAsRawType(SemIR::TypeId type_id) : type_id(type_id) {}
|
|
|
|
SemIR::TypeId type_id;
|
|
};
|
|
|
|
// An integer value together with its type. The type is used to determine how to
|
|
// format the value in diagnostics.
|
|
struct TypedInt {
|
|
using DiagnosticType = Diagnostics::TypeInfo<llvm::APSInt>;
|
|
|
|
SemIR::TypeId type;
|
|
llvm::APInt value;
|
|
};
|
|
|
|
struct SpecificInterfaceIdAsRawType {
|
|
using DiagnosticType = Diagnostics::TypeInfo<std::string>;
|
|
|
|
explicit(false) SpecificInterfaceIdAsRawType(
|
|
SemIR::SpecificInterfaceId specific_interface_id)
|
|
: specific_interface_id(specific_interface_id) {}
|
|
|
|
SemIR::SpecificInterfaceId specific_interface_id;
|
|
};
|
|
|
|
// A C++ type, for rendering in a diagnostic.
|
|
struct CppType {
|
|
using DiagnosticType = Diagnostics::TypeInfo<std::string>;
|
|
|
|
explicit(false) CppType(clang::QualType type) : type(type) {}
|
|
explicit(false) CppType(const clang::Type* type) : type(type, 0) {}
|
|
|
|
clang::QualType type;
|
|
};
|
|
|
|
} // namespace Carbon::Check
|
|
|
|
#endif // CARBON_TOOLCHAIN_CHECK_DIAGNOSTIC_HELPERS_H_
|