mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 20:00:12 +01:00
331 lines
14 KiB
Markdown
331 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 `{1}` expecting "
|
|
"{2} argument{2:s}",
|
|
IntAsSelect, std::string, IntAsSelect);
|
|
CARBON_DIAGNOSTIC(InCallToFunction, Note,
|
|
"calling function declared here");
|
|
context.emitter()
|
|
.Build(call_parse_node, CallArgCountMismatch,
|
|
arg_refs.size(), fn_name, aram_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/p6699.md#dont-sort-diagnostics)
|
|
- [Sort by line and column](/proposals/p6699.md#sort-by-line-and-column)
|
|
- [Sort by last processed token](/proposals/p6699.md#sort-by-last-processed-token)
|
|
|
|
## References
|
|
|
|
- Proposal
|
|
[#6699: Sort diagnostics](https://github.com/carbon-language/carbon-lang/pull/6699)
|