mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-15 22:38:10 +00:00
Improve lexer and parser diagnostic locations (#7990)
Lexer diagnostics used a single `const char*` as their source location, which only allowed pointing at a single character rather than a run of characters. This limited the underlining of unrecognized tokens, and also made escape sequence diagnostics particularly strange by stopping at whatever point the lexer discovered the error and pointing only at that location. For example: `"\w"` was reported at the `w`, and `"\x"` at the closing `"`. This change tries to always keep the primary location of a diagnostic the point at which typing (inserting or deleting) might fix the error. So for `"\x"`, `\x` is underlined, but the position is at the `"` as that's where you would type a hex code. The lexer's emitter now takes a special lexing source location that represents either a single position _between_ two characters, a single character, or a range of the source buffer. The lexer diagnostics are updated to construct these for the character range as appropriate. Parser diagnostics for a missing token were located at the _next_ token, which is particularly confusing for terminators like missing `;`s. Now they are located at a position after the previous token, with or without a space depending on the token, but typically on the same line. Diagnostics still sort by how far the parser has read. The parser diagnostics for unclosed groups also now attaches a label to the opening token when it is on a separate line. And the diagnostics for mismatched indent in a multi-line string attaches a label to the closing `'''` that establishes that indent. There are likely more location improvements we should do here, but this seemed like a reasonable incremental improvement and is what Claude found when doing an initial audit. One example where I think we should make further improvements is around suggesting raw identifiers when we find an unexpected `Cpp` or `Core` or `base`. We do this consistently now, but raw identifiers should be rare. A TODO is added for these cases. Assisted-by: Claude Code --------- Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
1 parent
611e6a210e
commit
68569d234d
173 files changed
+1782
-689
No files matched your search
@@ -2,6 +2,7 @@
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include "toolchain/diagnostics/format_providers.h"
|
||||
#include "toolchain/lex/token_kind.h"
|
||||
#include "toolchain/parse/context.h"
|
||||
#include "toolchain/parse/handle.h"
|
||||
@@ -189,7 +190,8 @@ auto HandleExprInPostfix(Context& context) -> void {
|
||||
if (context.PositionKind() != Lex::TokenKind::Period) {
|
||||
CARBON_DIAGNOSTIC(ExpectedPeriodAfterPackage, Error,
|
||||
"expected `.` after `package` expression");
|
||||
context.emitter().Emit(*context.position(), ExpectedPeriodAfterPackage);
|
||||
context.emitter().Emit(context.PointAfterPreviousToken(),
|
||||
ExpectedPeriodAfterPackage);
|
||||
state.has_error = true;
|
||||
}
|
||||
context.PushState(state);
|
||||
@@ -228,12 +230,21 @@ auto HandleExprInPostfix(Context& context) -> void {
|
||||
NodeKind::SelfTypeName)) {
|
||||
// OK, `.Self`.
|
||||
} else {
|
||||
CARBON_DIAGNOSTIC(ExpectedIdentifierOrSelfAfterPeriod, Error,
|
||||
"expected identifier or `Self` after `.`");
|
||||
context.emitter().Emit(*context.position(),
|
||||
ExpectedIdentifierOrSelfAfterPeriod);
|
||||
// A keyword after the `.` was most likely meant as the name, and is
|
||||
// what the message is about. Otherwise the name is missing, and goes
|
||||
// after the `.`.
|
||||
bool recover_as_raw = context.PositionKind().is_keyword();
|
||||
CARBON_DIAGNOSTIC(
|
||||
ExpectedIdentifierOrSelfAfterPeriod, Error,
|
||||
"expected identifier or `Self` after `.`"
|
||||
"{0:; prefix reserved word with `r#` to form a valid identifier|}",
|
||||
Diagnostics::BoolAsSelect);
|
||||
context.emitter().Emit(
|
||||
recover_as_raw ? DiagnosticLoc(*context.position())
|
||||
: context.PointAfterPreviousToken(),
|
||||
ExpectedIdentifierOrSelfAfterPeriod, recover_as_raw);
|
||||
// Only consume if it is a number or word.
|
||||
if (context.PositionKind().is_keyword()) {
|
||||
if (recover_as_raw) {
|
||||
context.AddLeafNode(NodeKind::IdentifierNameNotBeforeSignature,
|
||||
context.Consume(), /*has_error=*/true);
|
||||
} else if (context.PositionIs(Lex::TokenKind::IntLiteral)) {
|
||||
@@ -262,7 +273,16 @@ auto HandleExprInPostfix(Context& context) -> void {
|
||||
[&](const Context::State& s) { return s.kind; }));
|
||||
bool in_pattern = state_kinds == llvm::ArrayRef{StateKind::ExprPattern,
|
||||
StateKind::ExprLoop};
|
||||
context.emitter().Emit(*context.position(),
|
||||
// The missing expression goes after the token before it, unless it
|
||||
// starts a statement. Then the token before it ends another statement,
|
||||
// and the statement starts at the token found in the expression's
|
||||
// place.
|
||||
bool starts_statement =
|
||||
state_kinds ==
|
||||
llvm::ArrayRef{StateKind::ExprStatementFinish, StateKind::ExprLoop};
|
||||
context.emitter().Emit(starts_statement
|
||||
? DiagnosticLoc(*context.position())
|
||||
: context.PointAfterPreviousToken(),
|
||||
in_pattern ? ExpectedPattern : ExpectedExpr);
|
||||
}
|
||||
|
||||
@@ -508,7 +528,7 @@ auto HandleExprStatementFinish(Context& context) -> void {
|
||||
if (!state.has_error) {
|
||||
CARBON_DIAGNOSTIC(ExpectedExprSemi, Error,
|
||||
"expected `;` after expression statement");
|
||||
context.emitter().Emit(*context.position(), ExpectedExprSemi);
|
||||
context.emitter().Emit(context.PointAfterPreviousToken(), ExpectedExprSemi);
|
||||
}
|
||||
|
||||
context.AddNode(NodeKind::ExprStatement,
|
||||
|
||||
Reference in new issue
Block a user