mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:20:11 +01:00
Rumdl already appears to have _significantly_ fewer bugs than prettier, and a solid LSP for editor integration. The tool is: https://github.com/rvben/rumdl/ I've separated out the change across three commits for easier review. The configuration tries to match the existing formatting, the changes to the all the files are to correct issues found by the new tool. Assisted-by: Antigravity with Gemini --------- Co-authored-by: Richard Smith <richard@metafoo.co.uk>
330 lines
14 KiB
Markdown
330 lines
14 KiB
Markdown
# Diagnostics
|
|
|
|
<!--
|
|
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
|
|
-->
|
|
|
|
<!-- toc -->
|
|
|
|
## Table of contents
|
|
|
|
- [Overview](#overview)
|
|
- [Emitters](#emitters)
|
|
- [Consumers](#consumers)
|
|
- [SortingConsumer](#sortingconsumer)
|
|
- [Producing diagnostics](#producing-diagnostics)
|
|
- [Diagnostic registry](#diagnostic-registry)
|
|
- [CARBON_DIAGNOSTIC placement](#carbon_diagnostic-placement)
|
|
- [Diagnostic context](#diagnostic-context)
|
|
- [Diagnostic parameter types](#diagnostic-parameter-types)
|
|
- [Diagnostic message style guide](#diagnostic-message-style-guide)
|
|
- [Alternatives considered](#alternatives-considered)
|
|
- [References](#references)
|
|
|
|
<!-- tocstop -->
|
|
|
|
## Overview
|
|
|
|
The diagnostic code is used by the toolchain to produce output.
|
|
|
|
## Emitters
|
|
|
|
[`Emitter`s](/toolchain/diagnostics/emitter.h) handle the main formatting of a
|
|
message. It's parameterized on a location type, which `ConvertLoc` translates
|
|
into a standardized DiagnosticLocation of file, line, and column.
|
|
|
|
When emitting, the resulting formatted message is passed to a `Consumer`.
|
|
|
|
## Consumers
|
|
|
|
`Consumer`s handle output of diagnostic messages after they've been formatted by
|
|
an Emitter. Important consumers are:
|
|
|
|
- [ConsoleConsumer](/toolchain/diagnostics/consumer.cpp): prints diagnostics
|
|
to console.
|
|
|
|
- [ErrorTrackingConsumer](/toolchain/diagnostics/consumer.h): counts the
|
|
number of errors produced, particularly so that it can be determined whether
|
|
any errors were encountered.
|
|
|
|
- [SortingConsumer](/toolchain/diagnostics/sorting_consumer.h): buffers and
|
|
sorts diagnostics to provide a more human-understandable order while
|
|
maintaining causal consistency.
|
|
|
|
### SortingConsumer
|
|
|
|
`SortingConsumer` is used by default by `carbon compile`. To see the actual
|
|
emitted order, use `carbon compile --stream-errors`.
|
|
|
|
The current `SortingConsumer` implementation sorts diagnostics based on the
|
|
`last_byte_offset`, which represents the latest token handled by the phase
|
|
emitting the diagnostic. This maintains the causal order of the toolchain's
|
|
traversal.
|
|
|
|
We expect cases where multiple diagnostics are emitted at the same offset,
|
|
particularly when they're emitted at the end of a scope. These can have attached
|
|
locations earlier in the scope, such as variable declarations. In these cases,
|
|
we put non-on-scope diagnostics first, and then sort on-scope diagnostics by
|
|
their start position (line and column).
|
|
|
|
Diagnostic sorting is stable, so that diagnostics from earlier phases are
|
|
printed first if all else is equal.
|
|
|
|
The sorting approach balances several competing needs:
|
|
|
|
- **Causal order**: Developers generally want to fix errors in the order they
|
|
are printed. If fixing error A could also fix error B, A should be printed
|
|
first.
|
|
|
|
- **Human-understandable order**: A human expects diagnostics to follow the
|
|
flow of the file. If all parse errors in a file are printed before any
|
|
semantic check errors, the developer may find it confusing to jump back and
|
|
forth through the file.
|
|
|
|
- **Performance where possible**: On fully correct code with no diagnostics,
|
|
which is our performance priority, this has negligible overhead. When there
|
|
are diagnostics, we try to only sort within the `SortingConsumer`. When
|
|
sorting is not desired (such as tools and IDEs that provide their own
|
|
ordering), it's easy to disable.
|
|
|
|
## Producing diagnostics
|
|
|
|
Diagnostics are used to surface issues from compilation. A simple diagnostic
|
|
looks like:
|
|
|
|
```cpp
|
|
CARBON_DIAGNOSTIC(InvalidCode, Error, "code is invalid");
|
|
emitter.Emit(location, InvalidCode);
|
|
```
|
|
|
|
Here, `CARBON_DIAGNOSTIC` defines a static instance of a diagnostic named
|
|
`InvalidCode` with the associated severity (`Error` or `Warning`).
|
|
|
|
The `Emit` call produces a single instance of the diagnostic. When emitted,
|
|
`"code is invalid"` will be the message used. The type of `location` depends on
|
|
the `DiagnosticEmitter`.
|
|
|
|
A diagnostic with an argument looks like:
|
|
|
|
```cpp
|
|
CARBON_DIAGNOSTIC(InvalidCharacter, Error, "invalid character {0}", char);
|
|
emitter.Emit(location, InvalidCharacter, invalid_char);
|
|
```
|
|
|
|
Here, the additional `char` argument to `CARBON_DIAGNOSTIC` specifies the type
|
|
of an argument to expect for message formatting. The `invalid_char` argument to
|
|
`Emit` provides the matching value. It's then passed along with the diagnostic
|
|
message format to `llvm::formatv` to produce the final diagnostic message.
|
|
|
|
An on-scope diagnostic uses `CARBON_DIAGNOSTIC_ON_SCOPE` which is identical
|
|
other than the macro name. For example:
|
|
|
|
```cpp
|
|
CARBON_DIAGNOSTIC_ON_SCOPE(InvalidInScope, Error, "error inside scope");
|
|
emitter.Emit(location, InvalidInScope);
|
|
```
|
|
|
|
## Diagnostic registry
|
|
|
|
There is a [registry](/toolchain/diagnostics/kind.def) which all diagnostics
|
|
must be added to. Each diagnostic has a line like:
|
|
|
|
```cpp
|
|
CARBON_DIAGNOSTIC_KIND(InvalidCode)
|
|
```
|
|
|
|
This produces a central enumeration of all diagnostics. The eventual intent is
|
|
to require tests for every diagnostic that can be produced, but that isn't
|
|
currently implemented.
|
|
|
|
## CARBON_DIAGNOSTIC placement
|
|
|
|
Idiomatically, `CARBON_DIAGNOSTIC` will be adjacent to the `Emit` call. However,
|
|
this is only because many diagnostics can only be produced in one code location.
|
|
If they can be produced in multiple locations, they will be at a higher scope so
|
|
that multiple `Emit` calls can reference them. When in a function,
|
|
`CARBON_DIAGNOSTIC` should be placed as close as possible to the usage so that
|
|
it's easier to see the associated output.
|
|
|
|
## Diagnostic context
|
|
|
|
Diagnostics can provide additional context for errors by attaching notes, which
|
|
have their own location information. A diagnostic with a note looks like:
|
|
|
|
```cpp
|
|
CARBON_DIAGNOSTIC(CallArgCountMismatch, Error,
|
|
"{0} argument{0:s} passed to function expecting "
|
|
"{1} argument{1:s}",
|
|
IntAsSelect, IntAsSelect);
|
|
CARBON_DIAGNOSTIC(InCallToFunction, Note,
|
|
"calling function declared here");
|
|
context.emitter()
|
|
.Build(call_parse_node, CallArgCountMismatch,
|
|
arg_refs.size(), param_refs.size())
|
|
.Note(param_parse_node, InCallToFunction)
|
|
.Emit();
|
|
```
|
|
|
|
The error and the note are registered as two separate diagnostics, but a single
|
|
overall diagnostic object is built and emitted, so that the error and the note
|
|
can be treated as a single unit.
|
|
|
|
Diagnostic context information can also be registered in a scope, so that all
|
|
diagnostics produced in that scope attach a specific note. For example:
|
|
|
|
```cpp
|
|
DiagnosticAnnotationScope annotate_diagnostics(
|
|
&context.emitter(), [&](auto& builder) {
|
|
CARBON_DIAGNOSTIC(
|
|
InCallToFunctionParam, Note,
|
|
"initializing parameter {0} of function declared here", int);
|
|
builder.Note(param_parse_node, InCallToFunctionParam,
|
|
diag_param_index + 1);
|
|
});
|
|
```
|
|
|
|
This is useful when delegating to another part of Check that may produce many
|
|
different kinds of diagnostic.
|
|
|
|
## Diagnostic parameter types
|
|
|
|
Diagnostic parameters should have informative types. We rely on three different
|
|
methods for formatting arguments:
|
|
|
|
- Builtin
|
|
[llvm::formatv](https://llvm.org/doxygen/FormatVariadic_8h_source.html)
|
|
support.
|
|
- This includes `char` and integer types (`int`, `int32_t`, and so on).
|
|
- String types can be added as needed, but stringifying values using the
|
|
methods noted below is preferred.
|
|
- Use `std::string` when allocations are required.
|
|
- `llvm::StringRef` is disallowed due to lifetime issues.
|
|
- `llvm::StringLiteral` is disallowed because format providers such as
|
|
`BoolAsSelect` should work in cases where a `StringLiteral` could be
|
|
used, and because string literal parameters tend to make the
|
|
resulting diagnostics hard to translate.
|
|
- `llvm::format_provider<...>` specializations.
|
|
- `BoolAsSelect` and `IntAsSelect` from
|
|
[format_providers.h](/toolchain/diagnostics/format_providers.h) are
|
|
recommended for many cases, because they allow putting the output string
|
|
in the format.
|
|
- `IntAsSelect` can also be used to support pluralization.
|
|
- Custom providers can also be added for non-translated values. For
|
|
example, `Lex::TokenKind` refers to syntax elements, and so can safely
|
|
have its own format provider.
|
|
- `DiagnosticConverter::ConvertArg` overrides.
|
|
- This can provide additional context to a formatter.
|
|
- For example, formatting `SemIR::NameId` accesses the IR's name list.
|
|
|
|
For `Check`, a custom diagnostic converter is provided that can convert some
|
|
common argument types. This includes some types defined in
|
|
[`check/diagnostic_helpers.h`](/toolchain/check/diagnostic_helpers.h) that exist
|
|
solely to be used as diagnostic parameter types. The types specifically
|
|
supported in `Check` diagnostics are:
|
|
|
|
- For formatting names:
|
|
- `NameId` for a general name. This automatically uses raw identifier
|
|
syntax for names that would collide with keywords.
|
|
- `LibraryNameId` for a library name string, which is formatted as either
|
|
`default library` or `library "foo"`.
|
|
- For formatting types, use the following, in order of preference:
|
|
|
|
- A `TypeOfInstId` parameter takes an `InstId` and formats the type of
|
|
that instruction.
|
|
- An `InstIdAsType` parameter takes an `InstId` for a type expression and
|
|
formats that type expression.
|
|
- A `TypeId` parameter is formatted as a canonical description of the
|
|
type. This should be avoided when possible: `TypeId` has no context
|
|
information, so any information about how the type was written in the
|
|
source program will be lost.
|
|
|
|
The above all include enclosing `` ` ``s around the formatted types. They
|
|
may also include additional information about the type, such as the names
|
|
bound to any aliases in the type, although at present they do not.
|
|
|
|
When a type is formatted within a larger snippet of Carbon code, it can be
|
|
desirable to instead just format the type itself; for this, `*AsRawType`
|
|
parameter types are supported:
|
|
|
|
- `InstIdAsRawType`
|
|
- `TypeIdAsRawType`
|
|
- For integer constants, `TypedInt` can be used to format an `APInt` given its
|
|
type. The type is used to determine the signedness to use for the value.
|
|
|
|
## Diagnostic message style guide
|
|
|
|
We want Carbon's diagnostics to be helpful for developers when they run into an
|
|
error, and phrased consistently across diagnostics. In addition, Carbon
|
|
diagnostics may be mixed with Clang diagnostics when compiling interoperable
|
|
code, so we are borrowing some features of Clang's
|
|
[Diagnostic Wording](https://clang.llvm.org/docs/InternalsManual.html#diagnostic-wording).
|
|
Carbon's diagnostic style aims to balance these concerns. Our style is:
|
|
|
|
- Start diagnostics with a lower case letter or quoted code, and omit trailing
|
|
periods.
|
|
|
|
- Quoted code should be enclosed in backticks, for example: ``"`{0}` is bad"``
|
|
|
|
- Phrase diagnostics as bullet points rather than full sentences. Leave out
|
|
articles unless they're necessary for clarity.
|
|
|
|
- Semicolons can be used to separate sentence fragments.
|
|
|
|
- Diagnostics should describe the situation the toolchain observed. The
|
|
language rule violated can be mentioned if it wouldn't otherwise be clear.
|
|
For example:
|
|
|
|
- `"redeclaration of X"` describes the situation and implies that
|
|
redeclarations are not permitted.
|
|
|
|
- ``"`self` declared in invalid context; can only be declared in implicit parameter list"``
|
|
describes the language rule.
|
|
|
|
- It's OK for a diagnostic to guess at the developer's intent and provide
|
|
a hint after explaining the situation and the rule, but not as a
|
|
substitute for that. For example,
|
|
``"add `as String` to convert `i32` to `String`"`` is not sufficient as
|
|
an error message, but
|
|
``"cannot implicitly convert `i32` to `String`; add `as String` for explicit conversion"``
|
|
could be acceptable.
|
|
|
|
- Use "cannot" if needed, but try to use phrasing that doesn't require it.
|
|
Avoid "allowed", "legal", "permitted", "valid", and related wording. For
|
|
example:
|
|
|
|
- ``"`export` in `impl` file"`` rather than
|
|
``"`export` is only allowed in API files"``.
|
|
- ``"`extern library` specifies current library"`` rather than
|
|
`` "`extern library` cannot specify the current library"``.
|
|
|
|
- Try to structure diagnostics such that inputs can be extracted without
|
|
string parsing; prefer [typed parameters](#diagnostic-parameter-types). We
|
|
would like to keep a path for diagnostics to be an API. There can be
|
|
exceptions where this is particularly difficult.
|
|
|
|
- TODO: Should diagnostics be atemporal and non-sequential ("multiple
|
|
declarations of X", "additional declaration here"), present tense but
|
|
sequential ("redeclaration of X", "previous declaration is here"), or
|
|
temporal ("redeclaration of X", "previous declaration was here")? We could
|
|
try to sidestep difference between the latter two by avoiding verbs with
|
|
tense ("previously declared here", "Y declared here", with no is/was).
|
|
|
|
- TODO: When do we put identifiers or expressions in diagnostics, versus
|
|
requiring notes pointing at relevant code? Is it only avoided for values, or
|
|
only allowed for types?
|
|
|
|
- TODO: Lots more things to decide, give examples.
|
|
|
|
## Alternatives considered
|
|
|
|
- [Don't sort diagnostics](/proposals/p006699-diagnostic-sorting.md#dont-sort-diagnostics)
|
|
- [Sort by line and column](/proposals/p006699-diagnostic-sorting.md#sort-by-line-and-column)
|
|
- [Sort by last processed token](/proposals/p006699-diagnostic-sorting.md#sort-by-last-processed-token)
|
|
|
|
## References
|
|
|
|
- Proposal
|
|
[#6699: Sort diagnostics](https://github.com/carbon-language/carbon-lang/pull/6699)
|