From 68569d234df4e5ab1f21d3e1e11cd5f1b94cf56f Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Sun, 1 Nov 2026 06:22:00 +0000 Subject: [PATCH] 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 --- .../fail_base_as_declared_name.carbon | 4 +- .../declaration/extern_library.carbon | 8 +- .../check/testdata/impl/error_recovery.carbon | 2 +- .../check/testdata/impl/redeclaration.carbon | 12 +- .../check/testdata/impl/reenter_scopes.carbon | 10 +- .../cpp/basics/import/bad_import.carbon | 8 +- .../cpp/basics/import/cpp_namespace.carbon | 12 +- .../interop/cpp/operators/operators.carbon | 18 +- .../testdata/packages/missing_prelude.carbon | 205 +++++++++++++++++- .../check/testdata/patterns/underscore.carbon | 4 +- toolchain/diagnostics/diagnostic.h | 6 +- toolchain/diagnostics/mocks.h | 51 +++++ .../testdata/fail_recovery_demo.carbon | 49 ++--- toolchain/docs/diagnostics.md | 42 ++++ .../decl_with_parse_error.carbon | 8 +- toolchain/lex/BUILD | 15 ++ toolchain/lex/helpers.cpp | 6 +- toolchain/lex/helpers.h | 3 +- toolchain/lex/lex.cpp | 66 ++++-- toolchain/lex/numeric_literal.cpp | 23 +- toolchain/lex/numeric_literal.h | 3 +- toolchain/lex/numeric_literal_benchmark.cpp | 12 +- toolchain/lex/numeric_literal_fuzzer.cpp | 2 +- toolchain/lex/source_loc.h | 55 +++++ toolchain/lex/string_literal.cpp | 104 +++++++-- toolchain/lex/string_literal.h | 5 +- toolchain/lex/string_literal_benchmark.cpp | 2 +- toolchain/lex/string_literal_fuzzer.cpp | 2 +- toolchain/lex/test_helpers.h | 13 +- toolchain/lex/testdata/char_literals.carbon | 10 +- .../testdata/fail_malformed_escapes.carbon | 48 ++++ .../fail_mismatched_brackets_line_end.carbon | 38 ++++ .../testdata/multiline_string_literals.carbon | 1 + toolchain/lex/testdata/string_literals.carbon | 15 +- toolchain/lex/tokenized_buffer.cpp | 52 ++++- toolchain/lex/tokenized_buffer.h | 30 ++- toolchain/lex/tokenized_buffer_test.cpp | 130 +++++++++-- toolchain/parse/context.cpp | 56 +++-- toolchain/parse/context.h | 78 ++++++- toolchain/parse/handle_alias.cpp | 3 +- toolchain/parse/handle_array_expr.cpp | 3 +- toolchain/parse/handle_base.cpp | 3 +- toolchain/parse/handle_binding_pattern.cpp | 25 ++- toolchain/parse/handle_choice.cpp | 34 ++- toolchain/parse/handle_code_block.cpp | 3 +- .../parse/handle_decl_name_and_params.cpp | 51 +++-- toolchain/parse/handle_expr.cpp | 36 ++- toolchain/parse/handle_form_literal.cpp | 3 +- toolchain/parse/handle_friend.cpp | 31 ++- toolchain/parse/handle_function.cpp | 3 +- toolchain/parse/handle_if_expr.cpp | 6 +- toolchain/parse/handle_impl.cpp | 5 +- toolchain/parse/handle_import_and_package.cpp | 75 ++++--- toolchain/parse/handle_inline_decl.cpp | 6 +- toolchain/parse/handle_lambda.cpp | 5 +- toolchain/parse/handle_let.cpp | 27 ++- toolchain/parse/handle_match.cpp | 16 +- toolchain/parse/handle_observe.cpp | 3 +- toolchain/parse/handle_paren_expr.cpp | 2 +- toolchain/parse/handle_pattern.cpp | 9 +- toolchain/parse/handle_pattern_list.cpp | 2 +- toolchain/parse/handle_period.cpp | 5 +- toolchain/parse/handle_require.cpp | 3 +- toolchain/parse/handle_requirement.cpp | 2 +- toolchain/parse/handle_statement.cpp | 5 +- toolchain/parse/handle_var.cpp | 3 +- .../parse/testdata/alias/fail_syntax.carbon | 18 +- .../parse/testdata/array/fail_syntax.carbon | 18 +- .../fail_missing_token_locations.carbon | 95 ++++++++ .../basics/fail_paren_match_regression.carbon | 8 +- .../testdata/basics/form_literals.carbon | 18 +- .../basics/multiline_string_recovery.carbon | 2 +- .../fail_keyword_alternative_name.carbon | 28 +++ toolchain/parse/testdata/class/adapt.carbon | 8 +- .../parse/testdata/class/fail_base.carbon | 10 +- .../testdata/class/fail_modifiers.carbon | 8 +- .../parse/testdata/class/fail_var_name.carbon | 10 +- toolchain/parse/testdata/class/friend.carbon | 15 +- .../testdata/for/fail_missing_cond.carbon | 12 +- .../parse/testdata/for/fail_missing_in.carbon | 2 +- .../testdata/for/fail_returned_var.carbon | 2 +- .../testdata/for/fail_square_brackets.carbon | 4 +- .../testdata/function/declaration.carbon | 24 +- .../parse/testdata/function/definition.carbon | 10 +- .../parse/testdata/function/extern.carbon | 4 +- .../fail_template_no_param.carbon | 4 +- .../generics/generic_params/template.carbon | 2 +- .../testdata/generics/impl/fail_impl.carbon | 20 +- .../impl/fail_out_of_line_member.carbon | 2 +- .../testdata/generics/impl/match_first.carbon | 4 +- .../fail_associated_constants.carbon | 16 +- .../interface/fail_missing_name.carbon | 2 +- .../interface/fail_missing_open_curly.carbon | 6 +- .../interface/fail_self_param_syntax.carbon | 2 +- .../generics/interface/require.carbon | 4 +- .../named_constraint/fail_incomplete.carbon | 4 +- .../testdata/if/fail_else_unbraced.carbon | 18 +- .../parse/testdata/if/fail_errors.carbon | 7 +- .../testdata/if/fail_square_brackets.carbon | 2 +- .../parse/testdata/if/fail_unbraced.carbon | 8 +- .../testdata/if_expr/fail_top_level_if.carbon | 4 +- .../testdata/index/fail_malformed_expr.carbon | 3 +- toolchain/parse/testdata/lambda/lambda.carbon | 22 +- .../let/expression_pattern_precedence.carbon | 4 +- .../parse/testdata/let/fail_bad_name.carbon | 2 +- .../testdata/let/fail_missing_name.carbon | 4 +- .../parse/testdata/let/fail_no_semi.carbon | 8 +- .../match/fail_missing_case_arrow.carbon | 4 +- .../match/fail_missing_case_pattern.carbon | 2 +- .../fail_missing_case_statements_block.carbon | 4 +- .../match/fail_missing_cases_block.carbon | 4 +- .../match/fail_missing_default_arrow.carbon | 2 +- ...il_missing_default_statements_block.carbon | 4 +- .../fail_missing_guard_close_paren.carbon | 2 +- ...sing_guard_parens_only_parse_errors.carbon | 4 +- .../testdata/namespace/fail_arrow.carbon | 2 +- .../testdata/namespace/fail_incomplete.carbon | 12 +- .../testdata/namespace/fail_modifiers.carbon | 4 +- .../observe/fail_invalid_operator.carbon | 2 +- .../testdata/observe/fail_missing_expr.carbon | 6 +- .../fail_infix_uneven_space_after.carbon | 2 +- .../fail_infix_uneven_space_before.carbon | 2 +- .../operators/fail_invalid_infix.carbon | 8 +- .../operators/fail_postincrement.carbon | 4 +- .../operators/fail_precedence_assign.carbon | 6 +- .../operators/fail_precedence_where.carbon | 12 +- .../testdata/operators/fail_star_minus.carbon | 2 +- .../testdata/operators/fail_star_star.carbon | 2 +- .../parse/testdata/operators/modifier.carbon | 2 +- .../parse/testdata/operators/unary.carbon | 2 +- .../testdata/package_expr/fail_in_name.carbon | 23 +- .../parse/testdata/packages/export.carbon | 20 +- .../packages/import/cpp_inline.carbon | 41 +--- .../testdata/packages/import/export.carbon | 2 +- .../packages/import/fail_extra_string.carbon | 2 +- .../import/fail_inline_cpp_no_semi.carbon | 32 +++ .../import/fail_library_is_identifier.carbon | 2 +- .../import/fail_name_is_keyword.carbon | 5 +- .../packages/import/fail_no_semi.carbon | 4 +- .../import/fail_omit_library_keyword.carbon | 2 +- .../testdata/packages/import/fail_type.carbon | 2 +- .../packages/library/fail_invalid_name.carbon | 10 +- .../packages/package/fail_extra_string.carbon | 2 +- .../package/fail_in_nested_scope.carbon | 2 +- .../package/fail_library_is_identifier.carbon | 2 +- .../package/fail_library_skips_name.carbon | 2 +- .../package/fail_name_is_keyword.carbon | 5 +- .../packages/package/fail_no_semi.carbon | 4 +- .../package/fail_omit_library_keyword.carbon | 2 +- .../package/fail_trailing_impl.carbon | 2 +- ...e_instead_of_compound_member_access.carbon | 4 +- .../testdata/return/fail_expr_no_semi.carbon | 4 +- .../parse/testdata/return/fail_no_semi.carbon | 8 +- .../return/fail_returned_no_var.carbon | 2 +- .../testdata/return/fail_var_no_semi.carbon | 4 +- .../struct/fail_extra_token_in_type.carbon | 2 +- .../struct/fail_extra_token_in_value.carbon | 2 +- .../struct/fail_period_string_colon.carbon | 2 +- .../struct/fail_period_string_equals.carbon | 2 +- .../testdata/struct/struct_pattern.carbon | 32 +-- toolchain/parse/testdata/typeof/basic.carbon | 43 +++- .../parse/testdata/var/fail_bad_name.carbon | 4 +- .../testdata/var/fail_in_interface.carbon | 4 +- .../parse/testdata/var/fail_no_semi.carbon | 8 +- .../parse/testdata/var/fail_runtime.carbon | 14 +- .../parse/testdata/variadics/expand.carbon | 2 +- .../variadics/pack_expansion_statement.carbon | 4 +- .../testdata/where_expr/designators.carbon | 10 +- .../testdata/where_expr/fail_rewrite.carbon | 2 +- .../testdata/where_expr/where_and.carbon | 4 +- .../parse/testdata/while/fail_no_semi.carbon | 8 +- .../parse/testdata/while/fail_unbraced.carbon | 4 +- toolchain/parse/tree_and_subtrees.cpp | 21 +- 173 files changed, 1782 insertions(+), 689 deletions(-) create mode 100644 toolchain/lex/source_loc.h create mode 100644 toolchain/lex/testdata/fail_malformed_escapes.carbon create mode 100644 toolchain/lex/testdata/fail_mismatched_brackets_line_end.carbon create mode 100644 toolchain/parse/testdata/basics/fail_missing_token_locations.carbon create mode 100644 toolchain/parse/testdata/choice/fail_keyword_alternative_name.carbon create mode 100644 toolchain/parse/testdata/packages/import/fail_inline_cpp_no_semi.carbon diff --git a/toolchain/check/testdata/class/inheritance/fail_base_as_declared_name.carbon b/toolchain/check/testdata/class/inheritance/fail_base_as_declared_name.carbon index 748712346334..635c9bbeaab9 100644 --- a/toolchain/check/testdata/class/inheritance/fail_base_as_declared_name.carbon +++ b/toolchain/check/testdata/class/inheritance/fail_base_as_declared_name.carbon @@ -12,8 +12,6 @@ namespace N; -// CHECK:STDERR: fail_base_as_declared_name.carbon:[[@LINE+4]]:6-9: error: `.` should be followed by a name [ExpectedDeclNameAfterPeriod] -// CHECK:STDERR: -// CHECK:STDERR: fail_base_as_declared_name.carbon:[[@LINE+2]]:6-9: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] +// CHECK:STDERR: fail_base_as_declared_name.carbon:[[@LINE+2]]:6-9: error: `.` should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclNameAfterPeriod] // CHECK:STDERR: fn N.base() {} diff --git a/toolchain/check/testdata/function/declaration/extern_library.carbon b/toolchain/check/testdata/function/declaration/extern_library.carbon index 3e7e445ad023..e352ba816e25 100644 --- a/toolchain/check/testdata/function/declaration/extern_library.carbon +++ b/toolchain/check/testdata/function/declaration/extern_library.carbon @@ -86,12 +86,12 @@ extern library "extern_library_owner" fn F(); library "[[@TEST_NAME]]"; +// CHECK:STDERR: fail_extern_library_mismatch_owner.carbon:[[@LINE+4]]:41: error: `import` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: +// CHECK:STDERR: fail_extern_library_mismatch_owner.carbon:[[@LINE+2]]:1-40: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] +// CHECK:STDERR: import library "extern_library_mismatch" -// CHECK:STDERR: fail_extern_library_mismatch_owner.carbon:[[@LINE+4]]:1-6: error: `import` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: -// CHECK:STDERR: fail_extern_library_mismatch_owner.carbon:[[@LINE-4]]:1-40: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] -// CHECK:STDERR: extern fn F(); // --- fail_extern_self_library.carbon diff --git a/toolchain/check/testdata/impl/error_recovery.carbon b/toolchain/check/testdata/impl/error_recovery.carbon index 5a719b1e3b9b..edc1248c0f74 100644 --- a/toolchain/check/testdata/impl/error_recovery.carbon +++ b/toolchain/check/testdata/impl/error_recovery.carbon @@ -112,7 +112,7 @@ impl C as I { // This leaves the impl with a placeholder instruction in the witness table. // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+6]]:9: error: name `x` not found [NameNotFound] // CHECK:STDERR: - // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+4]]:11-14: error: expected `,` or `]` [UnexpectedTokenAfterListElement] + // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+4]]:10: error: expected `,` or `]` [UnexpectedTokenAfterListElement] // CHECK:STDERR: // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+2]]:8-18: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: diff --git a/toolchain/check/testdata/impl/redeclaration.carbon b/toolchain/check/testdata/impl/redeclaration.carbon index 56346060efbc..944cf7ea7344 100644 --- a/toolchain/check/testdata/impl/redeclaration.carbon +++ b/toolchain/check/testdata/impl/redeclaration.carbon @@ -49,11 +49,11 @@ class C { } // TODO: This should typecheck (see #5366). -// CHECK:STDERR: fail_todo_redeclaration_reenter_class.carbon:[[@LINE+6]]:9-10: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_todo_redeclaration_reenter_class.carbon:[[@LINE+6]]:9: error: expected expression [ExpectedExpr] // CHECK:STDERR: // CHECK:STDERR: fail_todo_redeclaration_reenter_class.carbon:[[@LINE+4]]:9-10: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: -// CHECK:STDERR: fail_todo_redeclaration_reenter_class.carbon:[[@LINE+2]]:15: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_todo_redeclaration_reenter_class.carbon:[[@LINE+2]]:14: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: impl C.(as Z) {} @@ -71,7 +71,7 @@ class C { // TODO: This should typecheck (see #5366). // CHECK:STDERR: fail_todo_redeclaration_reenter_class_explicit_self.carbon:[[@LINE+6]]:9-12: error: name `Self` not found [NameNotFound] // CHECK:STDERR: -// CHECK:STDERR: fail_todo_redeclaration_reenter_class_explicit_self.carbon:[[@LINE+4]]:20: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_todo_redeclaration_reenter_class_explicit_self.carbon:[[@LINE+4]]:19: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: // CHECK:STDERR: fail_todo_redeclaration_reenter_class_explicit_self.carbon:[[@LINE+2]]:1-21: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: @@ -89,7 +89,7 @@ class C { // TODO: This should typecheck (see #5366). // CHECK:STDERR: fail_todo_redeclaration_reenter_class_mixed_self_outside.carbon:[[@LINE+6]]:9-12: error: name `Self` not found [NameNotFound] // CHECK:STDERR: -// CHECK:STDERR: fail_todo_redeclaration_reenter_class_mixed_self_outside.carbon:[[@LINE+4]]:20: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_todo_redeclaration_reenter_class_mixed_self_outside.carbon:[[@LINE+4]]:19: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: // CHECK:STDERR: fail_todo_redeclaration_reenter_class_mixed_self_outside.carbon:[[@LINE+2]]:1-21: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: @@ -105,11 +105,11 @@ class C { } // TODO: This should typecheck (see #5366). -// CHECK:STDERR: fail_todo_redeclaration_reenter_class_mixed_self_inside.carbon:[[@LINE+6]]:9-10: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_todo_redeclaration_reenter_class_mixed_self_inside.carbon:[[@LINE+6]]:9: error: expected expression [ExpectedExpr] // CHECK:STDERR: // CHECK:STDERR: fail_todo_redeclaration_reenter_class_mixed_self_inside.carbon:[[@LINE+4]]:9-10: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: -// CHECK:STDERR: fail_todo_redeclaration_reenter_class_mixed_self_inside.carbon:[[@LINE+2]]:15: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_todo_redeclaration_reenter_class_mixed_self_inside.carbon:[[@LINE+2]]:14: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: impl C.(as Z) {} diff --git a/toolchain/check/testdata/impl/reenter_scopes.carbon b/toolchain/check/testdata/impl/reenter_scopes.carbon index 00c4da891795..f45cd2cbcacf 100644 --- a/toolchain/check/testdata/impl/reenter_scopes.carbon +++ b/toolchain/check/testdata/impl/reenter_scopes.carbon @@ -21,11 +21,11 @@ class N.C { class D {} } -// CHECK:STDERR: fail_todo_reenter_class_scope.carbon:[[@LINE+6]]:11-12: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_todo_reenter_class_scope.carbon:[[@LINE+6]]:11: error: expected expression [ExpectedExpr] // CHECK:STDERR: // CHECK:STDERR: fail_todo_reenter_class_scope.carbon:[[@LINE+4]]:11-12: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: -// CHECK:STDERR: fail_todo_reenter_class_scope.carbon:[[@LINE+2]]:17: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_todo_reenter_class_scope.carbon:[[@LINE+2]]:16: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: impl N.C.(as Z) { fn ZF() { @@ -49,7 +49,7 @@ class N.C { // CHECK:STDERR: // CHECK:STDERR: fail_todo_reenter_class_scope_explicit_self.carbon:[[@LINE+6]]:19: error: name `Z` not found [NameNotFound] // CHECK:STDERR: -// CHECK:STDERR: fail_todo_reenter_class_scope_explicit_self.carbon:[[@LINE+4]]:22: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_todo_reenter_class_scope_explicit_self.carbon:[[@LINE+4]]:21: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: // CHECK:STDERR: fail_todo_reenter_class_scope_explicit_self.carbon:[[@LINE+2]]:1-22: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: @@ -75,7 +75,7 @@ class N.C { // CHECK:STDERR: // CHECK:STDERR: fail_todo_reenter_class_scope_unrelated_self.carbon:[[@LINE+6]]:16: error: name `Z` not found [NameNotFound] // CHECK:STDERR: -// CHECK:STDERR: fail_todo_reenter_class_scope_unrelated_self.carbon:[[@LINE+4]]:19: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_todo_reenter_class_scope_unrelated_self.carbon:[[@LINE+4]]:18: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: // CHECK:STDERR: fail_todo_reenter_class_scope_unrelated_self.carbon:[[@LINE+2]]:1-19: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: @@ -99,7 +99,7 @@ class N.D {} // CHECK:STDERR: // CHECK:STDERR: fail_todo_reenter_namespace_scope.carbon:[[@LINE+6]]:14: error: name `Z` not found [NameNotFound] // CHECK:STDERR: -// CHECK:STDERR: fail_todo_reenter_namespace_scope.carbon:[[@LINE+4]]:17: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_todo_reenter_namespace_scope.carbon:[[@LINE+4]]:16: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: // CHECK:STDERR: fail_todo_reenter_namespace_scope.carbon:[[@LINE+2]]:1-17: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: diff --git a/toolchain/check/testdata/interop/cpp/basics/import/bad_import.carbon b/toolchain/check/testdata/interop/cpp/basics/import/bad_import.carbon index bb615e7ed4f0..6d176396a035 100644 --- a/toolchain/check/testdata/interop/cpp/basics/import/bad_import.carbon +++ b/toolchain/check/testdata/interop/cpp/basics/import/bad_import.carbon @@ -31,7 +31,7 @@ import Cpp inline '''c++ library "[[@TEST_NAME]]"; -// CHECK:STDERR: fail_import_cpp_library_without_name_and_inline_without_content.carbon:[[@LINE+4]]:20-25: error: expected a string literal to specify the library name [ExpectedLibraryName] +// CHECK:STDERR: fail_import_cpp_library_without_name_and_inline_without_content.carbon:[[@LINE+4]]:19: error: expected a string literal to specify the library name [ExpectedLibraryName] // CHECK:STDERR: // CHECK:STDERR: fail_import_cpp_library_without_name_and_inline_without_content.carbon:[[@LINE+2]]:20-25: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: @@ -41,7 +41,7 @@ import Cpp library inline; library "[[@TEST_NAME]]"; -// CHECK:STDERR: fail_import_cpp_library_with_name_and_inline_without_content.carbon:[[@LINE+4]]:28-33: error: `import` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_import_cpp_library_with_name_and_inline_without_content.carbon:[[@LINE+4]]:27: error: `import` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: // CHECK:STDERR: fail_import_cpp_library_with_name_and_inline_without_content.carbon:[[@LINE+2]]:1-34: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: @@ -51,7 +51,7 @@ import Cpp library "foo.h" inline; library "[[@TEST_NAME]]"; -// CHECK:STDERR: fail_import_cpp_library_without_name_and_inline_with_content.carbon:[[@LINE+4]]:20-25: error: expected a string literal to specify the library name [ExpectedLibraryName] +// CHECK:STDERR: fail_import_cpp_library_without_name_and_inline_with_content.carbon:[[@LINE+4]]:19: error: expected a string literal to specify the library name [ExpectedLibraryName] // CHECK:STDERR: // CHECK:STDERR: fail_import_cpp_library_without_name_and_inline_with_content.carbon:[[@LINE+2]]:20-25: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: @@ -61,7 +61,7 @@ import Cpp library inline ""; library "[[@TEST_NAME]]"; -// CHECK:STDERR: fail_import_cpp_library_with_name_and_inline_with_content.carbon:[[@LINE+4]]:28-33: error: `import` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_import_cpp_library_with_name_and_inline_with_content.carbon:[[@LINE+4]]:27: error: `import` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: // CHECK:STDERR: fail_import_cpp_library_with_name_and_inline_with_content.carbon:[[@LINE+2]]:1-37: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: diff --git a/toolchain/check/testdata/interop/cpp/basics/import/cpp_namespace.carbon b/toolchain/check/testdata/interop/cpp/basics/import/cpp_namespace.carbon index 4239b7a64d23..ae4da0fd0e46 100644 --- a/toolchain/check/testdata/interop/cpp/basics/import/cpp_namespace.carbon +++ b/toolchain/check/testdata/interop/cpp/basics/import/cpp_namespace.carbon @@ -20,9 +20,7 @@ library "[[@TEST_NAME]]"; import Cpp library "header.h"; -// CHECK:STDERR: fail_duplicate_cpp_name.carbon:[[@LINE+4]]:11-13: error: `namespace` introducer should be followed by a name [ExpectedDeclName] -// CHECK:STDERR: -// CHECK:STDERR: fail_duplicate_cpp_name.carbon:[[@LINE+2]]:11-13: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] +// CHECK:STDERR: fail_duplicate_cpp_name.carbon:[[@LINE+2]]:11-13: error: `namespace` introducer should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclName] // CHECK:STDERR: namespace Cpp; @@ -33,9 +31,7 @@ library "[[@TEST_NAME]]"; import Cpp library "header.h"; namespace N; -// CHECK:STDERR: fail_cpp_in_inner_namespace.carbon:[[@LINE+4]]:13-15: error: `.` should be followed by a name [ExpectedDeclNameAfterPeriod] -// CHECK:STDERR: -// CHECK:STDERR: fail_cpp_in_inner_namespace.carbon:[[@LINE+2]]:13-15: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] +// CHECK:STDERR: fail_cpp_in_inner_namespace.carbon:[[@LINE+2]]:13-15: error: `.` should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclNameAfterPeriod] // CHECK:STDERR: namespace N.Cpp; @@ -55,9 +51,9 @@ library "[[@TEST_NAME]]"; import Cpp library "header.h"; -// CHECK:STDERR: fail_add_name_to_cpp_namespace.carbon:[[@LINE+4]]:7-9: error: `class` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_add_name_to_cpp_namespace.carbon:[[@LINE+4]]:7-9: error: `class` introducer should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclName] // CHECK:STDERR: -// CHECK:STDERR: fail_add_name_to_cpp_namespace.carbon:[[@LINE+2]]:7-9: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] +// CHECK:STDERR: fail_add_name_to_cpp_namespace.carbon:[[@LINE+2]]:7-9: error: name `r#Cpp` not found [NameNotFound] // CHECK:STDERR: class Cpp.C {}; diff --git a/toolchain/check/testdata/interop/cpp/operators/operators.carbon b/toolchain/check/testdata/interop/cpp/operators/operators.carbon index 9272a788fb53..efd68857e50f 100644 --- a/toolchain/check/testdata/interop/cpp/operators/operators.carbon +++ b/toolchain/check/testdata/interop/cpp/operators/operators.carbon @@ -69,12 +69,12 @@ import Cpp library "prefix_inc_and_dec.h"; fn F() { var prefix: Cpp.Prefix = Cpp.Prefix.Prefix(); - // CHECK:STDERR: fail_prefix_calling_postfix.carbon:[[@LINE+4]]:9-10: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_prefix_calling_postfix.carbon:[[@LINE+4]]:9: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: // CHECK:STDERR: fail_prefix_calling_postfix.carbon:[[@LINE+2]]:3-11: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: prefix++; - // CHECK:STDERR: fail_prefix_calling_postfix.carbon:[[@LINE+2]]:9-10: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_prefix_calling_postfix.carbon:[[@LINE+2]]:9: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: prefix--; } @@ -493,26 +493,26 @@ fn F() { var c: Cpp.UnsupportedUnaryC = Cpp.UnsupportedUnaryC.UnsupportedUnaryC(); // Arithmetic. - // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+6]]:37: error: expected expression [ExpectedExpr] + // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+6]]:36: error: expected expression [ExpectedExpr] // CHECK:STDERR: - // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+4]]:37: error: whitespace missing after binary operator [BinaryOperatorRequiresWhitespace] + // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+4]]:37: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: - // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+2]]:37: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] + // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+2]]:38: error: whitespace missing after binary operator [BinaryOperatorRequiresWhitespace] // CHECK:STDERR: let plus: Cpp.UnsupportedUnaryC = +c; // Increment and Decrement. - // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+2]]:4-5: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+2]]:4: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: c++; - // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+2]]:4-5: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+2]]:4: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: c--; // Bitwise. - // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+4]]:42: error: expected expression [ExpectedExpr] + // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+4]]:41: error: expected expression [ExpectedExpr] // CHECK:STDERR: - // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+2]]:42: error: `let` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+2]]:41: error: `let` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: let not_value: Cpp.UnsupportedUnaryC = ~c; diff --git a/toolchain/check/testdata/packages/missing_prelude.carbon b/toolchain/check/testdata/packages/missing_prelude.carbon index 7e81b121dcee..50bcc38aa81c 100644 --- a/toolchain/check/testdata/packages/missing_prelude.carbon +++ b/toolchain/check/testdata/packages/missing_prelude.carbon @@ -75,31 +75,33 @@ let n: i32 = {}; library "[[@TEST_NAME]]"; -// CHECK:STDERR: fail_prelude_as_namespace.carbon:[[@LINE+4]]:11-14: error: `namespace` introducer should be followed by a name [ExpectedDeclName] -// CHECK:STDERR: -// CHECK:STDERR: fail_prelude_as_namespace.carbon:[[@LINE+2]]:11-14: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] +// CHECK:STDERR: fail_prelude_as_namespace.carbon:[[@LINE+2]]:11-14: error: `namespace` introducer should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclName] // CHECK:STDERR: namespace Core; -// CHECK:STDERR: fail_prelude_as_namespace.carbon:[[@LINE+2]]:4-7: error: `fn` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_prelude_as_namespace.carbon:[[@LINE+4]]:4-7: error: `fn` introducer should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclName] +// CHECK:STDERR: +// CHECK:STDERR: fail_prelude_as_namespace.carbon:[[@LINE+2]]:30-33: warning: binding `N` unused [UnusedBinding] // CHECK:STDERR: fn Core.Int[T: type](generic N: T) -> {} { return {}; } // Make sure use of `i32` doesn't crash after above errors. +// CHECK:STDERR: fail_prelude_as_namespace.carbon:[[@LINE+2]]:15-17: error: `Core.Int` implicitly referenced here, but package `Core` not found [CoreNotFound] +// CHECK:STDERR: let n: type = i32; // --- fail_prelude_as_class.carbon library "[[@TEST_NAME]]"; -// CHECK:STDERR: fail_prelude_as_class.carbon:[[@LINE+4]]:7-10: error: `class` introducer should be followed by a name [ExpectedDeclName] -// CHECK:STDERR: -// CHECK:STDERR: fail_prelude_as_class.carbon:[[@LINE+2]]:7-10: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] +// CHECK:STDERR: fail_prelude_as_class.carbon:[[@LINE+2]]:7-10: error: `class` introducer should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclName] // CHECK:STDERR: class Core { - fn Int[T: type](generic N: T) -> {} { return {}; } + fn Int[T: type](generic _: T) -> {} { return {}; } } +// CHECK:STDERR: fail_prelude_as_class.carbon:[[@LINE+2]]:15-17: error: `Core.Int` implicitly referenced here, but package `Core` not found [CoreNotFound] +// CHECK:STDERR: let n: type = i32; @@ -699,5 +701,192 @@ let n: type = i32; // CHECK:STDOUT: // CHECK:STDOUT: --- fail_prelude_as_namespace.carbon // CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %.Self.frozen: type = symbolic_binding .Self [symbolic_self] +// CHECK:STDOUT: %pattern_type.9a5: type = pattern_type type [concrete] +// CHECK:STDOUT: %T.patt: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic] +// CHECK:STDOUT: %T: type = symbolic_binding T, 0 [symbolic] +// CHECK:STDOUT: %pattern_type.51d: type = pattern_type %T [symbolic] +// CHECK:STDOUT: %N.patt: %pattern_type.51d = symbolic_binding_pattern N, 1 [symbolic] +// CHECK:STDOUT: %N: %T = symbolic_binding N, 1 [symbolic] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete] +// CHECK:STDOUT: %.469: Core.Form = init_form %empty_struct_type [concrete] +// CHECK:STDOUT: %pattern_type.a96: type = pattern_type %empty_struct_type [concrete] +// CHECK:STDOUT: %return.param_patt: %pattern_type.a96 = out_param_pattern [concrete] +// CHECK:STDOUT: %return.patt: %pattern_type.a96 = return_slot_pattern %return.param_patt, %empty_struct_type [concrete] +// CHECK:STDOUT: %Int.type: type = fn_type @Int [concrete] +// CHECK:STDOUT: %Int: %Int.type = struct_value () [concrete] +// CHECK:STDOUT: %n.patt: %pattern_type.9a5 = value_binding_pattern n [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .r#Core = %Core +// CHECK:STDOUT: .n = %n +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core: = namespace [concrete] { +// CHECK:STDOUT: .Int = %Int.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Int.decl: %Int.type = fn_decl @Int [concrete = constants.%Int] { +// CHECK:STDOUT: %T.patt.loc12_14.1: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc12_14.2 (constants.%T.patt)] +// CHECK:STDOUT: %N.patt.loc12_31.1: @Int.%pattern_type (%pattern_type.51d) = symbolic_binding_pattern N, 1 [symbolic = %N.patt.loc12_31.2 (constants.%N.patt)] +// CHECK:STDOUT: %return.param_patt: %pattern_type.a96 = out_param_pattern [concrete = constants.%return.param_patt] +// CHECK:STDOUT: %return.patt: %pattern_type.a96 = return_slot_pattern %return.param_patt, %.loc12_40.2 [concrete = constants.%return.patt] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %.loc12_40.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc12_40.2: type = converted %.loc12_40.1, constants.%empty_struct_type [concrete = constants.%empty_struct_type] +// CHECK:STDOUT: %.loc12_40.3: Core.Form = init_form %.loc12_40.2 [concrete = constants.%.469] +// CHECK:STDOUT: %.loc12_16.1: type = splice_block %.loc12_16.2 [concrete = type] { +// CHECK:STDOUT: %.Self.frozen.loc12_14: type = symbolic_binding .Self [symbolic_self = constants.%.Self.frozen] +// CHECK:STDOUT: %.loc12_16.2: type = type_literal type [concrete = type] +// CHECK:STDOUT: } +// CHECK:STDOUT: %T.loc12_14.2: type = symbolic_binding T, 0 [symbolic = %T.loc12_14.1 (constants.%T)] +// CHECK:STDOUT: %.loc12_33: type = splice_block %T.ref [symbolic = %T.loc12_14.1 (constants.%T)] { +// CHECK:STDOUT: %.Self.frozen.loc12_31: type = symbolic_binding .Self [symbolic_self = constants.%.Self.frozen] +// CHECK:STDOUT: %T.ref: type = name_ref T, %T.loc12_14.2 [symbolic = %T.loc12_14.1 (constants.%T)] +// CHECK:STDOUT: } +// CHECK:STDOUT: %N.loc12_31.2: @Int.%T.loc12_14.1 (%T) = symbolic_binding N, 1 [symbolic = %N.loc12_31.1 (constants.%N)] +// CHECK:STDOUT: %return.param: ref %empty_struct_type = out_param call_param0 +// CHECK:STDOUT: %return: ref %empty_struct_type = return_slot %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc17: type = type_literal type [concrete = type] +// CHECK:STDOUT: %n: type = wrapper_binding n, @__global_init.%.loc17 [concrete = ] +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %n.patt: %pattern_type.9a5 = value_binding_pattern n [concrete = constants.%n.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: generic fn @Int(%T.loc12_14.2: type, %N.loc12_31.2: @Int.%T.loc12_14.1 (%T)) { +// CHECK:STDOUT: %T.patt.loc12_14.2: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc12_14.2 (constants.%T.patt)] +// CHECK:STDOUT: %T.loc12_14.1: type = symbolic_binding T, 0 [symbolic = %T.loc12_14.1 (constants.%T)] +// CHECK:STDOUT: %pattern_type: type = pattern_type %T.loc12_14.1 [symbolic = %pattern_type (constants.%pattern_type.51d)] +// CHECK:STDOUT: %N.patt.loc12_31.2: @Int.%pattern_type (%pattern_type.51d) = symbolic_binding_pattern N, 1 [symbolic = %N.patt.loc12_31.2 (constants.%N.patt)] +// CHECK:STDOUT: %N.loc12_31.1: @Int.%T.loc12_14.1 (%T) = symbolic_binding N, 1 [symbolic = %N.loc12_31.1 (constants.%N)] +// CHECK:STDOUT: +// CHECK:STDOUT: !definition: +// CHECK:STDOUT: +// CHECK:STDOUT: fn() -> out %return.param: %empty_struct_type { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc12_52.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc12_52.2: init %empty_struct_type = struct_init () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc12_53: init %empty_struct_type = converted %.loc12_52.1, %.loc12_52.2 [concrete = constants.%empty_struct] +// CHECK:STDOUT: return %.loc12_53 +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc17: type = type_literal [concrete = ] +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @Int(constants.%T, constants.%N) { +// CHECK:STDOUT: %T.patt.loc12_14.2 => constants.%T.patt +// CHECK:STDOUT: %T.loc12_14.1 => constants.%T +// CHECK:STDOUT: %pattern_type => constants.%pattern_type.51d +// CHECK:STDOUT: %N.patt.loc12_31.2 => constants.%N.patt +// CHECK:STDOUT: %N.loc12_31.1 => constants.%N +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: --- fail_prelude_as_class.carbon // CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %Core: type = class_type @Core [concrete] +// CHECK:STDOUT: %.Self.frozen: type = symbolic_binding .Self [symbolic_self] +// CHECK:STDOUT: %pattern_type.9a5: type = pattern_type type [concrete] +// CHECK:STDOUT: %T.patt: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic] +// CHECK:STDOUT: %T: type = symbolic_binding T, 0 [symbolic] +// CHECK:STDOUT: %pattern_type.51d: type = pattern_type %T [symbolic] +// CHECK:STDOUT: %_.patt: %pattern_type.51d = symbolic_binding_pattern _, 1 [symbolic] +// CHECK:STDOUT: %_: %T = symbolic_binding _, 1 [symbolic] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete] +// CHECK:STDOUT: %.469: Core.Form = init_form %empty_struct_type [concrete] +// CHECK:STDOUT: %pattern_type.a96: type = pattern_type %empty_struct_type [concrete] +// CHECK:STDOUT: %return.param_patt: %pattern_type.a96 = out_param_pattern [concrete] +// CHECK:STDOUT: %return.patt: %pattern_type.a96 = return_slot_pattern %return.param_patt, %empty_struct_type [concrete] +// CHECK:STDOUT: %Core.Int.type: type = fn_type @Core.Int [concrete] +// CHECK:STDOUT: %Core.Int: %Core.Int.type = struct_value () [concrete] +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [concrete] +// CHECK:STDOUT: %n.patt: %pattern_type.9a5 = value_binding_pattern n [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .r#Core = %Core.decl +// CHECK:STDOUT: .n = %n +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.decl: type = class_decl @Core [concrete = constants.%Core] {} {} +// CHECK:STDOUT: %.loc12: type = type_literal type [concrete = type] +// CHECK:STDOUT: %n: type = wrapper_binding n, @__global_init.%.loc12 [concrete = ] +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %n.patt: %pattern_type.9a5 = value_binding_pattern n [concrete = constants.%n.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Core { +// CHECK:STDOUT: %Core.Int.decl: %Core.Int.type = fn_decl @Core.Int [concrete = constants.%Core.Int] { +// CHECK:STDOUT: %T.patt.loc7_11.1: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_11.2 (constants.%T.patt)] +// CHECK:STDOUT: %_.patt.loc7_28.1: @Core.Int.%pattern_type (%pattern_type.51d) = symbolic_binding_pattern _, 1 [symbolic = %_.patt.loc7_28.2 (constants.%_.patt)] +// CHECK:STDOUT: %return.param_patt: %pattern_type.a96 = out_param_pattern [concrete = constants.%return.param_patt] +// CHECK:STDOUT: %return.patt: %pattern_type.a96 = return_slot_pattern %return.param_patt, %.loc7_37.2 [concrete = constants.%return.patt] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %.loc7_37.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc7_37.2: type = converted %.loc7_37.1, constants.%empty_struct_type [concrete = constants.%empty_struct_type] +// CHECK:STDOUT: %.loc7_37.3: Core.Form = init_form %.loc7_37.2 [concrete = constants.%.469] +// CHECK:STDOUT: %.loc7_13.1: type = splice_block %.loc7_13.2 [concrete = type] { +// CHECK:STDOUT: %.Self.frozen.loc7_11: type = symbolic_binding .Self [symbolic_self = constants.%.Self.frozen] +// CHECK:STDOUT: %.loc7_13.2: type = type_literal type [concrete = type] +// CHECK:STDOUT: } +// CHECK:STDOUT: %T.loc7_11.2: type = symbolic_binding T, 0 [symbolic = %T.loc7_11.1 (constants.%T)] +// CHECK:STDOUT: %.loc7_30: type = splice_block %T.ref [symbolic = %T.loc7_11.1 (constants.%T)] { +// CHECK:STDOUT: %.Self.frozen.loc7_28: type = symbolic_binding .Self [symbolic_self = constants.%.Self.frozen] +// CHECK:STDOUT: %T.ref: type = name_ref T, %T.loc7_11.2 [symbolic = %T.loc7_11.1 (constants.%T)] +// CHECK:STDOUT: } +// CHECK:STDOUT: %_.loc7_28.2: @Core.Int.%T.loc7_11.1 (%T) = symbolic_binding _, 1 [symbolic = %_.loc7_28.1 (constants.%_)] +// CHECK:STDOUT: %return.param: ref %empty_struct_type = out_param call_param0 +// CHECK:STDOUT: %return: ref %empty_struct_type = return_slot %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %complete_type: = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type] +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%Core +// CHECK:STDOUT: .Int = %Core.Int.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: generic fn @Core.Int(%T.loc7_11.2: type, %_.loc7_28.2: @Core.Int.%T.loc7_11.1 (%T)) { +// CHECK:STDOUT: %T.patt.loc7_11.2: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc7_11.2 (constants.%T.patt)] +// CHECK:STDOUT: %T.loc7_11.1: type = symbolic_binding T, 0 [symbolic = %T.loc7_11.1 (constants.%T)] +// CHECK:STDOUT: %pattern_type: type = pattern_type %T.loc7_11.1 [symbolic = %pattern_type (constants.%pattern_type.51d)] +// CHECK:STDOUT: %_.patt.loc7_28.2: @Core.Int.%pattern_type (%pattern_type.51d) = symbolic_binding_pattern _, 1 [symbolic = %_.patt.loc7_28.2 (constants.%_.patt)] +// CHECK:STDOUT: %_.loc7_28.1: @Core.Int.%T.loc7_11.1 (%T) = symbolic_binding _, 1 [symbolic = %_.loc7_28.1 (constants.%_)] +// CHECK:STDOUT: +// CHECK:STDOUT: !definition: +// CHECK:STDOUT: +// CHECK:STDOUT: fn() -> out %return.param: %empty_struct_type { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc7_49.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc7_49.2: init %empty_struct_type = struct_init () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc7_50: init %empty_struct_type = converted %.loc7_49.1, %.loc7_49.2 [concrete = constants.%empty_struct] +// CHECK:STDOUT: return %.loc7_50 +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc12: type = type_literal [concrete = ] +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @Core.Int(constants.%T, constants.%_) { +// CHECK:STDOUT: %T.patt.loc7_11.2 => constants.%T.patt +// CHECK:STDOUT: %T.loc7_11.1 => constants.%T +// CHECK:STDOUT: %pattern_type => constants.%pattern_type.51d +// CHECK:STDOUT: %_.patt.loc7_28.2 => constants.%_.patt +// CHECK:STDOUT: %_.loc7_28.1 => constants.%_ +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/patterns/underscore.carbon b/toolchain/check/testdata/patterns/underscore.carbon index 478608b07026..00c6ef4555e3 100644 --- a/toolchain/check/testdata/patterns/underscore.carbon +++ b/toolchain/check/testdata/patterns/underscore.carbon @@ -84,9 +84,9 @@ class C { fn F() -> {} { let _: {} = {}; - // CHECK:STDERR: fail_use.carbon:[[@LINE+6]]:10: error: expected expression [ExpectedExpr] + // CHECK:STDERR: fail_use.carbon:[[@LINE+6]]:9: error: expected expression [ExpectedExpr] // CHECK:STDERR: - // CHECK:STDERR: fail_use.carbon:[[@LINE+4]]:10: error: `return` statements must end with a `;` [ExpectedStatementSemi] + // CHECK:STDERR: fail_use.carbon:[[@LINE+4]]:9: error: `return` statements must end with a `;` [ExpectedStatementSemi] // CHECK:STDERR: // CHECK:STDERR: fail_use.carbon:[[@LINE+2]]:10: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: diff --git a/toolchain/diagnostics/diagnostic.h b/toolchain/diagnostics/diagnostic.h index 1ace7da36dc8..074c740ea028 100644 --- a/toolchain/diagnostics/diagnostic.h +++ b/toolchain/diagnostics/diagnostic.h @@ -163,7 +163,11 @@ struct Loc { int32_t column_number = -1; // The number of bytes the location covers in `line`, starting at - // `column_number`. Should always be at least 1. + // `column_number`. + // + // Zero names the point just before `column_number`, where something missing + // would be written. That point can be just past the end of `line`, so + // `column_number` can be one more than the line's length. // // This is the range underlined in the source snippet. A message's own // location is a point that an editor puts a cursor on, so where a range was diff --git a/toolchain/diagnostics/mocks.h b/toolchain/diagnostics/mocks.h index c21dc57609cf..22d20dc4adb2 100644 --- a/toolchain/diagnostics/mocks.h +++ b/toolchain/diagnostics/mocks.h @@ -98,6 +98,39 @@ inline auto IsDiagnostic( testing::Field("labels", &Diagnostics::Diagnostic::labels, labels)); } +// The start and length expected of a location, in `IsDiagnosticWithRange`. +struct ExpectedLoc { + testing::Matcher line_number; + testing::Matcher column_number; + testing::Matcher length; +}; + +// Matches a diagnostic with one message and one range attached to it with no +// text, checking the lengths of the message's location and of the range as +// well as their starts. +inline auto IsDiagnosticWithRange(testing::Matcher kind, + testing::Matcher level, + ExpectedLoc loc, + testing::Matcher message, + ExpectedLoc range) + -> testing::Matcher { + return IsDiagnostic( + level, + testing::AllOf( + IsDiagnosticMessage(kind, level, loc.line_number, loc.column_number, + message), + testing::Field( + "loc", &Diagnostics::Message::loc, + testing::Field("length", &Diagnostics::Loc::length, loc.length))), + testing::IsEmpty(), + testing::ElementsAre(testing::AllOf( + IsDiagnosticLabel(Diagnostics::LabelCategory::Primary, + range.line_number, range.column_number, ""), + testing::Field("loc", &Diagnostics::Label::loc, + testing::Field("length", &Diagnostics::Loc::length, + range.length))))); +} + inline auto IsSingleDiagnostic(testing::Matcher kind, testing::Matcher level, testing::Matcher line_number, @@ -110,6 +143,24 @@ inline auto IsSingleDiagnostic(testing::Matcher kind, testing::IsEmpty(), testing::IsEmpty()); } +// Matches a diagnostic with one message, located at the point just before +// `column_number`, and nothing attached to it. +inline auto IsPointDiagnostic(testing::Matcher kind, + testing::Matcher level, + testing::Matcher line_number, + testing::Matcher column_number, + testing::Matcher message) + -> testing::Matcher { + return IsDiagnostic( + level, + testing::AllOf( + IsDiagnosticMessage(kind, level, line_number, column_number, message), + testing::Field( + "loc", &Diagnostics::Message::loc, + testing::Field("length", &Diagnostics::Loc::length, 0))), + testing::IsEmpty(), testing::IsEmpty()); +} + } // namespace Carbon::Testing namespace Carbon::Diagnostics { diff --git a/toolchain/diagnostics/testdata/fail_recovery_demo.carbon b/toolchain/diagnostics/testdata/fail_recovery_demo.carbon index 385a6dbaf6f4..f3a220f8a88f 100644 --- a/toolchain/diagnostics/testdata/fail_recovery_demo.carbon +++ b/toolchain/diagnostics/testdata/fail_recovery_demo.carbon @@ -14,46 +14,45 @@ // A tour of how a missing token is drawn, which is the case where nothing in // the source is wrong: what the reader needs is not a range to look at but a // place to put something. See /toolchain/docs/diagnostics_rendering.md for -// the rendering. +// the rendering and /toolchain/docs/diagnostics.md for the convention. // -// The rule is that the message is located at the token the parser found where -// the missing one should be, which is the first token after the gap. That token -// is underlined like any other range, so the place to put the missing token is -// its left edge. When it is the end of the file, the message is located just -// past the last token, which covers no text and is drawn as a lone `┳`. +// The rule is that the message is located at the point where the missing +// token would be inserted, which covers no text and is drawn as a lone `┳`. A +// token that goes after another is inserted just past it, even when the next +// token is on a later line or is the end of the file. An opening delimiter goes +// against the token after it when that token is on the same line. // // This is a parse-phase tour, so nothing here reaches checking. What each line // is for: // -// - `fn Paint`: a separator missing between two elements of a list, located at -// the first token of the element after the gap. -// - `if true`: an opening delimiter missing after its introducer, located at -// the token after the introducer. +// - `fn Paint`: a separator missing between two elements of a list, located +// just past the element it goes after. +// - `if true`: an opening delimiter missing after its introducer, located just +// before the token it goes against. // - `var n`: the lexer inserting a `)` to recover, which the label reports, // because everything reported after that point is about that reading of the // file rather than about what was written. -// - `class Widget`: a `;` missing at the end of a declaration, which is the -// case where the next token is the end of the file, so the message is drawn -// as a point just past `Widget`. This is last in the file because recovery -// skips to what looks like the end of the declaration, which consumes -// everything after it. +// - `class Widget`: a `;` missing at the end of a declaration, located just +// past the last token of the declaration. This is last in the file because +// recovery skips to what looks like the end of the declaration, which +// consumes everything after it. It is also the case where the next token is +// the end of the file. library "recovery_demo"; -// CHECK:STDERR: ╭─┤ fail_recovery_demo.carbon:[[@LINE+7]]:17 -// CHECK:STDERR: -> 50 │ fn Paint(a: i32 b: i32); -// CHECK:STDERR: · ┳ -// CHECK:STDERR: · ┗━━┫ error: expected `,` or `)` -// CHECK:STDERR: · ┃ [UnexpectedTokenAfterListElement] +// CHECK:STDERR: ╭─┤ fail_recovery_demo.carbon:[[@LINE+6]]:16 +// CHECK:STDERR: -> 49 │ fn Paint(a: i32 b: i32); +// CHECK:STDERR: · ┳ +// CHECK:STDERR: · ┗━━┫ error: expected `,` or `)` [UnexpectedTokenAfterListElement] // CHECK:STDERR: ╰──── // CHECK:STDERR: fn Paint(a: i32 b: i32); fn Blend() { // CHECK:STDERR: ╭─┤ fail_recovery_demo.carbon:[[@LINE+6]]:6 - // CHECK:STDERR: -> 59 │ if true { } - // CHECK:STDERR: · ━━┳━ - // CHECK:STDERR: · ┗━━┫ error: expected `(` after `if` [ExpectedParenAfter] + // CHECK:STDERR: -> 58 │ if true { } + // CHECK:STDERR: · ┳ + // CHECK:STDERR: · ┗━━┫ error: expected `(` after `if` [ExpectedParenAfter] // CHECK:STDERR: ╰──── // CHECK:STDERR: if true { } @@ -61,7 +60,7 @@ fn Blend() { fn Tint() { // CHECK:STDERR: ╭─┤ fail_recovery_demo.carbon:[[@LINE+9]]:16 - // CHECK:STDERR: -> 72 │ var n: i32 = (1; + // CHECK:STDERR: -> 71 │ var n: i32 = (1; // CHECK:STDERR: · ┳ ┬ // CHECK:STDERR: · ┗━━━━┫ error: opening symbol without a corresponding closing // CHECK:STDERR: · │ ┃ symbol @@ -73,7 +72,7 @@ fn Tint() { } // CHECK:STDERR: ╭─┤ fail_recovery_demo.carbon:[[@LINE+8]]:13 -// CHECK:STDERR: -> 83 │ class Widget +// CHECK:STDERR: -> 82 │ class Widget // CHECK:STDERR: · ┳ // CHECK:STDERR: · ┗━━┫ error: `class` declarations must either end with a `;` or have // CHECK:STDERR: · ┃ a `{ ... }` block for a definition diff --git a/toolchain/docs/diagnostics.md b/toolchain/docs/diagnostics.md index 622a394b6b01..53858c23ef29 100644 --- a/toolchain/docs/diagnostics.md +++ b/toolchain/docs/diagnostics.md @@ -20,6 +20,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Diagnostic registry](#diagnostic-registry) - [CARBON_DIAGNOSTIC placement](#carbon_diagnostic-placement) - [Choosing what a label marks](#choosing-what-a-label-marks) +- [Locating lexer and parser diagnostics](#locating-lexer-and-parser-diagnostics) - [Diagnostic parameter types](#diagnostic-parameter-types) - [Diagnostic message style guide](#diagnostic-message-style-guide) - [Alternatives considered](#alternatives-considered) @@ -378,6 +379,47 @@ Until a diagnostic is migrated its message location supplies the range, which is usually the one column an editor would put a cursor on and rarely an extent worth looking at. +## Locating lexer and parser diagnostics + +A lexer or parser diagnostic is about text the developer is about to edit, so +its message is located where they would put the cursor to start fixing it: + +- Something missing is located at the point where it would be inserted. A + point covers no text: it is a `Diagnostics::Loc` with a `length` of zero. A + missing token is inserted just past the token before it, so a missing `;` + at the end of a line is located at the end of that line rather than at the + first token of the next. An opening bracket is inserted against the token + after it when that token is on the same line, so `expected braced code + block` in `if (x) return;` is located just before `return`. + +- Something present but wrong is located at the tokens it covers. A word + that can't be a name, such as a keyword, in a name's position and followed + by a token that can follow the name, is taken as the name: the message + covers it and suggests `r#`. + +- Something missing at the start of a construct is located at the token + found in its place. The token before the point belongs to another + construct, possibly on another line, so an expression statement that starts + with `=` is reported at the `=` rather than at the end of the statement + before it. + +- Inside a token, the lexer locates the message at the point to fix and + attaches the range of what is malformed, which the snippet underlines + instead. An unterminated string literal is located where its terminator + goes and underlines the literal; a `\x` escape without two digits is + located at the digit that is missing or wrong and underlines the escape. + +In the lexer, a location is a `Lex::SourceLoc`. A `const char*` into the source +buffer converts to the one character it points at, `SourceLoc::Before` makes a +point, and `SourceLoc::Range` makes any other range. A range is never made from +a bare `const char*`, because converting one to `llvm::StringRef` scans for a +null byte, and a source buffer isn't guaranteed to have one. + +In the parser, a location is a `Parse::DiagnosticLoc`. A token converts to one, +`DiagnosticLoc::Tokens` covers a run of tokens, and `Before` and `After` make +the points on either side of a token. `Context::PointAfterPreviousToken` and +`Context::OpeningBracketInsertionPoint` locate most missing tokens. + ## Diagnostic parameter types Diagnostic parameters should have informative types. We rely on three different diff --git a/toolchain/language_server/testdata/document_symbol/decl_with_parse_error.carbon b/toolchain/language_server/testdata/document_symbol/decl_with_parse_error.carbon index 612b937ab37e..3b4b88402c5d 100644 --- a/toolchain/language_server/testdata/document_symbol/decl_with_parse_error.carbon +++ b/toolchain/language_server/testdata/document_symbol/decl_with_parse_error.carbon @@ -78,12 +78,12 @@ fn G() {} // CHECK:STDOUT: "message": "`fn` declarations must either end with a `;` or have a `{ ... }` block for a definition", // CHECK:STDOUT: "range": { // CHECK:STDOUT: "end": { -// CHECK:STDOUT: "character": 2, -// CHECK:STDOUT: "line": 2 +// CHECK:STDOUT: "character": 4, +// CHECK:STDOUT: "line": 0 // CHECK:STDOUT: }, // CHECK:STDOUT: "start": { -// CHECK:STDOUT: "character": 0, -// CHECK:STDOUT: "line": 2 +// CHECK:STDOUT: "character": 4, +// CHECK:STDOUT: "line": 0 // CHECK:STDOUT: } // CHECK:STDOUT: }, // CHECK:STDOUT: "severity": 1, diff --git a/toolchain/lex/BUILD b/toolchain/lex/BUILD index 7df9770d07ae..82285590964c 100644 --- a/toolchain/lex/BUILD +++ b/toolchain/lex/BUILD @@ -47,6 +47,7 @@ cc_library( srcs = ["helpers.cpp"], hdrs = ["helpers.h"], deps = [ + ":source_loc", "//toolchain/diagnostics:emitter", "@llvm-project//llvm:Support", ], @@ -57,6 +58,7 @@ cc_library( testonly = 1, hdrs = ["test_helpers.h"], deps = [ + ":source_loc", "//common:check", "//common:string_helpers", "//toolchain/diagnostics:emitter", @@ -72,6 +74,7 @@ cc_library( deps = [ ":character_set", ":helpers", + ":source_loc", "//common:check", "//toolchain/base:int", "//toolchain/diagnostics:emitter", @@ -131,6 +134,7 @@ cc_library( deps = [ ":character_set", ":helpers", + ":source_loc", ":token_info", "//common:check", "//toolchain/diagnostics:emitter", @@ -191,6 +195,7 @@ cc_library( ":helpers", ":mismatched_brackets", ":numeric_literal", + ":source_loc", ":string_literal", ":token_index", ":token_info", @@ -308,6 +313,15 @@ cc_library( alwayslink = 1, ) +cc_library( + name = "source_loc", + hdrs = ["source_loc.h"], + deps = [ + "//common:check", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "token_index", hdrs = ["token_index.h"], @@ -337,6 +351,7 @@ cc_library( ":character_set", ":helpers", ":numeric_literal", + ":source_loc", ":string_literal", ":token_index", ":token_info", diff --git a/toolchain/lex/helpers.cpp b/toolchain/lex/helpers.cpp index 6063679b0aae..9f74583621b6 100644 --- a/toolchain/lex/helpers.cpp +++ b/toolchain/lex/helpers.cpp @@ -6,7 +6,7 @@ namespace Carbon::Lex { -auto CanLexInt(Diagnostics::Emitter& emitter, llvm::StringRef text) +auto CanLexInt(Diagnostics::Emitter& emitter, llvm::StringRef text) -> bool { // Integer parsing has poor scaling characteristics for extremely large digit // amounts. We've done some performance work on this, but this limit exists to @@ -21,7 +21,9 @@ auto CanLexInt(Diagnostics::Emitter& emitter, llvm::StringRef text) "found a sequence of {0} digits, which is greater than the " "limit of {1}", size_t, size_t); - emitter.Emit(text.begin(), TooManyDigits, text.size(), DigitLimit); + emitter.Build(text.begin(), TooManyDigits, text.size(), DigitLimit) + .Attach(SourceLoc::Range(text)) + .Emit(); return false; } return true; diff --git a/toolchain/lex/helpers.h b/toolchain/lex/helpers.h index 3811cb7a871a..9b68fc3059c6 100644 --- a/toolchain/lex/helpers.h +++ b/toolchain/lex/helpers.h @@ -6,12 +6,13 @@ #define CARBON_TOOLCHAIN_LEX_HELPERS_H_ #include "toolchain/diagnostics/emitter.h" +#include "toolchain/lex/source_loc.h" namespace Carbon::Lex { // Should guard calls to getAsInteger due to performance issues with large // integers. Emits an error if the text cannot be lexed. -auto CanLexInt(Diagnostics::Emitter& emitter, llvm::StringRef text) +auto CanLexInt(Diagnostics::Emitter& emitter, llvm::StringRef text) -> bool; } // namespace Carbon::Lex diff --git a/toolchain/lex/lex.cpp b/toolchain/lex/lex.cpp index 21caf0b560ee..92e3e5aa58a5 100644 --- a/toolchain/lex/lex.cpp +++ b/toolchain/lex/lex.cpp @@ -24,6 +24,7 @@ #include "toolchain/lex/helpers.h" #include "toolchain/lex/mismatched_brackets.h" #include "toolchain/lex/numeric_literal.h" +#include "toolchain/lex/source_loc.h" #include "toolchain/lex/string_literal.h" #include "toolchain/lex/token_index.h" #include "toolchain/lex/token_info.h" @@ -259,7 +260,7 @@ class [[clang::internal_linkage]] Lexer { Diagnostics::ErrorTrackingConsumer consumer_; - TokenizedBuffer::SourcePointerDiagnosticEmitter emitter_; + TokenizedBuffer::SourceLocDiagnosticEmitter emitter_; TokenizedBuffer::TokenDiagnosticEmitter token_emitter_; }; @@ -1254,7 +1255,8 @@ auto Lexer::LexComment(llvm::StringRef source_text, ssize_t& position) -> void { } CARBON_DIAGNOSTIC(NoWhitespaceAfterCommentIntroducer, Error, "whitespace is required after '//'"); - emitter_.Emit(comment_text.begin() + 2, NoWhitespaceAfterCommentIntroducer); + emitter_.Emit(SourceLoc::Before(comment_text.begin() + 2), + NoWhitespaceAfterCommentIntroducer); // We use this to tweak the lexing of blocks below. is_valid_after_slashes = false; @@ -1458,13 +1460,18 @@ auto Lexer::LexNumericLiteral(llvm::StringRef source_text, ssize_t& position) } } -static auto DiagnoseUnterminatedString( - Diagnostics::Emitter& emitter, const StringLiteral& literal, - bool is_char) -> void { +static auto DiagnoseUnterminatedString(Diagnostics::Emitter& emitter, + const StringLiteral& literal, + bool is_char) -> void { CARBON_DIAGNOSTIC(UnterminatedString, Error, "{0:character|string} literal is missing a terminator", Diagnostics::BoolAsSelect); - emitter.Emit(literal.text().begin(), UnterminatedString, is_char); + // The terminator goes where the literal stops. + emitter + .Build(SourceLoc::Before(literal.text().end()), UnterminatedString, + is_char) + .Attach(SourceLoc::Range(literal.text())) + .Emit(); } auto Lexer::LexStringLiteral(llvm::StringRef source_text, ssize_t& position) @@ -1504,7 +1511,9 @@ auto Lexer::LexStringLiteral(llvm::StringRef source_text, ssize_t& position) "invalid multi-line string literal introducer; a file " "type indicator may not contain `'`, `#`, or `\"`, and " "the content must begin on a new line"); - emitter_.Emit(literal->text().begin(), MultiLineStringInvalidIntroducer); + emitter_.Build(literal->text().begin(), MultiLineStringInvalidIntroducer) + .Attach(SourceLoc::Range(literal->text())) + .Emit(); return lex_as_error(); } @@ -1533,6 +1542,10 @@ auto Lexer::LexStringLiteral(llvm::StringRef source_text, ssize_t& position) } } + // TODO: Recover from an unterminated literal in a way that lets parsing + // continue as if it were terminated. The error token makes the parser also + // report the enclosing construct as incomplete, for example a missing `;` + // after `var s: str = "abc`. if (!literal->is_terminated()) { DiagnoseUnterminatedString(emitter_, *literal, /*is_char=*/false); return lex_as_error(); @@ -1659,8 +1672,11 @@ auto Lexer::LexWordAsTypeLiteralToken(llvm::StringRef word, int32_t byte_offset) "found a type literal with a bit width using {0} digits, " "which is greater than the limit of {1}", size_t, size_t); - emitter_.Emit(word.begin() + 1, TooManyTypeBitWidthDigits, suffix.size(), - DigitLimit); + emitter_ + .Build(word.begin() + 1, TooManyTypeBitWidthDigits, suffix.size(), + DigitLimit) + .Attach(SourceLoc::Range(suffix)) + .Emit(); return LexTokenWithPayload(TokenKind::Error, word.size(), byte_offset); } @@ -1720,7 +1736,7 @@ auto Lexer::LexWordAsDollarIntLiteralToken(llvm::StringRef word, if (word.size() < 2) { CARBON_DIAGNOSTIC(DollarIntLiteralMissingNumber, Error, "expected digits after `$`"); - emitter_.Emit(word.begin() + 1, DollarIntLiteralMissingNumber); + emitter_.Emit(SourceLoc::Before(word.end()), DollarIntLiteralMissingNumber); return LexTokenWithPayload(TokenKind::Error, word.size(), byte_offset); } if (word[1] == '0' && word.size() > 2) { @@ -1866,7 +1882,9 @@ auto Lexer::LexError(llvm::StringRef source_text, ssize_t& position) LexTokenWithPayload(TokenKind::Error, error_text.size(), position); CARBON_DIAGNOSTIC(UnrecognizedCharacters, Error, "encountered unrecognized characters while parsing"); - emitter_.Emit(error_text.begin(), UnrecognizedCharacters); + emitter_.Build(error_text.begin(), UnrecognizedCharacters) + .Attach(SourceLoc::Range(error_text)) + .Emit(); position += error_text.size(); return token; @@ -2383,12 +2401,12 @@ static auto TokenEndPosition(const TokenizedBuffer& buffer, TokenIndex token) return TokenStartPosition(buffer, token) + buffer.GetTokenText(token).size(); } -// Where in the source a bracket that `correction` proposes would be written: -// just past the end of the token it goes after, rather than at the start of the -// token it goes before. For `f(x` on one line and `;` on the next, that points -// the suggestion at the position directly after the `x`, where the `)` belongs, -// instead of down at the `;`. Where the two tokens are separated, the bracket -// is then placed on the side that matches how it would be written. +// Where a user would insert the bracket that `correction` proposes: just past +// the end of the token it goes after, rather than at the start of the token it +// goes before. For `f(x` on one line and `;` on the next, that points the +// suggestion at the position directly after the `x`, where the `)` belongs, +// instead of down at the `;`. An opening bracket and a `}` closing a multi-line +// scope are inserted elsewhere, as described below. static auto BracketInsertionPosition(const TokenizedBuffer& buffer, const BracketCorrection& correction) -> const char* { @@ -2427,9 +2445,12 @@ static auto BracketInsertionPosition(const TokenizedBuffer& buffer, buffer.GetIndentColumnNumber(open_line) - 1); } } else if (correction.fix_token_kind.is_opening_symbol()) { - // An opening bracket binds to what comes after it, so it belongs on the far - // side of any space separating it from the token it follows. - pos = skip_spaces(pos, NoLimit); + // An opening bracket is inserted against the token after it when that token + // is on the same line. + TokenIndex insert_before(insert_after.index + 1); + if (buffer.GetEndLoc(insert_after).first == buffer.GetLine(insert_before)) { + pos = TokenStartPosition(buffer, insert_before); + } } return pos; } @@ -2470,8 +2491,9 @@ auto Lexer::DiagnoseAndFixMismatchedBrackets() -> void { // on the bracket rather than suggest one of them. fixes.ReplaceWithError(correction.diagnostic_token_index); } else { - builder.Attach(BracketInsertionPosition(buffer_, correction), - PossiblyMissingBracketHere, correction.fix_token_kind); + builder.Attach( + SourceLoc::Before(BracketInsertionPosition(buffer_, correction)), + PossiblyMissingBracketHere, correction.fix_token_kind); insertion_ids[correction_index] = correction.fix_action == BracketFixAction::InsertBefore ? fixes.InsertBefore(correction.fix_token_index, diff --git a/toolchain/lex/numeric_literal.cpp b/toolchain/lex/numeric_literal.cpp index 0a72ff28a375..2ea9468fbb98 100644 --- a/toolchain/lex/numeric_literal.cpp +++ b/toolchain/lex/numeric_literal.cpp @@ -93,7 +93,7 @@ auto NumericLiteral::Lex(llvm::StringRef source_text, // either diagnosing or extracting its meaning. class NumericLiteral::Parser { public: - Parser(Diagnostics::Emitter& emitter, NumericLiteral literal); + Parser(Diagnostics::Emitter& emitter, NumericLiteral literal); auto IsInt() -> bool { return literal_.radix_point_ == static_cast(literal_.text_.size()); @@ -129,7 +129,7 @@ class NumericLiteral::Parser { auto CheckFractionalPart() -> bool; auto CheckExponentPart() -> bool; - Diagnostics::Emitter& emitter_; + Diagnostics::Emitter& emitter_; NumericLiteral literal_; // The radix of the literal: 2, 10, or 16, for a prefix of '0b', no prefix, @@ -152,7 +152,7 @@ class NumericLiteral::Parser { bool exponent_is_negative_ = false; }; -NumericLiteral::Parser::Parser(Diagnostics::Emitter& emitter, +NumericLiteral::Parser::Parser(Diagnostics::Emitter& emitter, NumericLiteral literal) : emitter_(emitter), literal_(literal) { int_part_ = literal.text_.substr(0, literal.radix_point_); @@ -406,7 +406,9 @@ auto NumericLiteral::Parser::CheckDigitSequence(llvm::StringRef text, if (num_digit_separators == static_cast(text.size())) { CARBON_DIAGNOSTIC(EmptyDigitSequence, Error, "empty digit sequence in numeric literal"); - emitter_.Emit(text.begin(), EmptyDigitSequence); + emitter_.Build(SourceLoc::Before(text.begin()), EmptyDigitSequence) + .Attach(SourceLoc::Range(text)) + .Emit(); return {.ok = false}; } @@ -423,7 +425,9 @@ auto NumericLiteral::Parser::CheckLeadingZero() -> bool { int_part_ != "0") { CARBON_DIAGNOSTIC(UnknownBaseSpecifier, Error, "unknown base specifier in numeric literal"); - emitter_.Emit(int_part_.begin(), UnknownBaseSpecifier); + emitter_.Build(int_part_.begin(), UnknownBaseSpecifier) + .Attach(SourceLoc::Range(int_part_)) + .Emit(); return false; } return true; @@ -448,8 +452,11 @@ auto NumericLiteral::Parser::CheckFractionalPart() -> bool { InvalidRealLiteralRadix, Error, "{0:=2:binary|=8:octal} real number literals are not supported", Diagnostics::IntAsSelect); - emitter_.Emit(literal_.text_.begin() + literal_.radix_point_, - InvalidRealLiteralRadix, static_cast(radix_)); + emitter_ + .Build(literal_.text_.begin() + literal_.radix_point_, + InvalidRealLiteralRadix, static_cast(radix_)) + .Attach(SourceLoc::Range(literal_.text_)) + .Emit(); // Carry on and parse the real literal anyway. } @@ -483,7 +490,7 @@ auto NumericLiteral::Parser::CheckExponentPart() -> bool { // Parse the token and compute its value. auto NumericLiteral::ComputeValue( - Diagnostics::Emitter& emitter) const -> Value { + Diagnostics::Emitter& emitter) const -> Value { Parser parser(emitter, *this); if (!parser.Check()) { diff --git a/toolchain/lex/numeric_literal.h b/toolchain/lex/numeric_literal.h index be0896627edf..19faf46a98d4 100644 --- a/toolchain/lex/numeric_literal.h +++ b/toolchain/lex/numeric_literal.h @@ -11,6 +11,7 @@ #include "llvm/ADT/APInt.h" #include "llvm/ADT/StringRef.h" #include "toolchain/diagnostics/emitter.h" +#include "toolchain/lex/source_loc.h" namespace Carbon::Lex { @@ -52,7 +53,7 @@ class NumericLiteral { // Compute the value of the token, if possible. Emit diagnostics to the given // emitter if the token is not valid. - auto ComputeValue(Diagnostics::Emitter& emitter) const -> Value; + auto ComputeValue(Diagnostics::Emitter& emitter) const -> Value; // Get the text corresponding to this literal. auto text() const -> llvm::StringRef { return text_; } diff --git a/toolchain/lex/numeric_literal_benchmark.cpp b/toolchain/lex/numeric_literal_benchmark.cpp index 2f06756e8b4c..66216e4407a7 100644 --- a/toolchain/lex/numeric_literal_benchmark.cpp +++ b/toolchain/lex/numeric_literal_benchmark.cpp @@ -52,7 +52,7 @@ static void BM_Lex_IntDecimalN(benchmark::State& state) { static void BM_ComputeValue_Float(benchmark::State& state) { auto val = NumericLiteral::Lex("0.000001", true); CARBON_CHECK(val); - auto& emitter = Diagnostics::NullEmitter(); + auto& emitter = Diagnostics::NullEmitter(); for (auto _ : state) { val->ComputeValue(emitter); } @@ -60,7 +60,7 @@ static void BM_ComputeValue_Float(benchmark::State& state) { static void BM_ComputeValue_Int(benchmark::State& state) { auto val = NumericLiteral::Lex("1_234_567_890", true); - auto& emitter = Diagnostics::NullEmitter(); + auto& emitter = Diagnostics::NullEmitter(); CARBON_CHECK(val); for (auto _ : state) { val->ComputeValue(emitter); @@ -70,7 +70,7 @@ static void BM_ComputeValue_Int(benchmark::State& state) { static void BM_ComputeValue_IntDecimalN(benchmark::State& state) { std::string s = MakeIntString("", 10, state.range(0)); auto val = NumericLiteral::Lex(s, true); - auto& emitter = Diagnostics::NullEmitter(); + auto& emitter = Diagnostics::NullEmitter(); CARBON_CHECK(val); for (auto _ : state) { val->ComputeValue(emitter); @@ -80,7 +80,7 @@ static void BM_ComputeValue_IntDecimalN(benchmark::State& state) { static void BM_ComputeValue_IntBinaryN(benchmark::State& state) { std::string s = MakeIntString("0b", 2, state.range(0)); auto val = NumericLiteral::Lex(s, true); - auto& emitter = Diagnostics::NullEmitter(); + auto& emitter = Diagnostics::NullEmitter(); CARBON_CHECK(val); for (auto _ : state) { val->ComputeValue(emitter); @@ -90,7 +90,7 @@ static void BM_ComputeValue_IntBinaryN(benchmark::State& state) { static void BM_ComputeValue_IntOctalN(benchmark::State& state) { std::string s = MakeIntString("0o", 8, state.range(0)); auto val = NumericLiteral::Lex(s, true); - auto& emitter = Diagnostics::NullEmitter(); + auto& emitter = Diagnostics::NullEmitter(); CARBON_CHECK(val); for (auto _ : state) { val->ComputeValue(emitter); @@ -101,7 +101,7 @@ static void BM_ComputeValue_IntHexN(benchmark::State& state) { // 0 is in the middle so that it isn't truncated in parse. std::string s = MakeIntString("0x", 16, state.range(0)); auto val = NumericLiteral::Lex(s, true); - auto& emitter = Diagnostics::NullEmitter(); + auto& emitter = Diagnostics::NullEmitter(); CARBON_CHECK(val); for (auto _ : state) { val->ComputeValue(emitter); diff --git a/toolchain/lex/numeric_literal_fuzzer.cpp b/toolchain/lex/numeric_literal_fuzzer.cpp index 1040e0c355da..5b25c30adf6f 100644 --- a/toolchain/lex/numeric_literal_fuzzer.cpp +++ b/toolchain/lex/numeric_literal_fuzzer.cpp @@ -21,7 +21,7 @@ extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) { } volatile auto value = - token->ComputeValue(Diagnostics::NullEmitter()); + token->ComputeValue(Diagnostics::NullEmitter()); (void)value; return 0; } diff --git a/toolchain/lex/source_loc.h b/toolchain/lex/source_loc.h new file mode 100644 index 000000000000..337a9a624dd9 --- /dev/null +++ b/toolchain/lex/source_loc.h @@ -0,0 +1,55 @@ +// 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_LEX_SOURCE_LOC_H_ +#define CARBON_TOOLCHAIN_LEX_SOURCE_LOC_H_ + +#include "common/check.h" +#include "llvm/ADT/StringRef.h" + +namespace Carbon::Lex { + +// The location of a lexer diagnostic: a range of the source buffer. An empty +// range names the point between two characters, where something missing would +// be written. +// +// A `const char*` into the source buffer converts implicitly to the one +// character it points at, and any other range has to be made by a factory +// function. Converting a bare `const char*` to `llvm::StringRef` scans for a +// null byte, and a source buffer isn't guaranteed to have one. +class SourceLoc { + public: + // The character at `pos`, which must point into the source buffer. + // + // NOLINTNEXTLINE(google-explicit-constructor): A character is a location. + SourceLoc(const char* pos) : text_(pos, 1) {} + + // The range `text` covers, which must point into the source buffer. + static auto Range(llvm::StringRef text) -> SourceLoc { + return SourceLoc(text); + } + + // The range from `begin` up to `end`, both of which must point into the + // source buffer. + static auto Range(const char* begin, const char* end) -> SourceLoc { + CARBON_CHECK(begin <= end, "A range's end can't come before its start."); + return SourceLoc(llvm::StringRef(begin, end - begin)); + } + + // The point just before `pos`, which must point into the source buffer or + // just past its end. + static auto Before(const char* pos) -> SourceLoc { return Range(pos, pos); } + + // The source text this location covers. + auto text() const -> llvm::StringRef { return text_; } + + private: + explicit SourceLoc(llvm::StringRef text) : text_(text) {} + + llvm::StringRef text_; +}; + +} // namespace Carbon::Lex + +#endif // CARBON_TOOLCHAIN_LEX_SOURCE_LOC_H_ diff --git a/toolchain/lex/string_literal.cpp b/toolchain/lex/string_literal.cpp index 7974d4a79551..121e5f7a97ee 100644 --- a/toolchain/lex/string_literal.cpp +++ b/toolchain/lex/string_literal.cpp @@ -17,7 +17,7 @@ namespace Carbon::Lex { -using DiagnosticEmitter = Diagnostics::Emitter; +using DiagnosticEmitter = Diagnostics::Emitter; static constexpr char MultiLineIndicator[] = R"(''')"; static constexpr char DoubleQuotedMultiLineIndicator[] = R"(""")"; @@ -284,7 +284,9 @@ static auto CheckIndent(DiagnosticEmitter& emitter, llvm::StringRef text, ContentBeforeStringTerminator, Error, "only whitespace is permitted before the closing `'''` of a " "multi-line string"); - emitter.Emit(indent.end(), ContentBeforeStringTerminator); + emitter.Build(indent.end(), ContentBeforeStringTerminator) + .Attach(SourceLoc::Range(indent.end(), content.end())) + .Emit(); } return indent; @@ -302,7 +304,9 @@ static auto ExpandUnicodeEscapeSequence(DiagnosticEmitter& emitter, CARBON_DIAGNOSTIC(UnicodeEscapeTooLarge, Error, "code point specified by `\\u{{...}}` escape is greater " "than 0x10FFFF"); - emitter.Emit(digits.begin(), UnicodeEscapeTooLarge); + emitter.Build(digits.begin(), UnicodeEscapeTooLarge) + .Attach(SourceLoc::Range(digits)) + .Emit(); return false; } @@ -310,7 +314,9 @@ static auto ExpandUnicodeEscapeSequence(DiagnosticEmitter& emitter, CARBON_DIAGNOSTIC(UnicodeEscapeSurrogate, Error, "code point specified by `\\u{{...}}` escape is a " "surrogate character"); - emitter.Emit(digits.begin(), UnicodeEscapeSurrogate); + emitter.Build(digits.begin(), UnicodeEscapeSurrogate) + .Attach(SourceLoc::Range(digits)) + .Emit(); return false; } @@ -348,10 +354,18 @@ static auto AppendFrontOfContents(char*& buffer_cursor, // `result` string. `content` is the string content, starting from the first // character after the escape sequence introducer (for example, the `n` in // `\n`), and will be updated to remove the leading escape sequence. +// `escape_begin` points at the `\` that starts the introducer, which a raw +// string follows with its `#`s. static auto ExpandAndConsumeEscapeSequence(DiagnosticEmitter& emitter, + const char* escape_begin, llvm::StringRef& content, char*& buffer_cursor) -> void { CARBON_CHECK(!content.empty(), "should have escaped closing delimiter"); + // A diagnostic about the escape marks it from the `\` through as much of it + // as was consumed when the problem was found. + auto escape_through = [&](const char* end) { + return SourceLoc::Range(escape_begin, end); + }; char first = content.front(); content = content.drop_front(1); @@ -381,11 +395,13 @@ static auto ExpandAndConsumeEscapeSequence(DiagnosticEmitter& emitter, DecimalEscapeSequence, Error, "decimal digit follows `\\0` escape sequence. Use `\\x00` instead " "of `\\0` if the next character is a digit"); - emitter.Emit(content.begin(), DecimalEscapeSequence); + emitter.Build(escape_begin, DecimalEscapeSequence) + .Attach(escape_through(content.begin() + 1)) + .Emit(); return; } return; - case 'x': + case 'x': { if (content.size() >= 2 && IsUpperHexDigit(content[0]) && IsUpperHexDigit(content[1])) { AppendChar(buffer_cursor, static_cast(llvm::hexFromNibbles( @@ -396,8 +412,16 @@ static auto ExpandAndConsumeEscapeSequence(DiagnosticEmitter& emitter, CARBON_DIAGNOSTIC(HexadecimalEscapeMissingDigits, Error, "escape sequence `\\x` must be followed by two " "uppercase hexadecimal digits, for example `\\x0F`"); - emitter.Emit(content.begin(), HexadecimalEscapeMissingDigits); + // The digit that is missing or wrong is where the escape needs fixing. + const char* bad_digit = + content.begin() + + content.take_front(2).take_while(IsUpperHexDigit).size(); + emitter + .Build(SourceLoc::Before(bad_digit), HexadecimalEscapeMissingDigits) + .Attach(escape_through(bad_digit)) + .Emit(); break; + } case 'u': { llvm::StringRef remaining = content; if (remaining.consume_front("{")) { @@ -415,13 +439,21 @@ static auto ExpandAndConsumeEscapeSequence(DiagnosticEmitter& emitter, UnicodeEscapeMissingBracedDigits, Error, "escape sequence `\\u` must be followed by a braced sequence of " "uppercase hexadecimal digits, for example `\\u{{70AD}}`"); - emitter.Emit(content.begin(), UnicodeEscapeMissingBracedDigits); + // `remaining` starts at the part of `{...}` that is missing or wrong, + // which is where the escape needs fixing. + emitter + .Build(SourceLoc::Before(remaining.begin()), + UnicodeEscapeMissingBracedDigits) + .Attach(escape_through(remaining.begin())) + .Emit(); break; } default: CARBON_DIAGNOSTIC(UnknownEscapeSequence, Error, "unrecognized escape sequence `{0}`", char); - emitter.Emit(content.begin() - 1, UnknownEscapeSequence, first); + emitter.Build(escape_begin, UnknownEscapeSequence, first) + .Attach(escape_through(content.begin())) + .Emit(); break; } @@ -453,7 +485,14 @@ static auto ExpandEscapeSequencesAndRemoveIndent( MismatchedIndentInString, Error, "indentation does not match that of the closing `'''` in " "multi-line string literal"); - emitter.Emit(line_start, MismatchedIndentInString); + CARBON_DIAGNOSTIC_LABEL(ClosingDelimiterIndent, Info, + "closing `'''` is indented to here"); + auto diag = emitter.Build(line_start, MismatchedIndentInString); + // A line with no leading whitespace has no indent to mark. + if (contents.begin() != line_start) { + diag.Attach(SourceLoc::Range(line_start, contents.begin())); + } + diag.Attach(SourceLoc::Range(indent), ClosingDelimiterIndent).Emit(); } } @@ -500,13 +539,13 @@ static auto ExpandEscapeSequencesAndRemoveIndent( auto after_space = contents.find_if_not(IsHorizontalWhitespace); if (after_space == llvm::StringRef::npos || contents[after_space] != '\n') { - // TODO: Include the source range of the whitespace up to - // `contents.begin() + after_space` in the diagnostic. CARBON_DIAGNOSTIC( InvalidHorizontalWhitespaceInString, Error, "whitespace other than plain space must be expressed with an " "escape sequence in a string literal"); - emitter.Emit(contents.begin(), InvalidHorizontalWhitespaceInString); + emitter.Build(contents.begin(), InvalidHorizontalWhitespaceInString) + .Attach(SourceLoc::Range(contents.take_front(after_space))) + .Emit(); // Include the whitespace in the string contents for error recovery. AppendFrontOfContents(buffer_cursor, contents, after_space); } @@ -514,6 +553,7 @@ static auto ExpandEscapeSequencesAndRemoveIndent( continue; } + const char* escape_begin = contents.begin(); if (!contents.consume_front(escape)) { // This is not an escape sequence, just a raw `\`. AppendChar(buffer_cursor, contents.front()); @@ -528,7 +568,8 @@ static auto ExpandEscapeSequencesAndRemoveIndent( } // Handle this escape sequence. - ExpandAndConsumeEscapeSequence(emitter, contents, buffer_cursor); + ExpandAndConsumeEscapeSequence(emitter, escape_begin, contents, + buffer_cursor); buffer_last_escape = buffer_cursor; } } @@ -540,7 +581,7 @@ static auto IsControlCharacter(llvm::UTF32 c) -> bool { } auto StringLiteral::ComputeCharLiteralValue( - Diagnostics::Emitter& emitter) const + Diagnostics::Emitter& emitter) const -> std::optional { CARBON_DCHECK(kind_ == Kind::Char); CARBON_DCHECK(is_terminated_); @@ -548,7 +589,9 @@ auto StringLiteral::ComputeCharLiteralValue( if (hash_level_ != 0) { CARBON_DIAGNOSTIC(CharLiteralRaw, Error, "unexpected `#` before character literal"); - emitter.Emit(text_.begin(), CharLiteralRaw); + emitter.Build(text_.begin(), CharLiteralRaw) + .Attach(SourceLoc::Range(text_)) + .Emit(); } // Allocate a buffer sized to the content. Note it's possible this could be @@ -577,7 +620,10 @@ auto StringLiteral::ComputeCharLiteralValue( case llvm::conversionOK: { if (target_cursor == target) { CARBON_DIAGNOSTIC(CharLiteralEmpty, Error, "empty character literal"); - emitter.Emit(text_.begin(), CharLiteralEmpty); + // The character goes between the quotes. + emitter.Build(SourceLoc::Before(content_.begin()), CharLiteralEmpty) + .Attach(SourceLoc::Range(text_)) + .Emit(); return std::nullopt; } @@ -593,7 +639,9 @@ auto StringLiteral::ComputeCharLiteralValue( "control character in character literal; specify as " "escape sequence `\\u{{{0:X-2}}`", llvm::UTF32); - emitter.Emit(text_.begin(), CharLiteralControlCharacter, result); + emitter.Build(text_.begin(), CharLiteralControlCharacter, result) + .Attach(SourceLoc::Range(text_)) + .Emit(); return std::nullopt; } @@ -602,7 +650,9 @@ auto StringLiteral::ComputeCharLiteralValue( "escape sequence `\\x` in character literal; specify " "as escape sequence `\\u{{{0:X-2}}`", llvm::UTF32); - emitter.Emit(text_.begin(), CharLiteralHexEscape, result); + emitter.Build(text_.begin(), CharLiteralHexEscape, result) + .Attach(SourceLoc::Range(text_)) + .Emit(); return std::nullopt; } @@ -610,18 +660,24 @@ auto StringLiteral::ComputeCharLiteralValue( } case llvm::sourceExhausted: { CARBON_DIAGNOSTIC(CharLiteralUnderflow, Error, "incomplete UTF-8"); - emitter.Emit(text_.begin(), CharLiteralUnderflow); + emitter.Build(text_.begin(), CharLiteralUnderflow) + .Attach(SourceLoc::Range(text_)) + .Emit(); return std::nullopt; } case llvm::targetExhausted: { CARBON_DIAGNOSTIC(CharLiteralOverflow, Error, "too many characters"); - emitter.Emit(text_.begin(), CharLiteralOverflow); + emitter.Build(text_.begin(), CharLiteralOverflow) + .Attach(SourceLoc::Range(text_)) + .Emit(); return std::nullopt; } case llvm::sourceIllegal: { CARBON_DIAGNOSTIC(CharLiteralInvalidUTF8, Error, "invalid UTF-8 character"); - emitter.Emit(text_.begin(), CharLiteralInvalidUTF8); + emitter.Build(text_.begin(), CharLiteralInvalidUTF8) + .Attach(SourceLoc::Range(text_)) + .Emit(); return std::nullopt; } } @@ -637,7 +693,9 @@ auto StringLiteral::ComputeStringValue(llvm::BumpPtrAllocator& allocator, CARBON_DIAGNOSTIC( MultiLineStringWithDoubleQuotes, Error, "use `'''` delimiters for a multi-line string literal, not `\"\"\"`"); - emitter.Emit(text_.begin(), MultiLineStringWithDoubleQuotes); + emitter.Build(text_.begin(), MultiLineStringWithDoubleQuotes) + .Attach(SourceLoc::Range(text_.take_front(hash_level_ + 3))) + .Emit(); } llvm::StringRef indent = IsMultiLine(kind_) ? CheckIndent(emitter, text_, content_) diff --git a/toolchain/lex/string_literal.h b/toolchain/lex/string_literal.h index 00a6169a6191..1fc4b9a8b5b5 100644 --- a/toolchain/lex/string_literal.h +++ b/toolchain/lex/string_literal.h @@ -10,6 +10,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" #include "toolchain/diagnostics/emitter.h" +#include "toolchain/lex/source_loc.h" #include "toolchain/lex/token_info.h" namespace Carbon::Lex { @@ -41,7 +42,7 @@ class StringLiteral { // Expand any escape sequences and compute the resulting character. This // handles error recovery internally, but can return nullopt for an invalid // character. - auto ComputeCharLiteralValue(Diagnostics::Emitter& emitter) const + auto ComputeCharLiteralValue(Diagnostics::Emitter& emitter) const -> std::optional; // Expand any escape sequences in the given string literal and compute the @@ -51,7 +52,7 @@ class StringLiteral { // deal with, this can return the content directly. Otherwise, the allocator // will be used for the StringRef. auto ComputeStringValue(llvm::BumpPtrAllocator& allocator, - Diagnostics::Emitter& emitter) const + Diagnostics::Emitter& emitter) const -> llvm::StringRef; // Get the text corresponding to this literal. diff --git a/toolchain/lex/string_literal_benchmark.cpp b/toolchain/lex/string_literal_benchmark.cpp index 21c9e6f0db52..4155f396008c 100644 --- a/toolchain/lex/string_literal_benchmark.cpp +++ b/toolchain/lex/string_literal_benchmark.cpp @@ -99,7 +99,7 @@ static void BM_SimpleStringValue(benchmark::State& state, int size, x.append(terminator); for (auto _ : state) { StringLiteral::Lex(x)->ComputeStringValue( - allocator, Diagnostics::NullEmitter()); + allocator, Diagnostics::NullEmitter()); } } diff --git a/toolchain/lex/string_literal_fuzzer.cpp b/toolchain/lex/string_literal_fuzzer.cpp index 9558afcfe451..f222a2adf5f9 100644 --- a/toolchain/lex/string_literal_fuzzer.cpp +++ b/toolchain/lex/string_literal_fuzzer.cpp @@ -45,7 +45,7 @@ extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) { break; } - auto* null_emitter = &Diagnostics::NullEmitter(); + auto* null_emitter = &Diagnostics::NullEmitter(); if (literal->kind() == Lex::StringLiteral::Kind::Char) { volatile auto value = literal->ComputeCharLiteralValue(*null_emitter); (void)value; diff --git a/toolchain/lex/test_helpers.h b/toolchain/lex/test_helpers.h index 03b177e5d574..2bb4bf0ad4c8 100644 --- a/toolchain/lex/test_helpers.h +++ b/toolchain/lex/test_helpers.h @@ -12,12 +12,14 @@ #include "common/check.h" #include "common/string_helpers.h" #include "toolchain/diagnostics/emitter.h" +#include "toolchain/lex/source_loc.h" namespace Carbon::Testing { // A diagnostic converter for tests that lex a single token. Produces // locations such as "`12.5`:1:3" to refer to the third character in the token. -class SingleTokenDiagnosticEmitter : public Diagnostics::Emitter { +class SingleTokenDiagnosticEmitter + : public Diagnostics::Emitter { public: // Form a converter for a given token. The string provided here must refer // to the same character array that we are going to lex. @@ -27,8 +29,9 @@ class SingleTokenDiagnosticEmitter : public Diagnostics::Emitter { protected: // Implements `DiagnosticConverter::ConvertLoc`. - auto ConvertLoc(const char* pos, ContextFnT /*context_fn*/) const + auto ConvertLoc(Lex::SourceLoc loc, ContextFnT /*context_fn*/) const -> Diagnostics::ConvertedLoc override { + const char* pos = loc.text().begin(); CARBON_CHECK(StringRefContainsPointer(token_, pos), "invalid diagnostic location"); llvm::StringRef prefix = token_.take_front(pos - token_.begin()); @@ -37,7 +40,8 @@ class SingleTokenDiagnosticEmitter : public Diagnostics::Emitter { // On first line. return {.loc = {.line_number = 1, .column_number = - static_cast(pos - token_.begin() + 1)}, + static_cast(pos - token_.begin() + 1), + .length = static_cast(loc.text().size())}, .last_byte_offset = -1}; } else { // On second or subsequent lines. Note that the line number here is 2 @@ -46,7 +50,8 @@ class SingleTokenDiagnosticEmitter : public Diagnostics::Emitter { return { .loc = {.line_number = static_cast(before_last_newline.count('\n') + 2), - .column_number = static_cast(this_line.size() + 1)}, + .column_number = static_cast(this_line.size() + 1), + .length = static_cast(loc.text().size())}, .last_byte_offset = -1}; } } diff --git a/toolchain/lex/testdata/char_literals.carbon b/toolchain/lex/testdata/char_literals.carbon index a6e115c92fe1..4f91bc9907ca 100644 --- a/toolchain/lex/testdata/char_literals.carbon +++ b/toolchain/lex/testdata/char_literals.carbon @@ -53,7 +53,7 @@ '\xC3\xFF' // CHECK:STDOUT: - { index: 4, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "'\\xC3\\xFF'", has_leading_space: true } -// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:1: error: empty character literal [CharLiteralEmpty] +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:2: error: empty character literal [CharLiteralEmpty] // CHECK:STDERR: '' // CHECK:STDOUT: - { index: 5, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "''", has_leading_space: true } @@ -75,7 +75,7 @@ // CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:1: error: unexpected `#` before character literal [CharLiteralRaw] // CHECK:STDERR: -// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:1: error: empty character literal [CharLiteralEmpty] +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:4: error: empty character literal [CharLiteralEmpty] // CHECK:STDERR: ##''## // CHECK:STDOUT: - { index: 9, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "##''##", has_leading_space: true } @@ -94,17 +94,17 @@ ##'#\'## // CHECK:STDOUT: - { index: 11, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "##'#\\'##", has_leading_space: true } -// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:1: error: character literal is missing a terminator [UnterminatedString] +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:2: error: character literal is missing a terminator [UnterminatedString] // CHECK:STDERR: ' // CHECK:STDOUT: - { index: 12, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "'", has_leading_space: true } -// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:1: error: character literal is missing a terminator [UnterminatedString] +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:4: error: character literal is missing a terminator [UnterminatedString] // CHECK:STDERR: '\' // CHECK:STDOUT: - { index: 13, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "'\\'", has_leading_space: true } -// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:1: error: character literal is missing a terminator [UnterminatedString] +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:3: error: character literal is missing a terminator [UnterminatedString] // CHECK:STDERR: '\ // CHECK:STDOUT: - { index: 14, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "'\\", has_leading_space: true } diff --git a/toolchain/lex/testdata/fail_malformed_escapes.carbon b/toolchain/lex/testdata/fail_malformed_escapes.carbon new file mode 100644 index 000000000000..85d922ba34ef --- /dev/null +++ b/toolchain/lex/testdata/fail_malformed_escapes.carbon @@ -0,0 +1,48 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lex/testdata/fail_malformed_escapes.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lex/testdata/fail_malformed_escapes.carbon + +// Each diagnostic is located where the escape stops being well-formed, and +// marks the escape up to there. + +// --- fail_hex_escape_wrong_digit.carbon +// CHECK:STDOUT: - filename: fail_hex_escape_wrong_digit.carbon +// CHECK:STDOUT: tokens: + +// CHECK:STDERR: fail_hex_escape_wrong_digit.carbon:[[@LINE+2]]:4: error: escape sequence `\x` must be followed by two uppercase hexadecimal digits, for example `\x0F` [HexadecimalEscapeMissingDigits] +// CHECK:STDERR: +"\xG1" +// CHECK:STDOUT: - { index: 1, kind: "StringLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "\"\\xG1\"", value: "xG1", has_leading_space: true } + +// --- fail_hex_escape_one_digit.carbon +// CHECK:STDOUT: - filename: fail_hex_escape_one_digit.carbon +// CHECK:STDOUT: tokens: + +// CHECK:STDERR: fail_hex_escape_one_digit.carbon:[[@LINE+2]]:5: error: escape sequence `\x` must be followed by two uppercase hexadecimal digits, for example `\x0F` [HexadecimalEscapeMissingDigits] +// CHECK:STDERR: +"\x0g" +// CHECK:STDOUT: - { index: 1, kind: "StringLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "\"\\x0g\"", value: "x0g", has_leading_space: true } + +// --- fail_unicode_escape_empty_braces.carbon +// CHECK:STDOUT: - filename: fail_unicode_escape_empty_braces.carbon +// CHECK:STDOUT: tokens: + +// CHECK:STDERR: fail_unicode_escape_empty_braces.carbon:[[@LINE+2]]:5: error: escape sequence `\u` must be followed by a braced sequence of uppercase hexadecimal digits, for example `\u{70AD}}` [UnicodeEscapeMissingBracedDigits] +// CHECK:STDERR: +"\u{}" +// CHECK:STDOUT: - { index: 1, kind: "StringLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "\"\\u{}\"", value: "u{}", has_leading_space: true } + +// --- fail_unicode_escape_unclosed.carbon +// CHECK:STDOUT: - filename: fail_unicode_escape_unclosed.carbon +// CHECK:STDOUT: tokens: + +// CHECK:STDERR: fail_unicode_escape_unclosed.carbon:[[@LINE+2]]:7: error: escape sequence `\u` must be followed by a braced sequence of uppercase hexadecimal digits, for example `\u{70AD}}` [UnicodeEscapeMissingBracedDigits] +// CHECK:STDERR: +"\u{12" +// CHECK:STDOUT: - { index: 1, kind: "StringLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "\"\\u{12\"", value: "u{12", has_leading_space: true } diff --git a/toolchain/lex/testdata/fail_mismatched_brackets_line_end.carbon b/toolchain/lex/testdata/fail_mismatched_brackets_line_end.carbon new file mode 100644 index 000000000000..0647eab6b1b2 --- /dev/null +++ b/toolchain/lex/testdata/fail_mismatched_brackets_line_end.carbon @@ -0,0 +1,38 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lex/testdata/fail_mismatched_brackets_line_end.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lex/testdata/fail_mismatched_brackets_line_end.carbon +// CHECK:STDOUT: - filename: fail_mismatched_brackets_line_end.carbon +// CHECK:STDOUT: tokens: + +// A missing `{` goes at the end of the line of the token it follows, before the +// comment there. +fn F() { +// CHECK:STDOUT: - { index: 1, kind: "Fn", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "fn", has_leading_space: true } +// CHECK:STDOUT: - { index: 2, kind: "Identifier", line: {{ *}}[[@LINE-2]], column: 4, indent: 1, spelling: "F", identifier: 0, has_leading_space: true } +// CHECK:STDOUT: - { index: 3, kind: "OpenParen", line: {{ *}}[[@LINE-3]], column: 5, indent: 1, spelling: "(", closing_token: 4 } +// CHECK:STDOUT: - { index: 4, kind: "CloseParen", line: {{ *}}[[@LINE-4]], column: 6, indent: 1, spelling: ")", opening_token: 3 } +// CHECK:STDOUT: - { index: 5, kind: "OpenCurlyBrace", line: {{ *}}[[@LINE-5]], column: 8, indent: 1, spelling: "{", closing_token: 16, has_leading_space: true } + if (true) // A comment. + // CHECK:STDOUT: - { index: 6, kind: "If", line: {{ *}}[[@LINE-1]], column: 3, indent: 3, spelling: "if", has_leading_space: true } + // CHECK:STDOUT: - { index: 7, kind: "OpenParen", line: {{ *}}[[@LINE-2]], column: 6, indent: 3, spelling: "(", closing_token: 9, has_leading_space: true } + // CHECK:STDOUT: - { index: 8, kind: "True", line: {{ *}}[[@LINE-3]], column: 7, indent: 3, spelling: "true" } + // CHECK:STDOUT: - { index: 9, kind: "CloseParen", line: {{ *}}[[@LINE-4]], column: 11, indent: 3, spelling: ")", opening_token: 7 } + // CHECK:STDOUT: - { index: 10, kind: "OpenCurlyBrace", line: {{ *}}[[@LINE-5]], column: 12, indent: 3, spelling: "{", closing_token: 15, has_leading_space: true, recovery: true } + G(); + // CHECK:STDOUT: - { index: 11, kind: "Identifier", line: {{ *}}[[@LINE-1]], column: 5, indent: 5, spelling: "G", identifier: 1 } + // CHECK:STDOUT: - { index: 12, kind: "OpenParen", line: {{ *}}[[@LINE-2]], column: 6, indent: 5, spelling: "(", closing_token: 13 } + // CHECK:STDOUT: - { index: 13, kind: "CloseParen", line: {{ *}}[[@LINE-3]], column: 7, indent: 5, spelling: ")", opening_token: 12 } + // CHECK:STDOUT: - { index: 14, kind: "Semi", line: {{ *}}[[@LINE-4]], column: 8, indent: 5, spelling: ";" } + // CHECK:STDERR: fail_mismatched_brackets_line_end.carbon:[[@LINE+3]]:3: error: closing symbol without a corresponding opening symbol [UnmatchedClosing] + // CHECK:STDERR: fail_mismatched_brackets_line_end.carbon:[[@LINE-12]]:12: note: possibly missing `{` here [PossiblyMissingBracketHere] + // CHECK:STDERR: + } + // CHECK:STDOUT: - { index: 15, kind: "CloseCurlyBrace", line: {{ *}}[[@LINE-1]], column: 3, indent: 3, spelling: "}", opening_token: 10, has_leading_space: true } +} +// CHECK:STDOUT: - { index: 16, kind: "CloseCurlyBrace", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "}", opening_token: 5, has_leading_space: true } diff --git a/toolchain/lex/testdata/multiline_string_literals.carbon b/toolchain/lex/testdata/multiline_string_literals.carbon index 0ae61206ae57..e85910c6a2d6 100644 --- a/toolchain/lex/testdata/multiline_string_literals.carbon +++ b/toolchain/lex/testdata/multiline_string_literals.carbon @@ -38,6 +38,7 @@ var s: String = '''py // A trailing comment with ', #, and " in it. // --- AUTOUPDATE-SPLIT // CHECK:STDERR: fail_indent_mismatch.carbon:3:1: error: indentation does not match that of the closing `'''` in multi-line string literal [MismatchedIndentInString] +// CHECK:STDERR: fail_indent_mismatch.carbon:4:1-2: note: closing `'''` is indented to here [ClosingDelimiterIndent] // CHECK:STDERR: // CHECK:STDERR: fail_quotes.carbon:2:1: error: use `'''` delimiters for a multi-line string literal, not `"""` [MultiLineStringWithDoubleQuotes] // CHECK:STDERR: diff --git a/toolchain/lex/testdata/string_literals.carbon b/toolchain/lex/testdata/string_literals.carbon index 6b497672791c..6a36886b4eaa 100644 --- a/toolchain/lex/testdata/string_literals.carbon +++ b/toolchain/lex/testdata/string_literals.carbon @@ -12,7 +12,7 @@ // CHECK:STDOUT: - filename: fail_decimal_escape.carbon // CHECK:STDOUT: tokens: -// CHECK:STDERR: fail_decimal_escape.carbon:[[@LINE+2]]:4: error: decimal digit follows `\0` escape sequence. Use `\x00` instead of `\0` if the next character is a digit [DecimalEscapeSequence] +// CHECK:STDERR: fail_decimal_escape.carbon:[[@LINE+2]]:2: error: decimal digit follows `\0` escape sequence. Use `\x00` instead of `\0` if the next character is a digit [DecimalEscapeSequence] // CHECK:STDERR: "\01" // CHECK:STDOUT: - { index: 1, kind: "StringLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "\"\\01\"", value: "\x001", has_leading_space: true } @@ -21,7 +21,7 @@ // CHECK:STDOUT: - filename: fail_unterminated.carbon // CHECK:STDOUT: tokens: -// CHECK:STDERR: fail_unterminated.carbon:[[@LINE+2]]:1: error: string literal is missing a terminator [UnterminatedString] +// CHECK:STDERR: fail_unterminated.carbon:[[@LINE+2]]:3: error: string literal is missing a terminator [UnterminatedString] // CHECK:STDERR: "s // CHECK:STDOUT: - { index: 1, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "\"s", has_leading_space: true } @@ -31,11 +31,20 @@ // CHECK:STDOUT: - filename: fail_unknown_escape.carbon // CHECK:STDOUT: tokens: -// CHECK:STDERR: fail_unknown_escape.carbon:[[@LINE+2]]:3: error: unrecognized escape sequence `w` [UnknownEscapeSequence] +// CHECK:STDERR: fail_unknown_escape.carbon:[[@LINE+2]]:2: error: unrecognized escape sequence `w` [UnknownEscapeSequence] // CHECK:STDERR: "\w" // CHECK:STDOUT: - { index: 1, kind: "StringLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "\"\\w\"", value: "w", has_leading_space: true } +// --- fail_raw_unknown_escape.carbon +// CHECK:STDOUT: - filename: fail_raw_unknown_escape.carbon +// CHECK:STDOUT: tokens: + +// CHECK:STDERR: fail_raw_unknown_escape.carbon:[[@LINE+2]]:3: error: unrecognized escape sequence `w` [UnknownEscapeSequence] +// CHECK:STDERR: +#"\#w"# +// CHECK:STDOUT: - { index: 1, kind: "StringLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "#\"\\#w\"#", value: "w", has_leading_space: true } + // --- fail_unicode_escape_missing_braced_digits.carbon // CHECK:STDOUT: - filename: fail_unicode_escape_missing_braced_digits.carbon // CHECK:STDOUT: tokens: diff --git a/toolchain/lex/tokenized_buffer.cpp b/toolchain/lex/tokenized_buffer.cpp index 4b7423a50ebf..6d9f4ee5e806 100644 --- a/toolchain/lex/tokenized_buffer.cpp +++ b/toolchain/lex/tokenized_buffer.cpp @@ -543,21 +543,30 @@ auto TokenizedBuffer::SourcePointerToDiagnosticLoc(const char* loc) const auto line_range = line_infos_.values(); // Find the first line starting after the given location. - const auto next_line_it = llvm::partition_point( + auto next_line_it = llvm::partition_point( line_range, [offset](const LineInfo& line) { return line.start <= offset; }); // Step back one line to find the line containing the given position. CARBON_CHECK(next_line_it != line_range.begin(), "location precedes the start of the first line"); - const auto line_it = std::prev(next_line_it); + auto line_it = std::prev(next_line_it); + + // A file that doesn't end in a newline has an empty line after its last one, + // starting at the end of the file. The end of the file is the end of the + // last line instead. + llvm::StringRef text = source_->text(); + if (line_it->start == offset && line_it != line_range.begin() && + offset == static_cast(text.size()) && !text.ends_with("\n")) { + next_line_it = line_it; + --line_it; + } int line_number = line_it - line_range.begin(); int column_number = offset - line_it->start; // Grab the line from the buffer by slicing from this line to the next // minus the newline. When on the last line, instead use the start to the end // of the buffer. - llvm::StringRef text = source_->text(); llvm::StringRef line = next_line_it != line_range.end() ? text.slice(line_it->start, next_line_it->start) : text.substr(line_it->start); @@ -589,6 +598,43 @@ auto TokenizedBuffer::TokenToDiagnosticLoc(TokenIndex token) const return converted; } +auto TokenizedBuffer::TokenRangeToDiagnosticLoc( + InclusiveTokenRange tokens) const -> Diagnostics::ConvertedLoc { + auto begin_loc = TokenToDiagnosticLoc(tokens.begin); + if (tokens.begin == tokens.end) { + return begin_loc; + } + auto end_loc = TokenToDiagnosticLoc(tokens.end); + begin_loc.last_byte_offset = end_loc.last_byte_offset; + // TODO: A range across lines covers only the rest of its first line, because + // a `Diagnostics::Loc` is on one line. Cover the whole range once a location + // can span lines. + if (begin_loc.loc.line_number != end_loc.loc.line_number) { + begin_loc.loc.length = + begin_loc.loc.line.size() - begin_loc.loc.column_number + 1; + } else { + if (begin_loc.loc.column_number != end_loc.loc.column_number) { + begin_loc.loc.length = end_loc.loc.column_number + end_loc.loc.length - + begin_loc.loc.column_number; + } + } + return begin_loc; +} + +auto TokenizedBuffer::PointAfterTokenToDiagnosticLoc(TokenIndex token) const + -> Diagnostics::ConvertedLoc { + // A recovery token isn't in the source. It was inserted just past the token + // before it, which puts the point just past it there too. + while (IsRecoveryToken(token)) { + token = TokenIndex(token.index - 1); + } + auto converted = SourcePointerToDiagnosticLoc( + source_->text().begin() + token_infos_.Get(token).byte_offset() + + GetTokenText(token).size()); + converted.loc.length = 0; + return converted; +} + auto TokenizedBuffer::OverlapsWithDumpSemIRRange( Lex::InclusiveTokenRange range) const -> bool { CARBON_CHECK(!dump_sem_ir_ranges_.empty()); diff --git a/toolchain/lex/tokenized_buffer.h b/toolchain/lex/tokenized_buffer.h index a73641b10e18..f8fe92aadd33 100644 --- a/toolchain/lex/tokenized_buffer.h +++ b/toolchain/lex/tokenized_buffer.h @@ -19,6 +19,7 @@ #include "toolchain/base/mem_usage.h" #include "toolchain/base/shared_value_stores.h" #include "toolchain/diagnostics/emitter.h" +#include "toolchain/lex/source_loc.h" #include "toolchain/lex/token_index.h" #include "toolchain/lex/token_info.h" #include "toolchain/lex/token_kind.h" @@ -205,6 +206,11 @@ class TokenizedBuffer : public Printable { // of the given word token. This should only be used for error recovery // purposes, when a phase after the lexer determines that a word token was // intended to represent an identifier rather than its special meaning. + // + // TODO: Recover with a raw identifier only when it names something, perhaps + // with a note suggesting `r#` otherwise. The parser recovers with one for any + // reserved word it takes as a name, so check cascades, for example to "name + // `r#Cpp` not found" on `class Cpp.C {}`. auto AddPostLexingRecoveryTokenAsIdentifier(TokenIndex token) -> TokenIndex; // Returns the 1-based indentation column number. @@ -251,6 +257,16 @@ class TokenizedBuffer : public Printable { auto TokenToDiagnosticLoc(TokenIndex token) const -> Diagnostics::ConvertedLoc; + // Converts a run of tokens to a diagnostic location. A run spanning lines + // covers the rest of its first line. + auto TokenRangeToDiagnosticLoc(InclusiveTokenRange tokens) const + -> Diagnostics::ConvertedLoc; + + // Converts the point just past `token`, where something missing after it + // would be written, to a diagnostic location. + auto PointAfterTokenToDiagnosticLoc(TokenIndex token) const + -> Diagnostics::ConvertedLoc; + // Returns true if the given range overlaps with an entry in // `dump_sem_ir_ranges_`. Must not be called when there are no ranges; query // `has_dump_sem_ir_ranges` first. @@ -294,17 +310,19 @@ class TokenizedBuffer : public Printable { private: friend class Lexer; - class SourcePointerDiagnosticEmitter - : public Diagnostics::Emitter { + class SourceLocDiagnosticEmitter : public Diagnostics::Emitter { public: - explicit SourcePointerDiagnosticEmitter(Diagnostics::Consumer* consumer, - const TokenizedBuffer* tokens) + explicit SourceLocDiagnosticEmitter(Diagnostics::Consumer* consumer, + const TokenizedBuffer* tokens) : Emitter(consumer), tokens_(tokens) {} protected: - auto ConvertLoc(const char* loc, ContextFnT /*context_fn*/) const + auto ConvertLoc(SourceLoc loc, ContextFnT /*context_fn*/) const -> Diagnostics::ConvertedLoc override { - return tokens_->SourcePointerToDiagnosticLoc(loc); + auto converted = + tokens_->SourcePointerToDiagnosticLoc(loc.text().begin()); + converted.loc.length = loc.text().size(); + return converted; } private: diff --git a/toolchain/lex/tokenized_buffer_test.cpp b/toolchain/lex/tokenized_buffer_test.cpp index 4bb81f0d28c6..febbc15ca1d0 100644 --- a/toolchain/lex/tokenized_buffer_test.cpp +++ b/toolchain/lex/tokenized_buffer_test.cpp @@ -1188,9 +1188,14 @@ TEST_F(LexerTest, TypeLiterals) { TEST_F(LexerTest, TypeLiteralTooManyDigits) { // We increase the number of digits until the first one that is to large. Testing::MockDiagnosticConsumer consumer; - EXPECT_CALL(consumer, HandleDiagnostic(IsSingleDiagnostic( - Diagnostics::Kind::TooManyTypeBitWidthDigits, - Diagnostics::Level::Error, 1, 2, _))); + // The bit width's digits are marked as the range that is too long. + EXPECT_CALL( + consumer, + HandleDiagnostic(Testing::IsDiagnosticWithRange( + Diagnostics::Kind::TooManyTypeBitWidthDigits, + Diagnostics::Level::Error, + {.line_number = 1, .column_number = 2, .length = 1}, _, + {.line_number = 1, .column_number = 2, .length = testing::Gt(1)}))); std::string code = "i"; // A 128-bit APInt should be plenty large, but if needed in the future it can // be widened without issue. @@ -1221,10 +1226,13 @@ TEST_F(LexerTest, TypeLiteralTooManyDigits) { // Make sure we can also gracefully reject very large number of digits without // crashing or hanging, and show the correct number. constexpr int Count = 10000; - EXPECT_CALL(consumer, HandleDiagnostic(IsSingleDiagnostic( - Diagnostics::Kind::TooManyTypeBitWidthDigits, - Diagnostics::Level::Error, 1, 2, - HasSubstr(llvm::formatv(" {0} ", Count))))); + EXPECT_CALL(consumer, + HandleDiagnostic(Testing::IsDiagnosticWithRange( + Diagnostics::Kind::TooManyTypeBitWidthDigits, + Diagnostics::Level::Error, + {.line_number = 1, .column_number = 2, .length = 1}, + HasSubstr(llvm::formatv(" {0} ", Count)), + {.line_number = 1, .column_number = 2, .length = Count}))); code = "i"; code.append(Count, '9'); auto& buffer = compile_helper_.GetTokenizedBuffer(code, &consumer); @@ -1328,8 +1336,9 @@ TEST_F(LexerTest, InvalidCommentRunsLumpTogether) { // no matter which invalid byte follows each `//`; a valid comment ends the // run and starts its own record. Testing::MockDiagnosticConsumer consumer; + // The whitespace goes just past the `//`. EXPECT_CALL(consumer, - HandleDiagnostic(IsSingleDiagnostic( + HandleDiagnostic(Testing::IsPointDiagnostic( Diagnostics::Kind::NoWhitespaceAfterCommentIntroducer, Diagnostics::Level::Error, 1, 3, _))); auto& buffer = compile_helper_.GetTokenizedBuffer( @@ -1369,8 +1378,9 @@ TEST_F(LexerTest, InvalidCommentRunAtEof) { TEST_F(LexerTest, DiagnosticWhitespace) { Testing::MockDiagnosticConsumer consumer; + // The whitespace goes just past the `//`. EXPECT_CALL(consumer, - HandleDiagnostic(IsSingleDiagnostic( + HandleDiagnostic(Testing::IsPointDiagnostic( Diagnostics::Kind::NoWhitespaceAfterCommentIntroducer, Diagnostics::Level::Error, 1, 3, _))); compile_helper_.GetTokenizedBuffer("//no space after comment", &consumer); @@ -1378,23 +1388,34 @@ TEST_F(LexerTest, DiagnosticWhitespace) { TEST_F(LexerTest, DiagnosticUnrecognizedEscape) { Testing::MockDiagnosticConsumer consumer; - EXPECT_CALL(consumer, - HandleDiagnostic(IsSingleDiagnostic( - Diagnostics::Kind::UnknownEscapeSequence, - Diagnostics::Level::Error, 1, 8, HasSubstr("`b`")))); + // The message is located at the `\` that starts the escape, and the escape + // is marked. + EXPECT_CALL( + consumer, + HandleDiagnostic(Testing::IsDiagnosticWithRange( + Diagnostics::Kind::UnknownEscapeSequence, Diagnostics::Level::Error, + {.line_number = 1, .column_number = 7, .length = 1}, HasSubstr("`b`"), + {.line_number = 1, .column_number = 7, .length = 2}))); compile_helper_.GetTokenizedBuffer(R"("hello\bworld")", &consumer); } TEST_F(LexerTest, DiagnosticBadHex) { Testing::MockDiagnosticConsumer consumer; - EXPECT_CALL(consumer, HandleDiagnostic(IsSingleDiagnostic( - Diagnostics::Kind::HexadecimalEscapeMissingDigits, - Diagnostics::Level::Error, 1, 9, _))); + // The message is located at the first digit that isn't an uppercase + // hexadecimal digit, and the escape before it is marked. + EXPECT_CALL(consumer, + HandleDiagnostic(Testing::IsDiagnosticWithRange( + Diagnostics::Kind::HexadecimalEscapeMissingDigits, + Diagnostics::Level::Error, + {.line_number = 1, .column_number = 9, .length = 0}, _, + {.line_number = 1, .column_number = 7, .length = 2}))); compile_helper_.GetTokenizedBuffer(R"("hello\xabworld")", &consumer); } TEST_F(LexerTest, DiagnosticInvalidDigit) { Testing::MockDiagnosticConsumer consumer; + // Only the invalid digit is marked, since the message says which literal it + // is in. EXPECT_CALL(consumer, HandleDiagnostic(IsSingleDiagnostic( Diagnostics::Kind::InvalidDigit, Diagnostics::Level::Error, 1, @@ -1420,17 +1441,82 @@ TEST_F(LexerTest, DiagnosticLfCr) { TEST_F(LexerTest, DiagnosticMissingTerminator) { Testing::MockDiagnosticConsumer consumer; - EXPECT_CALL(consumer, HandleDiagnostic(IsSingleDiagnostic( - Diagnostics::Kind::UnterminatedString, - Diagnostics::Level::Error, 1, 1, _))); + // The terminator goes where the literal stops, and the whole literal is + // marked. + EXPECT_CALL( + consumer, + HandleDiagnostic(Testing::IsDiagnosticWithRange( + Diagnostics::Kind::UnterminatedString, Diagnostics::Level::Error, + {.line_number = 1, .column_number = 5, .length = 0}, _, + {.line_number = 1, .column_number = 1, .length = 4}))); compile_helper_.GetTokenizedBuffer(R"(#" ")", &consumer); } +TEST_F(LexerTest, DiagnosticMissingMultiLineTerminator) { + Testing::MockDiagnosticConsumer consumer; + // The literal runs to the end of the file, which is where the terminator + // goes, and the literal is marked from its introducer. + EXPECT_CALL( + consumer, + HandleDiagnostic(Testing::IsDiagnosticWithRange( + Diagnostics::Kind::UnterminatedString, Diagnostics::Level::Error, + {.line_number = 3, .column_number = 1, .length = 0}, _, + {.line_number = 1, .column_number = 1, .length = _}))); + compile_helper_.GetTokenizedBuffer( + "'''\n" + " text\n", + &consumer); +} + +TEST_F(LexerTest, DiagnosticPointAtEndOfFile) { + Testing::MockDiagnosticConsumer consumer; + // A file that doesn't end in a newline ends at the end of its last line. + EXPECT_CALL(consumer, HandleDiagnostic(Testing::IsPointDiagnostic( + Diagnostics::Kind::DollarIntLiteralMissingNumber, + Diagnostics::Level::Error, 1, 2, _))); + compile_helper_.GetTokenizedBuffer("$", &consumer); +} + +// Returns the line and column of the point just past `token`, checking that it +// is a point. +static auto PointAfter(const TokenizedBuffer& buffer, int token) + -> std::pair { + auto loc = buffer.PointAfterTokenToDiagnosticLoc(TokenIndex(token)).loc; + EXPECT_THAT(loc.length, Eq(0)); + return {loc.line_number, loc.column_number}; +} + +TEST_F(LexerTest, PointAfterToken) { + // Tokens are `FileStart`, `a`, `b`, the string literal, and `FileEnd`. + auto& buffer = compile_helper_.GetTokenizedBuffer( + "a b\n" + "'''\n" + " c\n" + "'''"); + // The point is before the spaces after the token. + EXPECT_THAT(PointAfter(buffer, 1), Pair(1, 2)); + // The point just past the last token on a line is at the end of the line. + EXPECT_THAT(PointAfter(buffer, 2), Pair(1, 5)); + // A token spanning lines ends on its last line, which here ends the file. + EXPECT_THAT(PointAfter(buffer, 3), Pair(4, 4)); +} + +TEST_F(LexerTest, PointAfterRecoveryToken) { + // The lexer inserts a `)` after `a`. It isn't in the source, so the point + // just past it is the point just past `a`. + auto& buffer = compile_helper_.GetTokenizedBuffer("(a"); + ASSERT_TRUE(buffer.IsRecoveryToken(TokenIndex(3))); + EXPECT_THAT(PointAfter(buffer, 3), Pair(1, 3)); +} + TEST_F(LexerTest, DiagnosticUnrecognizedChar) { Testing::MockDiagnosticConsumer consumer; - EXPECT_CALL(consumer, HandleDiagnostic(IsSingleDiagnostic( - Diagnostics::Kind::UnrecognizedCharacters, - Diagnostics::Level::Error, 1, 1, _))); + EXPECT_CALL( + consumer, + HandleDiagnostic(Testing::IsDiagnosticWithRange( + Diagnostics::Kind::UnrecognizedCharacters, Diagnostics::Level::Error, + {.line_number = 1, .column_number = 1, .length = 1}, _, + {.line_number = 1, .column_number = 1, .length = 1}))); compile_helper_.GetTokenizedBuffer("\b", &consumer); } diff --git a/toolchain/parse/context.cpp b/toolchain/parse/context.cpp index 20124a3867c5..1325eb81acf4 100644 --- a/toolchain/parse/context.cpp +++ b/toolchain/parse/context.cpp @@ -55,11 +55,20 @@ auto Context::VlogPopState(llvm::StringLiteral label) const -> void { state_stack_.back()); } +auto Context::OpeningBracketInsertionPoint() const -> DiagnosticLoc { + auto previous_token = *(position_ - 1); + if (tokens().GetEndLoc(previous_token).first == + tokens().GetLine(*position_)) { + return DiagnosticLoc::Before(*position_); + } + return DiagnosticLoc::After(previous_token); +} + auto Context::DiagnoseExpectedOpenCurlyBrace(Lex::TokenIndex default_token, NodeKind start_kind) -> void { CARBON_DIAGNOSTIC(ExpectedCurlyBraceAfter, Error, "expected `{` after `{0}`", Lex::TokenKind); - emitter_.Emit(*position_, ExpectedCurlyBraceAfter, + emitter_.Emit(OpeningBracketInsertionPoint(), ExpectedCurlyBraceAfter, tokens().GetKind(default_token)); AddLeafNode(start_kind, default_token, /*has_error=*/true); } @@ -68,7 +77,7 @@ auto Context::DiagnoseExpectedOpenParen(Lex::TokenIndex default_token, NodeKind start_kind) -> void { CARBON_DIAGNOSTIC(ExpectedParenAfter, Error, "expected `(` after `{0}`", Lex::TokenKind); - emitter_.Emit(*position_, ExpectedParenAfter, + emitter_.Emit(OpeningBracketInsertionPoint(), ExpectedParenAfter, tokens().GetKind(default_token)); AddLeafNode(start_kind, default_token, /*has_error=*/true); } @@ -81,14 +90,20 @@ auto Context::DiagnoseAndSkipToCloseSymbol(State state, NodeKind close_kind) return; } - // TODO: Include the location of the matching opening delimiter in the - // diagnostic. CARBON_DIAGNOSTIC(ExpectedCloseSymbol, Error, "unexpected tokens before `{0}`", Lex::TokenKind); - emitter_.Emit(*position_, ExpectedCloseSymbol, - open_token_kind.closing_symbol()); + CARBON_DIAGNOSTIC_LABEL(GroupOpenedHere, Info, "matching this `{0}`", + Lex::TokenKind); + auto matched_close = tokens().GetMatchedClosingToken(state.token); + emitter_ + .Build(DiagnosticLoc::Tokens( + {.begin = *position_, + .end = Lex::TokenIndex(matched_close.index - 1)}), + ExpectedCloseSymbol, open_token_kind.closing_symbol()) + .Attach(state.token, GroupOpenedHere, open_token_kind) + .Emit(); - SkipTo(tokens().GetMatchedClosingToken(state.token)); + SkipTo(matched_close); AddNode(close_kind, Consume(), /*has_error=*/true); } @@ -184,13 +199,18 @@ auto Context::DiagnoseInvalidOperatorFixity(OperatorFixity fixity) -> void { "whitespace missing {0:=-1:before|=0:around|=1:after} " "binary operator", Diagnostics::IntAsSelect); + // Whitespace missing on one side goes at that side. Whitespace missing + // around the operator is about the operator. Diagnostics::IntAsSelect pos(0); + DiagnosticLoc loc = *position_; if (tokens().HasLeadingWhitespace(*position_)) { pos.value = 1; + loc = DiagnosticLoc::After(*position_); } else if (tokens().HasTrailingWhitespace(*position_)) { pos.value = -1; + loc = DiagnosticLoc::Before(*position_); } - emitter_.Emit(*position_, BinaryOperatorRequiresWhitespace, pos); + emitter_.Emit(loc, BinaryOperatorRequiresWhitespace, pos); } else { bool prefix = fixity == OperatorFixity::Prefix; @@ -209,7 +229,9 @@ auto Context::DiagnoseInvalidOperatorFixity(OperatorFixity fixity) -> void { UnaryOperatorRequiresWhitespace, Error, "whitespace is required {0:before|after} this unary operator", Diagnostics::BoolAsSelect); - emitter_.Emit(*position_, UnaryOperatorRequiresWhitespace, prefix); + emitter_.Emit(prefix ? DiagnosticLoc::Before(*position_) + : DiagnosticLoc::After(*position_), + UnaryOperatorRequiresWhitespace, prefix); } } } @@ -218,9 +240,13 @@ auto Context::RecoverFromInvalidListToken(Lex::TokenKind close_kind, bool already_has_error) -> void { // Don't error a second time on the same element. if (!already_has_error) { + // TODO: Also label the bracket that opened the list, to show which list + // this is. That needs the opening token, which callers hold in + // `state.token` but don't pass down. CARBON_DIAGNOSTIC(UnexpectedTokenAfterListElement, Error, "expected `,` or `{0}`", Lex::TokenKind); - emitter_.Emit(*position_, UnexpectedTokenAfterListElement, close_kind); + emitter_.Emit(PointAfterPreviousToken(), UnexpectedTokenAfterListElement, + close_kind); ReturnErrorOnState(); } @@ -279,8 +305,9 @@ auto Context::ParseLibraryName(bool accept_default) "expected `default` or a string literal to specify the library name"); CARBON_DIAGNOSTIC(ExpectedLibraryName, Error, "expected a string literal to specify the library name"); - emitter().Emit(*position(), accept_default ? ExpectedLibraryNameOrDefault - : ExpectedLibraryName); + emitter().Emit(PointAfterPreviousToken(), accept_default + ? ExpectedLibraryNameOrDefault + : ExpectedLibraryName); return std::nullopt; } @@ -298,7 +325,7 @@ auto Context::ParseLibrarySpecifier(bool accept_default) auto Context::DiagnoseExpectedDeclSemi(Lex::TokenKind expected_kind) -> void { CARBON_DIAGNOSTIC(ExpectedDeclSemi, Error, "`{0}` declarations must end with a `;`", Lex::TokenKind); - emitter().Emit(*position(), ExpectedDeclSemi, expected_kind); + emitter().Emit(PointAfterPreviousToken(), ExpectedDeclSemi, expected_kind); } auto Context::DiagnoseExpectedDeclSemiOrDefinition(Lex::TokenKind expected_kind) @@ -307,7 +334,8 @@ auto Context::DiagnoseExpectedDeclSemiOrDefinition(Lex::TokenKind expected_kind) "`{0}` declarations must either end with a `;` or " "have a `{{ ... }` block for a definition", Lex::TokenKind); - emitter().Emit(*position(), ExpectedDeclSemiOrDefinition, expected_kind); + emitter().Emit(PointAfterPreviousToken(), ExpectedDeclSemiOrDefinition, + expected_kind); } // Returns whether we are currently parsing in a scope in which function diff --git a/toolchain/parse/context.h b/toolchain/parse/context.h index 1548ed46bbe9..c7e7dbb75c58 100644 --- a/toolchain/parse/context.h +++ b/toolchain/parse/context.h @@ -45,23 +45,80 @@ enum class BindingContext : uint8_t { CompileTimeEntityParam, }; +// The location of a parse diagnostic: a run of tokens, or the point just before +// or just past a token, where something missing would be written. +class DiagnosticLoc { + public: + enum class Kind : int8_t { + Tokens, + PointBefore, + PointAfter, + }; + + // NOLINTNEXTLINE(google-explicit-constructor): A token is a location. + DiagnosticLoc(Lex::TokenIndex token) + : DiagnosticLoc(Kind::Tokens, {.begin = token, .end = token}) {} + + // The tokens from `tokens.begin` through `tokens.end`. + static auto Tokens(Lex::InclusiveTokenRange tokens) -> DiagnosticLoc { + return DiagnosticLoc(Kind::Tokens, tokens); + } + + // The point just before `token`. + static auto Before(Lex::TokenIndex token) -> DiagnosticLoc { + return DiagnosticLoc(Kind::PointBefore, {.begin = token, .end = token}); + } + + // The point just past `token`. + static auto After(Lex::TokenIndex token) -> DiagnosticLoc { + return DiagnosticLoc(Kind::PointAfter, {.begin = token, .end = token}); + } + + auto kind() const -> Kind { return kind_; } + + // The tokens this location covers, or the one token a point is before or + // after. + auto tokens() const -> Lex::InclusiveTokenRange { return tokens_; } + + private: + explicit DiagnosticLoc(Kind kind, Lex::InclusiveTokenRange tokens) + : kind_(kind), tokens_(tokens) {} + + Kind kind_; + Lex::InclusiveTokenRange tokens_; +}; + // Context and shared functionality for parser handlers. See state.def for state // documentation. class Context { public: // A token-based emitter for use during parse. - class DiagnosticEmitter : public Diagnostics::Emitter { + class DiagnosticEmitter : public Diagnostics::Emitter { public: explicit DiagnosticEmitter(Diagnostics::Consumer* consumer, Context* context) : Emitter(consumer), context_(context) {} protected: - // Applies the `position_` to the `last_byte_offset` returned by - // `TokenToDiagnosticLoc`. - auto ConvertLoc(Lex::TokenIndex token, ContextFnT /*context_fn*/) const + // Applies the `position_` to the `last_byte_offset` of the converted + // location, so that a diagnostic sorts by how far the parser had read even + // when the location it names is earlier. Diagnostics with the same offset + // stay in the order they were emitted. + auto ConvertLoc(DiagnosticLoc loc, ContextFnT /*context_fn*/) const -> Diagnostics::ConvertedLoc override { - auto converted = context_->tokens().TokenToDiagnosticLoc(token); + auto converted = + context_->tokens().TokenRangeToDiagnosticLoc(loc.tokens()); + switch (loc.kind()) { + case DiagnosticLoc::Kind::Tokens: + break; + case DiagnosticLoc::Kind::PointBefore: + converted.loc.length = 0; + break; + case DiagnosticLoc::Kind::PointAfter: + converted = context_->tokens().PointAfterTokenToDiagnosticLoc( + loc.tokens().begin); + break; + } converted.last_byte_offset = std::max(converted.last_byte_offset, context_->tokens().GetByteOffset(*context_->position())); @@ -627,6 +684,17 @@ class Context { auto position() -> Lex::TokenIterator& { return position_; } auto position() const -> Lex::TokenIterator { return position_; } + // Returns the point just past the token before `position_`, which is where a + // token missing at `position_` would be written. + auto PointAfterPreviousToken() const -> DiagnosticLoc { + return DiagnosticLoc::After(*(position_ - 1)); + } + + // Returns where a user would insert an opening bracket missing at + // `position_`: against the token at `position_` when it's on the line where + // the previous token ends, and otherwise just past the previous token. + auto OpeningBracketInsertionPoint() const -> DiagnosticLoc; + auto state_stack() -> llvm::SmallVector& { return state_stack_; } auto state_stack() const -> const llvm::SmallVector& { diff --git a/toolchain/parse/handle_alias.cpp b/toolchain/parse/handle_alias.cpp index bf85a54b4240..8c5fda729750 100644 --- a/toolchain/parse/handle_alias.cpp +++ b/toolchain/parse/handle_alias.cpp @@ -35,7 +35,8 @@ auto HandleAliasAfterName(Context& context) -> void { } else { CARBON_DIAGNOSTIC(ExpectedAliasInitializer, Error, "`alias` requires a `=` for the source"); - context.emitter().Emit(*context.position(), ExpectedAliasInitializer); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedAliasInitializer); context.RecoverFromDeclError(state, NodeKind::Alias, /*skip_past_likely_end=*/true); } diff --git a/toolchain/parse/handle_array_expr.cpp b/toolchain/parse/handle_array_expr.cpp index 27905269134e..06b9a4741d1c 100644 --- a/toolchain/parse/handle_array_expr.cpp +++ b/toolchain/parse/handle_array_expr.cpp @@ -32,7 +32,8 @@ auto HandleArrayExprComma(Context& context) -> void { context.AddLeafNode(NodeKind::ArrayExprComma, *context.position(), true); CARBON_DIAGNOSTIC(ExpectedArrayComma, Error, "expected `,` in `array(Type, Count)`"); - context.emitter().Emit(*context.position(), ExpectedArrayComma); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedArrayComma); state.has_error = true; } context.PushState(state, StateKind::ArrayExprFinish); diff --git a/toolchain/parse/handle_base.cpp b/toolchain/parse/handle_base.cpp index 3167cf58143e..2382dd8688ef 100644 --- a/toolchain/parse/handle_base.cpp +++ b/toolchain/parse/handle_base.cpp @@ -15,7 +15,8 @@ auto HandleBaseAfterIntroducer(Context& context) -> void { NodeKind::BaseColon)) { CARBON_DIAGNOSTIC(ExpectedAfterBase, Error, "`class` or `:` expected after `base`"); - context.emitter().Emit(*context.position(), ExpectedAfterBase); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedAfterBase); auto base_token = *(context.position() - 1); auto previous_token = Lex::TokenIndex(base_token.index - 1); if (context.tokens().GetKind(previous_token) != Lex::TokenKind::Extend) { diff --git a/toolchain/parse/handle_binding_pattern.cpp b/toolchain/parse/handle_binding_pattern.cpp index 06209479c14a..90e5a8db5858 100644 --- a/toolchain/parse/handle_binding_pattern.cpp +++ b/toolchain/parse/handle_binding_pattern.cpp @@ -77,15 +77,16 @@ auto HandleBindingPattern(Context& context) -> void { auto state = context.PopState(); // Handle an invalid pattern introducer for parameters and variables. - auto on_error = [&](bool expected_name, bool recover_as_raw = false) { + auto on_error = [&](DiagnosticLoc loc, bool expected_name, + bool recover_as_raw = false) { if (!state.has_error) { CARBON_DIAGNOSTIC( ExpectedBindingPattern, Error, "expected {0:name|`:` or `:?`} in binding pattern" "{1:; prefix reserved word with `r#` to form a valid identifier|}", Diagnostics::BoolAsSelect, Diagnostics::BoolAsSelect); - context.emitter().Emit(*context.position(), ExpectedBindingPattern, - expected_name, recover_as_raw); + context.emitter().Emit(loc, ExpectedBindingPattern, expected_name, + recover_as_raw); state.has_error = !recover_as_raw; } }; @@ -135,8 +136,7 @@ auto HandleBindingPattern(Context& context) -> void { AnonymousBindingInStructPattern, Error, "anonymous binding found in struct pattern; use `.field = " "_: field_type` or `unused field: field_type`"); - context.emitter().Emit(*context.position(), - AnonymousBindingInStructPattern); + context.emitter().Emit(*underscore, AnonymousBindingInStructPattern); state.has_error = true; } context.AddLeafNode(NodeKind::UnderscoreName, *underscore); @@ -147,17 +147,22 @@ auto HandleBindingPattern(Context& context) -> void { // such as a numeric type literal or a keyword. For error recovery, convert // the token to an identifier, as we can be confident that a word in this // position was intended to be a declared name. + auto word = context.Consume(); auto word_as_identifier = - context.tokens().AddPostLexingRecoveryTokenAsIdentifier( - context.Consume()); + context.tokens().AddPostLexingRecoveryTokenAsIdentifier(word); context.AddLeafNode(NodeKind::IdentifierNameNotBeforeSignature, word_as_identifier); - on_error(/*expected_name=*/true, /*recover_as_raw*/ true); + on_error(word, /*expected_name=*/true, /*recover_as_raw*/ true); } else { + // The missing name goes after the modifiers before it. With none, the + // binding starts at the token found in the name's place. + auto missing_name_loc = state.token == *context.position() + ? DiagnosticLoc(*context.position()) + : context.PointAfterPreviousToken(); // Add a placeholder for the name. context.AddLeafNode(NodeKind::IdentifierNameNotBeforeSignature, *context.position(), /*has_error=*/true); - on_error(/*expected_name=*/true); + on_error(missing_name_loc, /*expected_name=*/true); } auto token_kind = context.PositionKind(); @@ -177,7 +182,7 @@ auto HandleBindingPattern(Context& context) -> void { } return; } - on_error(/*expected_name=*/false); + on_error(context.PointAfterPreviousToken(), /*expected_name=*/false); // Add a substitute for the identifier name and virtual type-start nodes. context.AddInvalidParse(*context.position()); context.AddInvalidParse(*context.position()); diff --git a/toolchain/parse/handle_choice.cpp b/toolchain/parse/handle_choice.cpp index d4f1cace9533..f1ce58f48678 100644 --- a/toolchain/parse/handle_choice.cpp +++ b/toolchain/parse/handle_choice.cpp @@ -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/parse/context.h" #include "toolchain/parse/handle.h" @@ -23,7 +24,8 @@ auto HandleChoiceDefinitionStart(Context& context) -> void { if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedChoiceDefinition, Error, "choice definition expected"); - context.emitter().Emit(*context.position(), ExpectedChoiceDefinition); + context.emitter().Emit(context.OpeningBracketInsertionPoint(), + ExpectedChoiceDefinition); } context.AddNode(NodeKind::ChoiceDefinitionStart, *context.position(), @@ -55,18 +57,30 @@ auto HandleChoiceAlternative(Context& context) -> void { auto token = context.ConsumeIf(Lex::TokenKind::Identifier); if (!token) { + // A word that can't be a name, followed by a token that can follow one, + // was most likely meant as the name. + bool recover_as_raw = + context.PositionKind().is_word() && + context.PositionKind(Lookahead::NextToken) + .IsOneOf({Lex::TokenKind::Comma, Lex::TokenKind::CloseCurlyBrace, + Lex::TokenKind::OpenParen}); if (!state.has_error) { - CARBON_DIAGNOSTIC(ExpectedChoiceAlternativeName, Error, - "expected choice alternative name"); - context.emitter().Emit(*context.position(), - ExpectedChoiceAlternativeName); + CARBON_DIAGNOSTIC( + ExpectedChoiceAlternativeName, Error, + "expected choice alternative name" + "{0:; prefix reserved word with `r#` to form a valid identifier|}", + Diagnostics::BoolAsSelect); + context.emitter().Emit(*context.position(), ExpectedChoiceAlternativeName, + recover_as_raw); } - context.SkipPastLikelyEnd(*context.position()); - - context.ReturnErrorOnState(); - - return; + if (!recover_as_raw) { + context.SkipPastLikelyEnd(*context.position()); + context.ReturnErrorOnState(); + return; + } + token = context.tokens().AddPostLexingRecoveryTokenAsIdentifier( + context.Consume()); } if (context.PositionIs(Lex::TokenKind::OpenParen)) { diff --git a/toolchain/parse/handle_code_block.cpp b/toolchain/parse/handle_code_block.cpp index 08cb5d7e9f61..9371ef2f9545 100644 --- a/toolchain/parse/handle_code_block.cpp +++ b/toolchain/parse/handle_code_block.cpp @@ -20,7 +20,8 @@ auto HandleCodeBlock(Context& context) -> void { // Recover by parsing a single statement. CARBON_DIAGNOSTIC(ExpectedCodeBlock, Error, "expected braced code block"); - context.emitter().Emit(*context.position(), ExpectedCodeBlock); + context.emitter().Emit(context.OpeningBracketInsertionPoint(), + ExpectedCodeBlock); context.PushState(StateKind::Statement); } diff --git a/toolchain/parse/handle_decl_name_and_params.cpp b/toolchain/parse/handle_decl_name_and_params.cpp index 33a856d74fdb..5e1fca5e525b 100644 --- a/toolchain/parse/handle_decl_name_and_params.cpp +++ b/toolchain/parse/handle_decl_name_and_params.cpp @@ -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_index.h" #include "toolchain/parse/context.h" #include "toolchain/parse/handle.h" @@ -88,22 +89,44 @@ auto HandleDeclNameAndParams(Context& context) -> void { return; } - Lex::TokenIndex token = *context.position(); - if (context.tokens().GetKind(token) == Lex::TokenKind::FileEnd) { - // The end of file is an unhelpful diagnostic location. Instead, use the - // introducer token. - token = state.token; - } + // A word that can't be a name, followed by a token that can follow one, was + // most likely meant as the name, and is what the message is about. Otherwise + // the name is missing, and goes after the token before it. + bool recover_as_raw = + context.PositionKind().is_word() && + context.PositionKind(Lookahead::NextToken) + .IsOneOf({Lex::TokenKind::Period, Lex::TokenKind::OpenSquareBracket, + Lex::TokenKind::OpenParen, Lex::TokenKind::MinusGreater, + Lex::TokenKind::MinusGreaterQuestion, Lex::TokenKind::Semi, + Lex::TokenKind::OpenCurlyBrace, Lex::TokenKind::Equal}); + auto loc = recover_as_raw ? DiagnosticLoc(*context.position()) + : context.PointAfterPreviousToken(); if (state.token == *context.position()) { - CARBON_DIAGNOSTIC(ExpectedDeclNameAfterPeriod, Error, - "`.` should be followed by a name"); - context.emitter().Emit(token, ExpectedDeclNameAfterPeriod); + CARBON_DIAGNOSTIC( + ExpectedDeclNameAfterPeriod, Error, + "`.` should be followed by a name" + "{0:; prefix reserved word with `r#` to form a valid identifier|}", + Diagnostics::BoolAsSelect); + context.emitter().Emit(loc, ExpectedDeclNameAfterPeriod, recover_as_raw); } else { - CARBON_DIAGNOSTIC(ExpectedDeclName, Error, - "`{0}` introducer should be followed by a name", - Lex::TokenKind); - context.emitter().Emit(token, ExpectedDeclName, - context.tokens().GetKind(state.token)); + CARBON_DIAGNOSTIC( + ExpectedDeclName, Error, + "`{0}` introducer should be followed by a name" + "{1:; prefix reserved word with `r#` to form a valid identifier|}", + Lex::TokenKind, Diagnostics::BoolAsSelect); + context.emitter().Emit(loc, ExpectedDeclName, + context.tokens().GetKind(state.token), + recover_as_raw); + } + + if (recover_as_raw) { + HandleName(context, state, + context.tokens().AddPostLexingRecoveryTokenAsIdentifier( + context.Consume()), + NodeKind::IdentifierNameNotBeforeSignature, + NodeKind::IdentifierNameQualifierWithoutParams, + NodeKind::IdentifierNameMaybeBeforeSignature); + return; } context.ReturnErrorOnState(); context.AddInvalidParse(*context.position()); diff --git a/toolchain/parse/handle_expr.cpp b/toolchain/parse/handle_expr.cpp index 2542de0b6cc8..f314975b829e 100644 --- a/toolchain/parse/handle_expr.cpp +++ b/toolchain/parse/handle_expr.cpp @@ -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, diff --git a/toolchain/parse/handle_form_literal.cpp b/toolchain/parse/handle_form_literal.cpp index 936c1d0056f6..ada20ee2451b 100644 --- a/toolchain/parse/handle_form_literal.cpp +++ b/toolchain/parse/handle_form_literal.cpp @@ -40,7 +40,8 @@ auto HandlePrimitiveForm(Context& context) -> void { } else { CARBON_DIAGNOSTIC(ExpectedCategoryModifier, Error, "expected `ref`, `var`, or `val` after `form(`"); - context.emitter().Emit(*context.position(), ExpectedCategoryModifier); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedCategoryModifier); state.has_error = true; } context.PushState(state, StateKind::PrimitiveFormFinish); diff --git a/toolchain/parse/handle_friend.cpp b/toolchain/parse/handle_friend.cpp index 23df932082b2..51618a66f905 100644 --- a/toolchain/parse/handle_friend.cpp +++ b/toolchain/parse/handle_friend.cpp @@ -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/parse/context.h" #include "toolchain/parse/handle.h" @@ -18,13 +19,29 @@ auto HandleFriendDecl(Context& context) -> void { if (!context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Identifier, NodeKind::IdentifierNameExpr)) { - state.has_error = true; - CARBON_DIAGNOSTIC(ExpectedIdentifierForFriend, Error, - "expected identifier for friend declaration"); - context.emitter().Emit(*context.position(), ExpectedIdentifierForFriend); - context.RecoverFromDeclError(state, NodeKind::FriendDecl, - /*skip_past_likely_end=*/true); - return; + // A word that can't be a name, followed by the `;`, was most likely meant + // as the name, and is what the message is about. Otherwise the name is + // missing, and goes after `friend`. + bool recover_as_raw = + context.PositionKind().is_word() && + context.PositionKind(Lookahead::NextToken) == Lex::TokenKind::Semi; + CARBON_DIAGNOSTIC( + ExpectedIdentifierForFriend, Error, + "expected identifier for friend declaration" + "{0:; prefix reserved word with `r#` to form a valid identifier|}", + Diagnostics::BoolAsSelect); + context.emitter().Emit(recover_as_raw ? DiagnosticLoc(*context.position()) + : context.PointAfterPreviousToken(), + ExpectedIdentifierForFriend, recover_as_raw); + if (!recover_as_raw) { + state.has_error = true; + context.RecoverFromDeclError(state, NodeKind::FriendDecl, + /*skip_past_likely_end=*/true); + return; + } + context.AddLeafNode(NodeKind::IdentifierNameExpr, + context.tokens().AddPostLexingRecoveryTokenAsIdentifier( + context.Consume())); } context.AddNodeExpectingDeclSemi(state, NodeKind::FriendDecl, Lex::TokenKind::Friend, diff --git a/toolchain/parse/handle_function.cpp b/toolchain/parse/handle_function.cpp index 50d343b80318..0e3266f92e31 100644 --- a/toolchain/parse/handle_function.cpp +++ b/toolchain/parse/handle_function.cpp @@ -89,7 +89,8 @@ auto HandleFunctionSignatureFinish(Context& context) -> void { NodeKind::BuiltinName)) { CARBON_DIAGNOSTIC(ExpectedBuiltinName, Error, "expected builtin function name after `=`"); - context.emitter().Emit(*context.position(), ExpectedBuiltinName); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedBuiltinName); state.has_error = true; } auto semi = context.ConsumeIf(Lex::TokenKind::Semi); diff --git a/toolchain/parse/handle_if_expr.cpp b/toolchain/parse/handle_if_expr.cpp index 9e5e3d8a111c..eb962ed77b4c 100644 --- a/toolchain/parse/handle_if_expr.cpp +++ b/toolchain/parse/handle_if_expr.cpp @@ -22,7 +22,8 @@ auto HandleIfExprFinishCondition(Context& context) -> void { CARBON_DIAGNOSTIC(ExpectedThenAfterIf, Error, "expected `then` after `if` condition"); if (!state.has_error) { - context.emitter().Emit(*context.position(), ExpectedThenAfterIf); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedThenAfterIf); } // Add invalid nodes to substitute for `IfExprThen` and the final `Expr`. context.AddInvalidParse(*context.position()); @@ -45,7 +46,8 @@ auto HandleIfExprFinishThen(Context& context) -> void { CARBON_DIAGNOSTIC(ExpectedElseAfterIf, Error, "expected `else` after `if ... then ...`"); if (!state.has_error) { - context.emitter().Emit(*context.position(), ExpectedElseAfterIf); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedElseAfterIf); } // Add an invalid node to substitute for the final `Expr`. context.AddInvalidParse(*context.position()); diff --git a/toolchain/parse/handle_impl.cpp b/toolchain/parse/handle_impl.cpp index aa03a3e7cc5b..c4e812f78cb6 100644 --- a/toolchain/parse/handle_impl.cpp +++ b/toolchain/parse/handle_impl.cpp @@ -34,7 +34,8 @@ auto HandleImplAfterIntroducer(Context& context) -> void { } else { CARBON_DIAGNOSTIC(ImplExpectedAfterForall, Error, "expected `[` after `forall` in `impl` declaration"); - context.emitter().Emit(*context.position(), ImplExpectedAfterForall); + context.emitter().Emit(context.OpeningBracketInsertionPoint(), + ImplExpectedAfterForall); context.ReturnErrorOnState(); // If we aren't producing a node from the PatternListAsImplicit state, // we still need to create a node to be the child of the `ImplForall` @@ -69,7 +70,7 @@ auto HandleImplBeforeAs(Context& context) -> void { if (!state.has_error) { CARBON_DIAGNOSTIC(ImplExpectedAs, Error, "expected `as` in `impl` declaration"); - context.emitter().Emit(*context.position(), ImplExpectedAs); + context.emitter().Emit(context.PointAfterPreviousToken(), ImplExpectedAs); } context.ReturnErrorOnState(); } diff --git a/toolchain/parse/handle_import_and_package.cpp b/toolchain/parse/handle_import_and_package.cpp index c1ab5b4c59e2..086603bbb2af 100644 --- a/toolchain/parse/handle_import_and_package.cpp +++ b/toolchain/parse/handle_import_and_package.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/base/shared_value_stores.h" +#include "toolchain/diagnostics/format_providers.h" #include "toolchain/lex/token_kind.h" #include "toolchain/lex/tokenized_buffer.h" #include "toolchain/parse/context.h" @@ -19,27 +20,27 @@ static auto OnParseError(Context& context, Context::State state, /*has_error=*/true); } -// Determines whether the specified modifier appears within the introducer of -// the given declaration. +// Finds the specified modifier within the introducer of the given declaration, +// returning `None` when it isn't there. // TODO: Restructure how we handle packaging declarations to avoid the need to // do this. -static auto HasModifier(Context& context, Context::State state, - Lex::TokenKind modifier) -> bool { +static auto FindModifier(Context& context, Context::State state, + Lex::TokenKind modifier) -> Lex::TokenIndex { for (Lex::TokenIterator it(state.token); it != context.position(); ++it) { if (context.tokens().GetKind(*it) == modifier) { - return true; + return *it; } } - return false; + return Lex::TokenIndex::None; } // Handles everything after the declaration's introducer. template static auto HandleDeclContent(Context& context, Context::State state, - bool is_export, bool is_impl, + Lex::TokenIndex export_token, bool is_impl, llvm::function_refvoid> on_parse_error) -> void { - Tree::PackagingNames names = {.is_export = is_export}; + Tree::PackagingNames names = {.is_export = export_token.has_value()}; // Parse the package name. if (DeclKind == NodeKind::LibraryDecl || @@ -50,7 +51,6 @@ static auto HandleDeclContent(Context& context, Context::State state, } else { // We require a package name. This is either an identifier or package name // keyword. - auto package_name_position = *context.position(); if (auto ident = context.ConsumeIf(Lex::TokenKind::Identifier)) { names.package_id = PackageNameId::ForIdentifier(context.tokens().GetIdentifier(*ident)); @@ -62,16 +62,38 @@ static auto HandleDeclContent(Context& context, Context::State state, names.package_id = PackageNameId::Cpp; context.AddLeafNode(NodeKind::CppPackageName, *cpp); } else { - CARBON_DIAGNOSTIC(ExpectedIdentifierAfterPackage, Error, - "expected identifier after `package`"); - CARBON_DIAGNOSTIC(ExpectedIdentifierAfterImport, Error, - "expected identifier or `library` after `import`"); - context.emitter().Emit(package_name_position, + // A word that can't be a name, followed by a token that can follow one, + // was most likely meant as the name, and is what the message is about. + // Otherwise the name is missing, and goes after the token before it. + bool recover_as_raw = + context.PositionKind().is_word() && + context.PositionKind(Lookahead::NextToken) + .IsOneOf({Lex::TokenKind::Semi, Lex::TokenKind::Library}); + CARBON_DIAGNOSTIC( + ExpectedIdentifierAfterPackage, Error, + "expected identifier after `package`" + "{0:; prefix reserved word with `r#` to form a valid identifier|}", + Diagnostics::BoolAsSelect); + CARBON_DIAGNOSTIC( + ExpectedIdentifierAfterImport, Error, + "expected identifier or `library` after `import`" + "{0:; prefix reserved word with `r#` to form a valid identifier|}", + Diagnostics::BoolAsSelect); + context.emitter().Emit(recover_as_raw ? DiagnosticLoc(*context.position()) + : context.PointAfterPreviousToken(), DeclKind == NodeKind::PackageDecl ? ExpectedIdentifierAfterPackage - : ExpectedIdentifierAfterImport); - on_parse_error(); - return; + : ExpectedIdentifierAfterImport, + recover_as_raw); + if (!recover_as_raw) { + on_parse_error(); + return; + } + auto ident = context.tokens().AddPostLexingRecoveryTokenAsIdentifier( + context.Consume()); + names.package_id = + PackageNameId::ForIdentifier(context.tokens().GetIdentifier(ident)); + context.AddLeafNode(NodeKind::IdentifierPackageName, ident); } if (names.is_export) { @@ -80,7 +102,7 @@ static auto HandleDeclContent(Context& context, Context::State state, CARBON_DIAGNOSTIC(ExportImportPackage, Error, "`export` cannot be used when importing a package"); - context.emitter().Emit(package_name_position, ExportImportPackage); + context.emitter().Emit(export_token, ExportImportPackage); } } @@ -114,7 +136,8 @@ static auto HandleDeclContent(Context& context, Context::State state, if (!inline_body_token) { CARBON_DIAGNOSTIC(ExpectedStringAfterInline, Error, "expected string literal after `inline`"); - context.emitter().Emit(body_position, ExpectedStringAfterInline); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedStringAfterInline); on_parse_error(); return; } @@ -125,7 +148,8 @@ static auto HandleDeclContent(Context& context, Context::State state, // before the library name. CARBON_DIAGNOSTIC(MissingLibraryKeyword, Error, "missing `library` keyword"); - context.emitter().Emit(*context.position(), MissingLibraryKeyword); + context.emitter().Emit(context.PointAfterPreviousToken(), + MissingLibraryKeyword); on_parse_error(); return; } @@ -203,12 +227,12 @@ auto HandleImport(Context& context) -> void { if (VerifyInImports(context, state.token)) { // Scan the modifiers to see if this import declaration is exported. - bool is_export = HasModifier(context, state, Lex::TokenKind::Export); - if (is_export) { + auto export_token = FindModifier(context, state, Lex::TokenKind::Export); + if (export_token.has_value()) { RestrictExportToApi(context, state); } - HandleDeclContent(context, state, is_export, + HandleDeclContent(context, state, export_token, /*is_impl=*/false, on_parse_error); } else { on_parse_error(); @@ -240,7 +264,7 @@ static auto HandlePackageAndLibraryDecls(Context& context, -> void { auto state = context.PopState(); - bool is_impl = HasModifier(context, state, Lex::TokenKind::Impl); + bool is_impl = FindModifier(context, state, Lex::TokenKind::Impl).has_value(); auto on_parse_error = [&] { OnParseError(context, state, DeclKind); }; @@ -262,7 +286,8 @@ static auto HandlePackageAndLibraryDecls(Context& context, // `package`/`library` is no longer allowed, but `import` may repeat. context.set_packaging_state(Context::PackagingState::InImports); - HandleDeclContent(context, state, /*is_export=*/false, is_impl, + HandleDeclContent(context, state, + /*export_token=*/Lex::TokenIndex::None, is_impl, on_parse_error); } diff --git a/toolchain/parse/handle_inline_decl.cpp b/toolchain/parse/handle_inline_decl.cpp index 2a6ca422ed59..a4305a8d51d7 100644 --- a/toolchain/parse/handle_inline_decl.cpp +++ b/toolchain/parse/handle_inline_decl.cpp @@ -18,7 +18,8 @@ auto HandleInlineDeclAfterIntroducer(Context& context) -> void { } else { CARBON_DIAGNOSTIC(ExpectedCppAfterInline, Error, "expected `Cpp` after `inline`"); - context.emitter().Emit(*context.position(), ExpectedCppAfterInline); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedCppAfterInline); context.AddNode(NodeKind::InlineCppDecl, context.SkipPastLikelyEnd(state.token), /*has_error=*/true); @@ -33,7 +34,8 @@ auto HandleInlineDeclAfterIntroducer(Context& context) -> void { } else { CARBON_DIAGNOSTIC(ExpectedStringAfterInlineCpp, Error, "expected string literal after `inline Cpp`"); - context.emitter().Emit(*context.position(), ExpectedStringAfterInlineCpp); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedStringAfterInlineCpp); context.AddNode(NodeKind::InlineCppDecl, context.SkipPastLikelyEnd(state.token), /*has_error=*/true); diff --git a/toolchain/parse/handle_lambda.cpp b/toolchain/parse/handle_lambda.cpp index e67123416d9f..594b1ad95c30 100644 --- a/toolchain/parse/handle_lambda.cpp +++ b/toolchain/parse/handle_lambda.cpp @@ -60,12 +60,13 @@ static auto ParseLambdaBody(Context& context, Context::State state, if (has_return_type) { CARBON_DIAGNOSTIC(ExpectedLambdaBodyAfterReturnType, Error, "expected `{{` after return type"); - context.emitter().Emit(*context.position(), + context.emitter().Emit(context.PointAfterPreviousToken(), ExpectedLambdaBodyAfterReturnType); } else { CARBON_DIAGNOSTIC(ExpectedLambdaBody, Error, "expected `->`, `=>`, or `{{`"); - context.emitter().Emit(*context.position(), ExpectedLambdaBody); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedLambdaBody); } // Bundle everything into a complete lambda node for error recovery -- diff --git a/toolchain/parse/handle_let.cpp b/toolchain/parse/handle_let.cpp index 413a08d2d842..ee19a3284bef 100644 --- a/toolchain/parse/handle_let.cpp +++ b/toolchain/parse/handle_let.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/diagnostics/diagnostic.h" +#include "toolchain/diagnostics/format_providers.h" #include "toolchain/lex/token_kind.h" #include "toolchain/parse/context.h" #include "toolchain/parse/handle.h" @@ -41,21 +42,37 @@ auto HandleAssociatedConstant(Context& context) -> void { "phase keyword is not allowed on an associated constant"); context.emitter().Emit(*context.position(), PhaseKeywordInAssociatedConstant); + state.has_error = true; } else { + // A word that can't be a name, followed by the `:`, was most likely + // meant as the name, and is what the message is about. Otherwise the + // name is missing, and goes after `let`. + bool recover_as_raw = + kind.is_word() && + context.PositionKind(Lookahead::NextToken) == Lex::TokenKind::Colon; CARBON_DIAGNOSTIC( ExpectedAssociatedConstantIdentifier, Error, - "expected identifier in associated constant declaration"); - context.emitter().Emit(*context.position(), - ExpectedAssociatedConstantIdentifier); + "expected identifier in associated constant declaration" + "{0:; prefix reserved word with `r#` to form a valid identifier|}", + Diagnostics::BoolAsSelect); + context.emitter().Emit(recover_as_raw ? DiagnosticLoc(*context.position()) + : context.PointAfterPreviousToken(), + ExpectedAssociatedConstantIdentifier, + recover_as_raw); + if (recover_as_raw) { + identifier = context.tokens().AddPostLexingRecoveryTokenAsIdentifier( + context.Consume()); + } else { + state.has_error = true; + } } - state.has_error = true; } auto colon = context.ConsumeIf(Lex::TokenKind::Colon); if (identifier && !colon) { CARBON_DIAGNOSTIC(ExpectedAssociatedConstantColon, Error, "expected `:` in associated constant declaration"); - context.emitter().Emit(*context.position(), + context.emitter().Emit(context.PointAfterPreviousToken(), ExpectedAssociatedConstantColon); state.has_error = true; } diff --git a/toolchain/parse/handle_match.cpp b/toolchain/parse/handle_match.cpp index 2e9aa23d4b81..18e660e5de9f 100644 --- a/toolchain/parse/handle_match.cpp +++ b/toolchain/parse/handle_match.cpp @@ -15,7 +15,8 @@ static auto HandleMatchHandlerStart(Context& context, NodeKind label_kind) if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedMatchCaseArrow, Error, "expected `=>` introducing statement block"); - context.emitter().Emit(*context.position(), ExpectedMatchCaseArrow); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedMatchCaseArrow); } context.AddNode(label_kind, *context.position(), /*has_error=*/true); @@ -33,7 +34,8 @@ static auto HandleMatchHandlerStart(Context& context, NodeKind label_kind) if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedMatchCaseBlock, Error, "expected `{{` after `=>`"); - context.emitter().Emit(*context.position(), ExpectedMatchCaseBlock); + context.emitter().Emit(context.OpeningBracketInsertionPoint(), + ExpectedMatchCaseBlock); } context.AddNode(NodeKind::MatchHandlerStart, *context.position(), @@ -77,7 +79,8 @@ auto HandleMatchConditionFinish(Context& context) -> void { if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedMatchCasesBlock, Error, "expected `{{` starting block with cases"); - context.emitter().Emit(*context.position(), ExpectedMatchCasesBlock); + context.emitter().Emit(context.OpeningBracketInsertionPoint(), + ExpectedMatchCasesBlock); } context.AddNode(NodeKind::MatchStatementStart, *context.position(), @@ -94,7 +97,8 @@ auto HandleMatchConditionFinish(Context& context) -> void { state.has_error = false; if (context.PositionIs(Lex::TokenKind::CloseCurlyBrace)) { CARBON_DIAGNOSTIC(ExpectedMatchCases, Error, "expected cases"); - context.emitter().Emit(*context.position(), ExpectedMatchCases); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedMatchCases); state.has_error = true; } @@ -174,7 +178,7 @@ auto HandleMatchCaseAfterPattern(Context& context) -> void { if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedMatchCaseGuardOpenParen, Error, "expected `(` after `if`"); - context.emitter().Emit(*context.position(), + context.emitter().Emit(context.OpeningBracketInsertionPoint(), ExpectedMatchCaseGuardOpenParen); } @@ -207,7 +211,7 @@ auto HandleMatchCaseGuardFinish(Context& context) -> void { if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedMatchCaseGuardCloseParen, Error, "expected `)`"); - context.emitter().Emit(*context.position(), + context.emitter().Emit(context.PointAfterPreviousToken(), ExpectedMatchCaseGuardCloseParen); } diff --git a/toolchain/parse/handle_observe.cpp b/toolchain/parse/handle_observe.cpp index e456fcc417d4..ed689f176adc 100644 --- a/toolchain/parse/handle_observe.cpp +++ b/toolchain/parse/handle_observe.cpp @@ -38,7 +38,8 @@ auto HandleObserveOperator(Context& context) -> void { if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedObserveOperator, Error, "observe should use `==` or `impls` operator"); - context.emitter().Emit(*context.position(), ExpectedObserveOperator); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedObserveOperator); } context.RecoverFromDeclError(state, NodeKind::ObserveDecl, /*skip_past_likely_end=*/true); diff --git a/toolchain/parse/handle_paren_expr.cpp b/toolchain/parse/handle_paren_expr.cpp index b3cef4d87a78..d301b4649017 100644 --- a/toolchain/parse/handle_paren_expr.cpp +++ b/toolchain/parse/handle_paren_expr.cpp @@ -31,7 +31,7 @@ auto HandleOnlyParenExprFinish(Context& context) -> void { if (!state.has_error) { CARBON_DIAGNOSTIC(UnexpectedTokenInCompoundMemberAccess, Error, "expected `)`"); - context.emitter().Emit(*context.position(), + context.emitter().Emit(context.PointAfterPreviousToken(), UnexpectedTokenInCompoundMemberAccess); state.has_error = true; } diff --git a/toolchain/parse/handle_pattern.cpp b/toolchain/parse/handle_pattern.cpp index 274a656f35aa..8e1e00d8d1a0 100644 --- a/toolchain/parse/handle_pattern.cpp +++ b/toolchain/parse/handle_pattern.cpp @@ -76,11 +76,12 @@ auto HandleExprPattern(Context& context) -> void { if (context.PositionKind().is_binding_pattern_operator()) { if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedBindingName, Error, - "unexpected expression before {0} in binding pattern", + "unexpected expression before `{0}` in binding pattern", Lex::TokenKind); - // TODO: Underline the parsed expression. - context.emitter().Emit(*context.position(), ExpectedBindingName, - context.PositionKind()); + context.emitter().Emit( + DiagnosticLoc::Tokens( + {.begin = state.token, .end = *(context.position() - 1)}), + ExpectedBindingName, context.PositionKind()); state.has_error = true; } context.Consume(); diff --git a/toolchain/parse/handle_pattern_list.cpp b/toolchain/parse/handle_pattern_list.cpp index fbcf8b3ba64f..bf874ae8d4f2 100644 --- a/toolchain/parse/handle_pattern_list.cpp +++ b/toolchain/parse/handle_pattern_list.cpp @@ -107,7 +107,7 @@ auto HandleStructPatternFieldAfterDesignator(Context& context) -> void { CARBON_DIAGNOSTIC(ExpectedStructPatternDesignatedField, Error, "expected `= value` after `.field`"); - context.emitter().Emit(*context.position(), + context.emitter().Emit(context.PointAfterPreviousToken(), ExpectedStructPatternDesignatedField); } skip_to_recovery_position(/*add_invalid_parse=*/true); diff --git a/toolchain/parse/handle_period.cpp b/toolchain/parse/handle_period.cpp index 939573329ca5..92005ef8cc3d 100644 --- a/toolchain/parse/handle_period.cpp +++ b/toolchain/parse/handle_period.cpp @@ -44,7 +44,10 @@ static auto HandlePeriodOrArrow(Context& context, NodeKind node_kind, "expected identifier after `{0:->|.}`" "{1:; prefix reserved word with `r#` to form a valid identifier|}", Diagnostics::BoolAsSelect, Diagnostics::BoolAsSelect); - context.emitter().Emit(*context.position(), + // A reserved word in the name's position is what the message is about. + // Otherwise the name is missing, and goes after the `.` or `->`. + context.emitter().Emit(recover_as_raw ? DiagnosticLoc(*context.position()) + : context.PointAfterPreviousToken(), ExpectedIdentifierAfterPeriodOrArrow, is_arrow, recover_as_raw); // If we see a word, assume it was intended to be a name. diff --git a/toolchain/parse/handle_require.cpp b/toolchain/parse/handle_require.cpp index 93c89466b02a..4e60405409b8 100644 --- a/toolchain/parse/handle_require.cpp +++ b/toolchain/parse/handle_require.cpp @@ -32,7 +32,8 @@ auto HandleRequireBeforeImpls(Context& context) -> void { if (!state.has_error) { CARBON_DIAGNOSTIC(RequireExpectedImpls, Error, "expected `impls` in `require` declaration"); - context.emitter().Emit(*context.position(), RequireExpectedImpls); + context.emitter().Emit(context.PointAfterPreviousToken(), + RequireExpectedImpls); } context.ReturnErrorOnState(); } diff --git a/toolchain/parse/handle_requirement.cpp b/toolchain/parse/handle_requirement.cpp index 77b29002f9e2..8d8ec21497c3 100644 --- a/toolchain/parse/handle_requirement.cpp +++ b/toolchain/parse/handle_requirement.cpp @@ -56,7 +56,7 @@ auto HandleRequirementOperator(Context& context) -> void { CARBON_DIAGNOSTIC( ExpectedRequirementOperator, Error, "requirement should use `impls`, `=`, or `==` operator"); - context.emitter().Emit(*context.position(), + context.emitter().Emit(context.PointAfterPreviousToken(), ExpectedRequirementOperator); } context.ReturnErrorOnState(); diff --git a/toolchain/parse/handle_statement.cpp b/toolchain/parse/handle_statement.cpp index 29ab7820fdf8..fc55a6f618a0 100644 --- a/toolchain/parse/handle_statement.cpp +++ b/toolchain/parse/handle_statement.cpp @@ -96,7 +96,8 @@ static auto HandleStatementKeywordFinish(Context& context, NodeKind node_kind) if (!semi) { CARBON_DIAGNOSTIC(ExpectedStatementSemi, Error, "`{0}` statements must end with a `;`", Lex::TokenKind); - context.emitter().Emit(*context.position(), ExpectedStatementSemi, + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedStatementSemi, context.tokens().GetKind(state.token)); state.has_error = true; // Recover to the next semicolon if possible. @@ -144,7 +145,7 @@ auto HandleStatementForHeaderIn(Context& context) -> void { end_token = context.Consume(); } else if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedIn, Error, "expected `in` after loop pattern"); - context.emitter().Emit(*context.position(), ExpectedIn); + context.emitter().Emit(context.PointAfterPreviousToken(), ExpectedIn); state.has_error = true; } diff --git a/toolchain/parse/handle_var.cpp b/toolchain/parse/handle_var.cpp index dc4e07560a00..758024bf9fe9 100644 --- a/toolchain/parse/handle_var.cpp +++ b/toolchain/parse/handle_var.cpp @@ -42,7 +42,8 @@ auto HandleVarAsReturned(Context& context) -> void { if (!context.PositionIs(Lex::TokenKind::Var)) { CARBON_DIAGNOSTIC(ExpectedVarAfterReturned, Error, "expected `var` after `returned`"); - context.emitter().Emit(*context.position(), ExpectedVarAfterReturned); + context.emitter().Emit(context.PointAfterPreviousToken(), + ExpectedVarAfterReturned); context.AddLeafNode(NodeKind::EmptyDecl, context.SkipPastLikelyEnd(returned_token), /*has_error=*/true); diff --git a/toolchain/parse/testdata/alias/fail_syntax.carbon b/toolchain/parse/testdata/alias/fail_syntax.carbon index 180cf3350edd..178bd5751cf9 100644 --- a/toolchain/parse/testdata/alias/fail_syntax.carbon +++ b/toolchain/parse/testdata/alias/fail_syntax.carbon @@ -8,11 +8,11 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/alias/fail_syntax.carbon -// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:7: error: `alias` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:6: error: `alias` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: alias = b; -// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:9: error: `alias` requires a `=` for the source [ExpectedAliasInitializer] +// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:8: error: `alias` requires a `=` for the source [ExpectedAliasInitializer] // CHECK:STDERR: alias a b; @@ -24,15 +24,15 @@ alias a =; // CHECK:STDERR: alias a; -// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:7: error: `alias` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:6: error: `alias` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: alias =; -// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:9: error: `alias` requires a `=` for the source [ExpectedAliasInitializer] +// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:8: error: `alias` requires a `=` for the source [ExpectedAliasInitializer] // CHECK:STDERR: alias a b c = d; -// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:13: error: `alias` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:12: error: `alias` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: alias a = b c d; @@ -45,15 +45,15 @@ fn F() { // CHECK:STDERR: alias a =; + // CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:12: error: expected expression [ExpectedExpr] + // CHECK:STDERR: alias a = -// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:1: error: expected expression [ExpectedExpr] -// CHECK:STDERR: } +// CHECK:STDERR: fail_syntax.carbon:[[@LINE+2]]:12: error: `alias` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: alias a = b -// CHECK:STDERR: fail_syntax.carbon:[[@LINE+54]]:26: error: `alias` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_syntax.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─AliasIntroducer 'alias' diff --git a/toolchain/parse/testdata/array/fail_syntax.carbon b/toolchain/parse/testdata/array/fail_syntax.carbon index 16c4cb6a1f67..5159d9316bf4 100644 --- a/toolchain/parse/testdata/array/fail_syntax.carbon +++ b/toolchain/parse/testdata/array/fail_syntax.carbon @@ -18,29 +18,31 @@ var x: array(i32); // --- fail_invalid_char.carbon -// CHECK:STDERR: fail_invalid_char.carbon:[[@LINE+8]]:17: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_invalid_char.carbon:[[@LINE+9]]:17: error: expected expression [ExpectedExpr] // CHECK:STDERR: -// CHECK:STDERR: fail_invalid_char.carbon:[[@LINE+6]]:17: error: expected `,` in `array(Type, Count)` [ExpectedArrayComma] +// CHECK:STDERR: fail_invalid_char.carbon:[[@LINE+7]]:17: error: expected `,` in `array(Type, Count)` [ExpectedArrayComma] // CHECK:STDERR: -// CHECK:STDERR: fail_invalid_char.carbon:[[@LINE+4]]:17: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_invalid_char.carbon:[[@LINE+5]]:17: error: expected expression [ExpectedExpr] // CHECK:STDERR: -// CHECK:STDERR: fail_invalid_char.carbon:[[@LINE+2]]:17: error: unexpected tokens before `)` [ExpectedCloseSymbol] +// CHECK:STDERR: fail_invalid_char.carbon:[[@LINE+3]]:17: error: unexpected tokens before `)` [ExpectedCloseSymbol] +// CHECK:STDERR: fail_invalid_char.carbon:[[@LINE+2]]:16: note: matching this `(` [GroupOpenedHere] // CHECK:STDERR: fn X() -> array(:); // --- fail_unlexed_expr.carbon -// CHECK:STDERR: fail_unlexed_expr.carbon:[[@LINE+6]]:14: error: encountered unrecognized characters while parsing [UnrecognizedCharacters] +// CHECK:STDERR: fail_unlexed_expr.carbon:[[@LINE+7]]:14: error: encountered unrecognized characters while parsing [UnrecognizedCharacters] // CHECK:STDERR: -// CHECK:STDERR: fail_unlexed_expr.carbon:[[@LINE+4]]:14: error: expected `,` in `array(Type, Count)` [ExpectedArrayComma] +// CHECK:STDERR: fail_unlexed_expr.carbon:[[@LINE+5]]:14: error: expected `,` in `array(Type, Count)` [ExpectedArrayComma] // CHECK:STDERR: -// CHECK:STDERR: fail_unlexed_expr.carbon:[[@LINE+2]]:14: error: unexpected tokens before `)` [ExpectedCloseSymbol] +// CHECK:STDERR: fail_unlexed_expr.carbon:[[@LINE+3]]:14: error: unexpected tokens before `)` [ExpectedCloseSymbol] +// CHECK:STDERR: fail_unlexed_expr.carbon:[[@LINE+2]]:13: note: matching this `(` [GroupOpenedHere] // CHECK:STDERR: var y: array(`); // --- fail_no_open_paren.carbon -// CHECK:STDERR: fail_no_open_paren.carbon:[[@LINE+2]]:14-16: error: expected `(` after `array` [ExpectedParenAfter] +// CHECK:STDERR: fail_no_open_paren.carbon:[[@LINE+2]]:14: error: expected `(` after `array` [ExpectedParenAfter] // CHECK:STDERR: var x: array i32, 1; diff --git a/toolchain/parse/testdata/basics/fail_missing_token_locations.carbon b/toolchain/parse/testdata/basics/fail_missing_token_locations.carbon new file mode 100644 index 000000000000..46a7ab445eb2 --- /dev/null +++ b/toolchain/parse/testdata/basics/fail_missing_token_locations.carbon @@ -0,0 +1,95 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/basics/fail_missing_token_locations.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/basics/fail_missing_token_locations.carbon + +// --- fail_semi_before_comment.carbon + +// The `;` goes after the `1`, before the spaces and the comment. +// CHECK:STDERR: fail_semi_before_comment.carbon:[[@LINE+2]]:15: error: `var` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: +var x: i32 = 1 // The value. +var y: i32 = 2; + +// --- fail_expr_at_line_end.carbon + +fn F() { + // CHECK:STDERR: fail_expr_at_line_end.carbon:[[@LINE+4]]:15: error: expected expression [ExpectedExpr] + // CHECK:STDERR: + // CHECK:STDERR: fail_expr_at_line_end.carbon:[[@LINE+2]]:15: error: `var` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: + var x: i32 = + var y: i32 = 2; +} + +// --- fail_expr_at_statement_start.carbon + +// The statement starts at the `=`, so the missing expression goes there rather +// than at the end of the line before. +fn F() { + var x: i32 = 1; + // CHECK:STDERR: fail_expr_at_statement_start.carbon:[[@LINE+2]]:3: error: expected expression [ExpectedExpr] + // CHECK:STDERR: + = 3; +} + +// CHECK:STDOUT: - filename: fail_semi_before_comment.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ ├─IntTypeLiteral 'i32' +// CHECK:STDOUT: │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ ├─VariablePattern 'var' +// CHECK:STDOUT: │ ├─VariableInitializer '=' +// CHECK:STDOUT: │ ├─IntLiteral '1' +// CHECK:STDOUT: ├─VariableDecl ';' has_error +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_expr_at_line_end.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' +// CHECK:STDOUT: │ ├─IdentifierNameMaybeBeforeSignature 'F' +// CHECK:STDOUT: │ │ ╭─ExplicitParamListStart '(' +// CHECK:STDOUT: │ ├─ExplicitParamList ')' +// CHECK:STDOUT: │ ╭─FunctionDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ │ ├─IntTypeLiteral 'i32' +// CHECK:STDOUT: │ │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ │ ├─VariablePattern 'var' +// CHECK:STDOUT: │ │ ├─VariableInitializer '=' +// CHECK:STDOUT: │ │ ├─InvalidParse 'var' has_error +// CHECK:STDOUT: │ ├─VariableDecl ';' has_error +// CHECK:STDOUT: ├─FunctionDefinition '}' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_expr_at_statement_start.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' +// CHECK:STDOUT: │ ├─IdentifierNameMaybeBeforeSignature 'F' +// CHECK:STDOUT: │ │ ╭─ExplicitParamListStart '(' +// CHECK:STDOUT: │ ├─ExplicitParamList ')' +// CHECK:STDOUT: │ ╭─FunctionDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ │ ├─IntTypeLiteral 'i32' +// CHECK:STDOUT: │ │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ │ ├─VariablePattern 'var' +// CHECK:STDOUT: │ │ ├─VariableInitializer '=' +// CHECK:STDOUT: │ │ ├─IntLiteral '1' +// CHECK:STDOUT: │ ├─VariableDecl ';' +// CHECK:STDOUT: │ │ ╭─InvalidParse '=' has_error +// CHECK:STDOUT: │ │ ├─IntLiteral '3' +// CHECK:STDOUT: │ │ ╭─InfixOperatorEqual '=' has_error +// CHECK:STDOUT: │ ├─ExprStatement ';' +// CHECK:STDOUT: ├─FunctionDefinition '}' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) diff --git a/toolchain/parse/testdata/basics/fail_paren_match_regression.carbon b/toolchain/parse/testdata/basics/fail_paren_match_regression.carbon index e2d4c8a6b93a..a409f6338228 100644 --- a/toolchain/parse/testdata/basics/fail_paren_match_regression.carbon +++ b/toolchain/parse/testdata/basics/fail_paren_match_regression.carbon @@ -8,14 +8,14 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/basics/fail_paren_match_regression.carbon -// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+4]]:5: error: expected pattern [ExpectedPattern] +// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+6]]:4: error: expected pattern [ExpectedPattern] // CHECK:STDERR: -// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+2]]:12: error: expected `,` or `)` [UnexpectedTokenAfterListElement] +// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+4]]:11: error: expected `,` or `)` [UnexpectedTokenAfterListElement] +// CHECK:STDERR: +// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+2]]:15: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var = (foo {}) -// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+13]]:26: error: `var` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_paren_match_regression.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─VariableIntroducer 'var' diff --git a/toolchain/parse/testdata/basics/form_literals.carbon b/toolchain/parse/testdata/basics/form_literals.carbon index 134cf9724b0c..c987437c4801 100644 --- a/toolchain/parse/testdata/basics/form_literals.carbon +++ b/toolchain/parse/testdata/basics/form_literals.carbon @@ -29,25 +29,27 @@ fn F() { // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:8: error: expected expression [ExpectedExpr] // CHECK:STDERR: form(); - // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:8-10: error: expected `ref`, `var`, or `val` after `form(` [ExpectedCategoryModifier] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:8: error: expected `ref`, `var`, or `val` after `form(` [ExpectedCategoryModifier] // CHECK:STDERR: form(i32); - // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:8-12: error: expected `ref`, `var`, or `val` after `form(` [ExpectedCategoryModifier] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+5]]:8: error: expected `ref`, `var`, or `val` after `form(` [ExpectedCategoryModifier] // CHECK:STDERR: - // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:14-16: error: unexpected tokens before `)` [ExpectedCloseSymbol] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+3]]:14-28: error: unexpected tokens before `)` [ExpectedCloseSymbol] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:7: note: matching this `(` [GroupOpenedHere] // CHECK:STDERR: form(whole lot of nonsense); - // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:18-20: error: unexpected tokens before `)` [ExpectedCloseSymbol] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+3]]:18-32: error: unexpected tokens before `)` [ExpectedCloseSymbol] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:7: note: matching this `(` [GroupOpenedHere] // CHECK:STDERR: form(var whole lot of nonsense); - // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:8-12: error: expected `(` after `form` [ExpectedParenAfter] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:8: error: expected `(` after `form` [ExpectedParenAfter] // CHECK:STDERR: - // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:8-12: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:7: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: form whole lot of nonsense; - // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:8-10: error: expected `(` after `form` [ExpectedParenAfter] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:8: error: expected `(` after `form` [ExpectedParenAfter] // CHECK:STDERR: - // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:8-10: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+2]]:7: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: form var whole lot of nonsense; } diff --git a/toolchain/parse/testdata/basics/multiline_string_recovery.carbon b/toolchain/parse/testdata/basics/multiline_string_recovery.carbon index c8f6a37dd432..b00250517b89 100644 --- a/toolchain/parse/testdata/basics/multiline_string_recovery.carbon +++ b/toolchain/parse/testdata/basics/multiline_string_recovery.carbon @@ -18,7 +18,7 @@ fn F() { // The error skips past the string literal's group and the tokens after the // closing delimiter, ending the declaration at the `;` so that the following // declaration parses cleanly. - // CHECK:STDERR: fail_string_in_skipped_group.carbon:[[@LINE+2]]:20-22: error: `var` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: fail_string_in_skipped_group.carbon:[[@LINE+2]]:19: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var x: i32 = bad bad (''' str diff --git a/toolchain/parse/testdata/choice/fail_keyword_alternative_name.carbon b/toolchain/parse/testdata/choice/fail_keyword_alternative_name.carbon new file mode 100644 index 000000000000..155665aec6c3 --- /dev/null +++ b/toolchain/parse/testdata/choice/fail_keyword_alternative_name.carbon @@ -0,0 +1,28 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/choice/fail_keyword_alternative_name.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/choice/fail_keyword_alternative_name.carbon + +choice C { + // CHECK:STDERR: fail_keyword_alternative_name.carbon:[[@LINE+2]]:3-7: error: expected choice alternative name; prefix reserved word with `r#` to form a valid identifier [ExpectedChoiceAlternativeName] + // CHECK:STDERR: + class, + Other +} + +// CHECK:STDOUT: - filename: fail_keyword_alternative_name.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─ChoiceIntroducer 'choice' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'C' +// CHECK:STDOUT: │ ╭─ChoiceDefinitionStart '{' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'class' +// CHECK:STDOUT: │ ├─ChoiceAlternativeListComma ',' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'Other' +// CHECK:STDOUT: ├─ChoiceDefinition '}' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) diff --git a/toolchain/parse/testdata/class/adapt.carbon b/toolchain/parse/testdata/class/adapt.carbon index ef2b35e9f2da..545375ea4af6 100644 --- a/toolchain/parse/testdata/class/adapt.carbon +++ b/toolchain/parse/testdata/class/adapt.carbon @@ -36,7 +36,7 @@ adapt i32; // --- fail_bad_syntax.carbon class A { - // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:11-24: error: `adapt` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:10: error: `adapt` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: adapt 1 trailing_token; } @@ -48,13 +48,13 @@ class B { } class C { + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:8: error: expected expression [ExpectedExpr] + // CHECK:STDERR: adapt -// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:1: error: expected expression [ExpectedExpr] -// CHECK:STDERR: } class D { - // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:9-13: error: expected expression [ExpectedExpr] + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:8: error: expected expression [ExpectedExpr] // CHECK:STDERR: adapt class; } diff --git a/toolchain/parse/testdata/class/fail_base.carbon b/toolchain/parse/testdata/class/fail_base.carbon index b98a4b5db6d2..f831eb8b804f 100644 --- a/toolchain/parse/testdata/class/fail_base.carbon +++ b/toolchain/parse/testdata/class/fail_base.carbon @@ -13,25 +13,25 @@ class A { // CHECK:STDERR: base; - // CHECK:STDERR: fail_base.carbon:[[@LINE+2]]:8-12: error: `class` or `:` expected after `base` [ExpectedAfterBase] + // CHECK:STDERR: fail_base.carbon:[[@LINE+2]]:7: error: `class` or `:` expected after `base` [ExpectedAfterBase] // CHECK:STDERR: base calss X {} - // CHECK:STDERR: fail_base.carbon:[[@LINE+2]]:9: error: expected expression [ExpectedExpr] + // CHECK:STDERR: fail_base.carbon:[[@LINE+2]]:8: error: expected expression [ExpectedExpr] // CHECK:STDERR: base: ; + // CHECK:STDERR: fail_base.carbon:[[@LINE+2]]:12: error: `base` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: base: { } // We should resume parsing here after the previous error. - // CHECK:STDERR: fail_base.carbon:[[@LINE+2]]:3-5: error: `base` declarations must end with a `;` [ExpectedDeclSemi] - // CHECK:STDERR: var n: i32; } base class Foo {} class B { - // CHECK:STDERR: fail_base.carbon:[[@LINE+2]]:15-17: error: `class` or `:` expected after `base` [ExpectedAfterBase] + // CHECK:STDERR: fail_base.carbon:[[@LINE+2]]:14: error: `class` or `:` expected after `base` [ExpectedAfterBase] // CHECK:STDERR: extend base Foo; } diff --git a/toolchain/parse/testdata/class/fail_modifiers.carbon b/toolchain/parse/testdata/class/fail_modifiers.carbon index 96afbb0047ed..44f2227f6caf 100644 --- a/toolchain/parse/testdata/class/fail_modifiers.carbon +++ b/toolchain/parse/testdata/class/fail_modifiers.carbon @@ -10,20 +10,20 @@ // --- fail_virtual_class.carbon -virtual class B -// CHECK:STDERR: fail_virtual_class.carbon:[[@LINE+2]]:1: error: `virtual` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] +// CHECK:STDERR: fail_virtual_class.carbon:[[@LINE+2]]:16: error: `virtual` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] // CHECK:STDERR: +virtual class B // --- fail_impl_class.carbon -// CHECK:STDERR: fail_impl_class.carbon:[[@LINE+2]]:1-4: error: `impl` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_impl_class.carbon:[[@LINE+2]]:11: error: `impl` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: impl class // --- fail_extend_adapt_base.carbon class C { - // CHECK:STDERR: fail_extend_adapt_base.carbon:[[@LINE+2]]:16-19: error: expected expression [ExpectedExpr] + // CHECK:STDERR: fail_extend_adapt_base.carbon:[[@LINE+2]]:15: error: expected expression [ExpectedExpr] // CHECK:STDERR: extend adapt base B; } diff --git a/toolchain/parse/testdata/class/fail_var_name.carbon b/toolchain/parse/testdata/class/fail_var_name.carbon index f537dc9864fb..ee8d74385137 100644 --- a/toolchain/parse/testdata/class/fail_var_name.carbon +++ b/toolchain/parse/testdata/class/fail_var_name.carbon @@ -15,11 +15,11 @@ class C { var name; - // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:7: error: expected pattern [ExpectedPattern] + // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:6: error: expected pattern [ExpectedPattern] // CHECK:STDERR: var :; - // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:7: error: expected pattern [ExpectedPattern] + // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:6: error: expected pattern [ExpectedPattern] // CHECK:STDERR: var : C; @@ -27,17 +27,17 @@ class C { // CHECK:STDERR: var name:; - // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:12: error: `var` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:11: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var name C; - // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:15: error: `var` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:14: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var name: C C; var self: C; - // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:12-15: error: `var` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: fail_var_name.carbon:[[@LINE+2]]:11: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var name name: C; diff --git a/toolchain/parse/testdata/class/friend.carbon b/toolchain/parse/testdata/class/friend.carbon index 5fe892985987..29abf9f118bb 100644 --- a/toolchain/parse/testdata/class/friend.carbon +++ b/toolchain/parse/testdata/class/friend.carbon @@ -31,25 +31,25 @@ class A { } class B { - // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:12-22: error: `friend` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:11: error: `friend` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: friend X extra_token; } class C { + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:9: error: expected identifier for friend declaration [ExpectedIdentifierForFriend] + // CHECK:STDERR: friend -// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:1: error: expected identifier for friend declaration [ExpectedIdentifierForFriend] -// CHECK:STDERR: } class D { + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:11: error: `friend` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: friend X -// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:1: error: `friend` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: } class E { - // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:10-14: error: expected identifier for friend declaration [ExpectedIdentifierForFriend] + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:10-14: error: expected identifier for friend declaration; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierForFriend] // CHECK:STDERR: friend class; } @@ -108,7 +108,8 @@ class E { // CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'E' // CHECK:STDOUT: │ ╭─ClassDefinitionStart '{' // CHECK:STDOUT: │ │ ╭─FriendIntroducer 'friend' -// CHECK:STDOUT: │ ├─FriendDecl ';' has_error +// CHECK:STDOUT: │ │ ├─IdentifierNameExpr 'class' +// CHECK:STDOUT: │ ├─FriendDecl ';' // CHECK:STDOUT: ├─ClassDefinition '}' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) diff --git a/toolchain/parse/testdata/for/fail_missing_cond.carbon b/toolchain/parse/testdata/for/fail_missing_cond.carbon index 203134f3280c..af2a96501042 100644 --- a/toolchain/parse/testdata/for/fail_missing_cond.carbon +++ b/toolchain/parse/testdata/for/fail_missing_cond.carbon @@ -12,13 +12,13 @@ fn F() { // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+2]]:7: error: expected `(` after `for` [ExpectedParenAfter] // CHECK:STDERR: for { + // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+6]]:4: error: expected `in` after loop pattern [ExpectedIn] + // CHECK:STDERR: + // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+4]]:4: error: expected expression [ExpectedExpr] + // CHECK:STDERR: + // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+2]]:4: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: } -// CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+8]]:1: error: expected `in` after loop pattern [ExpectedIn] -// CHECK:STDERR: -// CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+6]]:1: error: expected expression [ExpectedExpr] -// CHECK:STDERR: -// CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+4]]:1: error: expected braced code block [ExpectedCodeBlock] -// CHECK:STDERR: // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+2]]:1: error: expected expression [ExpectedExpr] // CHECK:STDERR: } diff --git a/toolchain/parse/testdata/for/fail_missing_in.carbon b/toolchain/parse/testdata/for/fail_missing_in.carbon index 6ab33acfdcb7..f518f8f2e526 100644 --- a/toolchain/parse/testdata/for/fail_missing_in.carbon +++ b/toolchain/parse/testdata/for/fail_missing_in.carbon @@ -9,7 +9,7 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/for/fail_missing_in.carbon fn foo() { - // CHECK:STDERR: fail_missing_in.carbon:[[@LINE+2]]:19: error: expected `in` after loop pattern [ExpectedIn] + // CHECK:STDERR: fail_missing_in.carbon:[[@LINE+2]]:18: error: expected `in` after loop pattern [ExpectedIn] // CHECK:STDERR: for (var x: i32 y) { Print(x); diff --git a/toolchain/parse/testdata/for/fail_returned_var.carbon b/toolchain/parse/testdata/for/fail_returned_var.carbon index 2c670c67b8b8..b1de2950bd98 100644 --- a/toolchain/parse/testdata/for/fail_returned_var.carbon +++ b/toolchain/parse/testdata/for/fail_returned_var.carbon @@ -9,7 +9,7 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/for/fail_returned_var.carbon fn foo() -> i32 { - // CHECK:STDERR: fail_returned_var.carbon:[[@LINE+2]]:8-15: error: expected pattern [ExpectedPattern] + // CHECK:STDERR: fail_returned_var.carbon:[[@LINE+2]]:8: error: expected pattern [ExpectedPattern] // CHECK:STDERR: for (returned var x: i32 in y) { return var; diff --git a/toolchain/parse/testdata/for/fail_square_brackets.carbon b/toolchain/parse/testdata/for/fail_square_brackets.carbon index 3d3571849a9c..43fe2d96100b 100644 --- a/toolchain/parse/testdata/for/fail_square_brackets.carbon +++ b/toolchain/parse/testdata/for/fail_square_brackets.carbon @@ -11,9 +11,9 @@ fn F() { // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+10]]:7: error: expected `(` after `for` [ExpectedParenAfter] // CHECK:STDERR: - // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+8]]:7: error: expected pattern [ExpectedPattern] + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+8]]:6: error: expected pattern [ExpectedPattern] // CHECK:STDERR: - // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+6]]:7: error: expected expression [ExpectedExpr] + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+6]]:6: error: expected expression [ExpectedExpr] // CHECK:STDERR: // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+4]]:7: error: expected braced code block [ExpectedCodeBlock] // CHECK:STDERR: diff --git a/toolchain/parse/testdata/function/declaration.carbon b/toolchain/parse/testdata/function/declaration.carbon index 8e89c269b8eb..55b93c391ef4 100644 --- a/toolchain/parse/testdata/function/declaration.carbon +++ b/toolchain/parse/testdata/function/declaration.carbon @@ -45,7 +45,7 @@ private override default fn I(); // --- fail_identifier_instead_of_sig.carbon -// CHECK:STDERR: fail_identifier_instead_of_sig.carbon:[[@LINE+2]]:8-10: error: `fn` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] +// CHECK:STDERR: fail_identifier_instead_of_sig.carbon:[[@LINE+2]]:7: error: `fn` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] // CHECK:STDERR: fn foo bar; @@ -58,15 +58,15 @@ fn Div[(); // --- fail_missing_name.carbon -// CHECK:STDERR: fail_missing_name.carbon:[[@LINE+2]]:4: error: `fn` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_missing_name.carbon:[[@LINE+2]]:3: error: `fn` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: fn (); // --- fail_no_sig_or_semi.carbon -fn foo -// CHECK:STDERR: fail_no_sig_or_semi.carbon:[[@LINE+2]]:1: error: `fn` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] +// CHECK:STDERR: fail_no_sig_or_semi.carbon:[[@LINE+2]]:7: error: `fn` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] // CHECK:STDERR: +fn foo // --- fail_only_fn_and_semi.carbon @@ -76,13 +76,13 @@ fn; // --- fail_repeated_fn_and_semi.carbon -// CHECK:STDERR: fail_repeated_fn_and_semi.carbon:[[@LINE+2]]:4-5: error: `fn` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_repeated_fn_and_semi.carbon:[[@LINE+2]]:4-5: error: `fn` introducer should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclName] // CHECK:STDERR: fn fn; // --- fail_skip_indented_newline_until_outdent.carbon - // CHECK:STDERR: fail_skip_indented_newline_until_outdent.carbon:[[@LINE+2]]:6: error: `fn` introducer should be followed by a name [ExpectedDeclName] + // CHECK:STDERR: fail_skip_indented_newline_until_outdent.carbon:[[@LINE+2]]:5: error: `fn` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: fn (x, y, @@ -91,7 +91,7 @@ fn F(); // --- fail_skip_indented_newline_with_semi.carbon -// CHECK:STDERR: fail_skip_indented_newline_with_semi.carbon:[[@LINE+2]]:4: error: `fn` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_skip_indented_newline_with_semi.carbon:[[@LINE+2]]:3: error: `fn` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: fn (x, y, @@ -100,7 +100,7 @@ fn F(); // --- fail_skip_indented_newline_without_semi.carbon -// CHECK:STDERR: fail_skip_indented_newline_without_semi.carbon:[[@LINE+2]]:4: error: `fn` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_skip_indented_newline_without_semi.carbon:[[@LINE+2]]:3: error: `fn` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: fn (x, y, @@ -109,7 +109,7 @@ fn F(); // --- fail_skip_to_newline_without_semi.carbon -// CHECK:STDERR: fail_skip_to_newline_without_semi.carbon:[[@LINE+2]]:4: error: `fn` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_skip_to_newline_without_semi.carbon:[[@LINE+2]]:3: error: `fn` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: fn () fn F(); @@ -127,7 +127,7 @@ fn foo(bar); // --- fail_without_name_and_many_tokens_in_params.carbon -// CHECK:STDERR: fail_without_name_and_many_tokens_in_params.carbon:[[@LINE+2]]:4: error: `fn` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_without_name_and_many_tokens_in_params.carbon:[[@LINE+2]]:3: error: `fn` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: fn (a tokens c d e f g h i j k l m n o p q r s t u v w x y z); @@ -344,8 +344,8 @@ fn ValReturn() -> val; // CHECK:STDOUT: - filename: fail_repeated_fn_and_semi.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' -// CHECK:STDOUT: │ ├─InvalidParse 'fn' has_error -// CHECK:STDOUT: ├─FunctionDecl ';' has_error +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'fn' +// CHECK:STDOUT: ├─FunctionDecl ';' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) // CHECK:STDOUT: - filename: fail_skip_indented_newline_until_outdent.carbon diff --git a/toolchain/parse/testdata/function/definition.carbon b/toolchain/parse/testdata/function/definition.carbon index 61baae861280..bcb751bc9d35 100644 --- a/toolchain/parse/testdata/function/definition.carbon +++ b/toolchain/parse/testdata/function/definition.carbon @@ -35,9 +35,9 @@ fn foo() -> f64 { fn F() { // Note: this might become valid depending on the expression syntax. This test // shouldn't be taken as a sign it should remain invalid. + // CHECK:STDERR: fail_identifier_in_statements.carbon:[[@LINE+2]]:6: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: bar -// CHECK:STDERR: fail_identifier_in_statements.carbon:[[@LINE+2]]:1: error: expected `;` after expression statement [ExpectedExprSemi] -// CHECK:STDERR: } // --- nested.carbon @@ -59,19 +59,19 @@ impl T as I { // --- fail_builtin_identifier.carbon -// CHECK:STDERR: fail_builtin_identifier.carbon:[[@LINE+2]]:18-23: error: expected builtin function name after `=` [ExpectedBuiltinName] +// CHECK:STDERR: fail_builtin_identifier.carbon:[[@LINE+2]]:17: error: expected builtin function name after `=` [ExpectedBuiltinName] // CHECK:STDERR: fn NotString() = banana; // --- fail_builtin_two_strings.carbon -// CHECK:STDERR: fail_builtin_two_strings.carbon:[[@LINE+2]]:32-38: error: `fn` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_builtin_two_strings.carbon:[[@LINE+2]]:31: error: `fn` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: fn JunkAfterString() = "hello" "world"; // --- fail_builtin_spurious_equals.carbon -// CHECK:STDERR: fail_builtin_spurious_equals.carbon:[[@LINE+2]]:23: error: expected builtin function name after `=` [ExpectedBuiltinName] +// CHECK:STDERR: fail_builtin_spurious_equals.carbon:[[@LINE+2]]:22: error: expected builtin function name after `=` [ExpectedBuiltinName] // CHECK:STDERR: fn SpuriousEquals() = { } fn TestRecoveryFromSpuriousEquals(); diff --git a/toolchain/parse/testdata/function/extern.carbon b/toolchain/parse/testdata/function/extern.carbon index 54f81477c823..80561b767b4e 100644 --- a/toolchain/parse/testdata/function/extern.carbon +++ b/toolchain/parse/testdata/function/extern.carbon @@ -22,13 +22,13 @@ extern library default fn G(); // --- fail_name_missing.carbon -// CHECK:STDERR: fail_name_missing.carbon:[[@LINE+2]]:16-17: error: expected `default` or a string literal to specify the library name [ExpectedLibraryNameOrDefault] +// CHECK:STDERR: fail_name_missing.carbon:[[@LINE+2]]:15: error: expected `default` or a string literal to specify the library name [ExpectedLibraryNameOrDefault] // CHECK:STDERR: extern library fn G(); // --- fail_identifier.carbon -// CHECK:STDERR: fail_identifier.carbon:[[@LINE+4]]:16-18: error: expected `default` or a string literal to specify the library name [ExpectedLibraryNameOrDefault] +// CHECK:STDERR: fail_identifier.carbon:[[@LINE+4]]:15: error: expected `default` or a string literal to specify the library name [ExpectedLibraryNameOrDefault] // CHECK:STDERR: // CHECK:STDERR: fail_identifier.carbon:[[@LINE+2]]:16-18: error: unrecognized declaration introducer [UnrecognizedDecl] // CHECK:STDERR: diff --git a/toolchain/parse/testdata/generics/generic_params/fail_template_no_param.carbon b/toolchain/parse/testdata/generics/generic_params/fail_template_no_param.carbon index 192bebc30cdf..ec1fd91c2513 100644 --- a/toolchain/parse/testdata/generics/generic_params/fail_template_no_param.carbon +++ b/toolchain/parse/testdata/generics/generic_params/fail_template_no_param.carbon @@ -10,7 +10,7 @@ // --- fail_template_before_type.carbon -// CHECK:STDERR: fail_template_before_type.carbon:[[@LINE+2]]:15-17: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_template_before_type.carbon:[[@LINE+2]]:14: error: expected name in binding pattern [ExpectedBindingPattern] // CHECK:STDERR: fn A(template i32); @@ -22,7 +22,7 @@ fn C(template); // --- fail_reserved_word_after_generic.carbon -// CHECK:STDERR: fail_reserved_word_after_generic.carbon:[[@LINE+2]]:22: error: expected name in binding pattern; prefix reserved word with `r#` to form a valid identifier [ExpectedBindingPattern] +// CHECK:STDERR: fail_reserved_word_after_generic.carbon:[[@LINE+2]]:14-21: error: expected name in binding pattern; prefix reserved word with `r#` to form a valid identifier [ExpectedBindingPattern] // CHECK:STDERR: fn E(generic template: i32); diff --git a/toolchain/parse/testdata/generics/generic_params/template.carbon b/toolchain/parse/testdata/generics/generic_params/template.carbon index 249661865bcc..36b9af6bef5b 100644 --- a/toolchain/parse/testdata/generics/generic_params/template.carbon +++ b/toolchain/parse/testdata/generics/generic_params/template.carbon @@ -22,7 +22,7 @@ fn foo(template ref a: i32); // CHECK:STDERR: fn foo(template ref a: i32); -// CHECK:STDERR: fail_template_ref.carbon:[[@LINE+2]]:12-19: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_template_ref.carbon:[[@LINE+2]]:11: error: expected name in binding pattern [ExpectedBindingPattern] // CHECK:STDERR: fn foo(ref template a: i32); diff --git a/toolchain/parse/testdata/generics/impl/fail_impl.carbon b/toolchain/parse/testdata/generics/impl/fail_impl.carbon index 6af25306e99a..7c2448e7b623 100644 --- a/toolchain/parse/testdata/generics/impl/fail_impl.carbon +++ b/toolchain/parse/testdata/generics/impl/fail_impl.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/impl/fail_impl.carbon -// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:10-12: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:9: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: impl foo bar; @@ -16,19 +16,19 @@ impl foo bar; // CHECK:STDERR: impl i32 as; -// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:18-27: error: `impl` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] +// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:17: error: `impl` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] // CHECK:STDERR: impl bool as bar unexpected; -// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:6-11: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:5: error: expected expression [ExpectedExpr] // CHECK:STDERR: impl return as A; -// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:6-11: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:5: error: expected expression [ExpectedExpr] // CHECK:STDERR: impl return B; -// CHECK:STDERR: fail_impl.carbon:[[@LINE+4]]:13-15: error: expected `[` after `forall` in `impl` declaration [ImplExpectedAfterForall] +// CHECK:STDERR: fail_impl.carbon:[[@LINE+4]]:13: error: expected `[` after `forall` in `impl` declaration [ImplExpectedAfterForall] // CHECK:STDERR: // CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:16: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: @@ -42,7 +42,7 @@ impl forall [] u32; // CHECK:STDERR: impl forall [invalid] i8; -// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:13-15: error: expected `[` after `forall` in `impl` declaration [ImplExpectedAfterForall] +// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:13: error: expected `[` after `forall` in `impl` declaration [ImplExpectedAfterForall] // CHECK:STDERR: impl forall f16 as Quux; @@ -50,11 +50,11 @@ impl forall f16 as Quux; // CHECK:STDERR: impl forall [T: type] str; -// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:25-34: error: expected `as` in `impl` declaration [ImplExpectedAs] +// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:24: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: impl forall [T: type] T missing_as; -// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:38-42: error: `impl` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] +// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:37: error: `impl` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] // CHECK:STDERR: impl forall [T: type] T as Interface extra; @@ -62,10 +62,10 @@ impl forall [T: type] T as Interface extra; // CHECK:STDERR: impl; +// CHECK:STDERR: fail_impl.carbon:[[@LINE+2]]:5: error: expected expression [ExpectedExpr] +// CHECK:STDERR: impl -// CHECK:STDERR: fail_impl.carbon:[[@LINE+87]]:26: error: expected expression [ExpectedExpr] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_impl.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─ImplIntroducer 'impl' diff --git a/toolchain/parse/testdata/generics/impl/fail_out_of_line_member.carbon b/toolchain/parse/testdata/generics/impl/fail_out_of_line_member.carbon index a3ed24ead36e..a84986b86cc0 100644 --- a/toolchain/parse/testdata/generics/impl/fail_out_of_line_member.carbon +++ b/toolchain/parse/testdata/generics/impl/fail_out_of_line_member.carbon @@ -18,7 +18,7 @@ impl bool as Interface { fn F(); } -// CHECK:STDERR: fail_out_of_line_member.carbon:[[@LINE+2]]:4: error: `fn` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_out_of_line_member.carbon:[[@LINE+2]]:3: error: `fn` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: fn (bool as Interface).F() {} diff --git a/toolchain/parse/testdata/generics/impl/match_first.carbon b/toolchain/parse/testdata/generics/impl/match_first.carbon index 24b114619a90..148bff8ae634 100644 --- a/toolchain/parse/testdata/generics/impl/match_first.carbon +++ b/toolchain/parse/testdata/generics/impl/match_first.carbon @@ -20,9 +20,9 @@ match_first; // --- fail_eof.carbon -match_first -// CHECK:STDERR: fail_eof.carbon:[[@LINE+2]]:1: error: expected `{` after `match_first` [ExpectedCurlyBraceAfter] +// CHECK:STDERR: fail_eof.carbon:[[@LINE+2]]:12: error: expected `{` after `match_first` [ExpectedCurlyBraceAfter] // CHECK:STDERR: +match_first // --- with_impls.carbon diff --git a/toolchain/parse/testdata/generics/interface/fail_associated_constants.carbon b/toolchain/parse/testdata/generics/interface/fail_associated_constants.carbon index 71ee08373cb5..a9c5c2ed6aeb 100644 --- a/toolchain/parse/testdata/generics/interface/fail_associated_constants.carbon +++ b/toolchain/parse/testdata/generics/interface/fail_associated_constants.carbon @@ -13,7 +13,7 @@ library "[[@TEST_NAME]]"; interface I { - // CHECK:STDERR: fail_tuple_pattern.carbon:[[@LINE+2]]:7: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] + // CHECK:STDERR: fail_tuple_pattern.carbon:[[@LINE+2]]:6: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] // CHECK:STDERR: let (T: type, U: type); } @@ -23,7 +23,7 @@ interface I { library "[[@TEST_NAME]]"; interface I { - // CHECK:STDERR: fail_tuple_pattern_with_default.carbon:[[@LINE+2]]:15: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] + // CHECK:STDERR: fail_tuple_pattern_with_default.carbon:[[@LINE+2]]:14: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] // CHECK:STDERR: default let (T: type, U: type) = ({}, {}); } @@ -33,7 +33,7 @@ interface I { library "[[@TEST_NAME]]"; interface I { - // CHECK:STDERR: fail_var_pattern.carbon:[[@LINE+2]]:7-9: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] + // CHECK:STDERR: fail_var_pattern.carbon:[[@LINE+2]]:6: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] // CHECK:STDERR: let var T: type; } @@ -43,7 +43,7 @@ interface I { library "[[@TEST_NAME]]"; interface I { - // CHECK:STDERR: fail_var_pattern_with_default.carbon:[[@LINE+2]]:15-17: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] + // CHECK:STDERR: fail_var_pattern_with_default.carbon:[[@LINE+2]]:14: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] // CHECK:STDERR: default let var T: type = {}; } @@ -83,7 +83,7 @@ interface I { library "[[@TEST_NAME]]"; interface I { - // CHECK:STDERR: fail_underscore.carbon:[[@LINE+2]]:7: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] + // CHECK:STDERR: fail_underscore.carbon:[[@LINE+2]]:7: error: expected identifier in associated constant declaration; prefix reserved word with `r#` to form a valid identifier [ExpectedAssociatedConstantIdentifier] // CHECK:STDERR: let _: {}; } @@ -201,7 +201,11 @@ interface I { // CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'I' // CHECK:STDOUT: │ ╭─InterfaceDefinitionStart '{' // CHECK:STDOUT: │ │ ╭─AssociatedConstantIntroducer 'let' -// CHECK:STDOUT: │ ├─AssociatedConstantDecl ';' has_error +// CHECK:STDOUT: │ │ │ ╭─IdentifierNameNotBeforeSignature '_' +// CHECK:STDOUT: │ │ │ │ ╭─StructLiteralStart '{' +// CHECK:STDOUT: │ │ │ ├─StructLiteral '}' +// CHECK:STDOUT: │ │ ├─AssociatedConstantNameAndType ':' +// CHECK:STDOUT: │ ├─AssociatedConstantDecl ';' // CHECK:STDOUT: ├─InterfaceDefinition '}' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) diff --git a/toolchain/parse/testdata/generics/interface/fail_missing_name.carbon b/toolchain/parse/testdata/generics/interface/fail_missing_name.carbon index 5a0295e1b83d..ef8d6e52b3b6 100644 --- a/toolchain/parse/testdata/generics/interface/fail_missing_name.carbon +++ b/toolchain/parse/testdata/generics/interface/fail_missing_name.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/interface/fail_missing_name.carbon -// CHECK:STDERR: fail_missing_name.carbon:[[@LINE+2]]:11: error: `interface` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_missing_name.carbon:[[@LINE+2]]:10: error: `interface` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: interface { } diff --git a/toolchain/parse/testdata/generics/interface/fail_missing_open_curly.carbon b/toolchain/parse/testdata/generics/interface/fail_missing_open_curly.carbon index 651cdf97e80a..c2098ec37525 100644 --- a/toolchain/parse/testdata/generics/interface/fail_missing_open_curly.carbon +++ b/toolchain/parse/testdata/generics/interface/fail_missing_open_curly.carbon @@ -8,14 +8,14 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/interface/fail_missing_open_curly.carbon -// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+2]]:15-17: error: `interface` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] +// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+2]]:14: error: `interface` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] // CHECK:STDERR: interface Bar Baz {} +// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+2]]:14: error: `interface` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] +// CHECK:STDERR: interface Foo -// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+11]]:26: error: `interface` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_missing_open_curly.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─InterfaceIntroducer 'interface' diff --git a/toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon b/toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon index 3631b5f8fd06..6715f0ff8492 100644 --- a/toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon +++ b/toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon @@ -9,7 +9,7 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon interface Foo { - // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+2]]:13-16: error: expected `,` or `]` [UnexpectedTokenAfterListElement] + // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+2]]:12: error: expected `,` or `]` [UnexpectedTokenAfterListElement] // CHECK:STDERR: fn Sub[me Self](b: Self) -> Self; diff --git a/toolchain/parse/testdata/generics/interface/require.carbon b/toolchain/parse/testdata/generics/interface/require.carbon index 79a54c87be42..2ea6422baf99 100644 --- a/toolchain/parse/testdata/generics/interface/require.carbon +++ b/toolchain/parse/testdata/generics/interface/require.carbon @@ -71,7 +71,7 @@ interface Z { interface Y {} interface Z { - // CHECK:STDERR: fail_wrong_keyword.carbon:[[@LINE+2]]:11-12: error: expected expression [ExpectedExpr] + // CHECK:STDERR: fail_wrong_keyword.carbon:[[@LINE+2]]:10: error: expected expression [ExpectedExpr] // CHECK:STDERR: require as Y; } @@ -81,7 +81,7 @@ interface Z { interface Y {} interface Z { - // CHECK:STDERR: fail_as_in_self.carbon:[[@LINE+2]]:16-17: error: expected `impls` in `require` declaration [RequireExpectedImpls] + // CHECK:STDERR: fail_as_in_self.carbon:[[@LINE+2]]:15: error: expected `impls` in `require` declaration [RequireExpectedImpls] // CHECK:STDERR: require Self as Y impls Y; } diff --git a/toolchain/parse/testdata/generics/named_constraint/fail_incomplete.carbon b/toolchain/parse/testdata/generics/named_constraint/fail_incomplete.carbon index d3158f0179af..1aa0e435248e 100644 --- a/toolchain/parse/testdata/generics/named_constraint/fail_incomplete.carbon +++ b/toolchain/parse/testdata/generics/named_constraint/fail_incomplete.carbon @@ -8,11 +8,11 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/named_constraint/fail_incomplete.carbon -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:12-16: error: `constraint` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:11: error: `constraint` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: constraint "Foo" -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:1-10: error: `constraint` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:11: error: `constraint` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: constraint diff --git a/toolchain/parse/testdata/if/fail_else_unbraced.carbon b/toolchain/parse/testdata/if/fail_else_unbraced.carbon index 802d4bca610e..1050630e32c3 100644 --- a/toolchain/parse/testdata/if/fail_else_unbraced.carbon +++ b/toolchain/parse/testdata/if/fail_else_unbraced.carbon @@ -9,20 +9,20 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/if/fail_else_unbraced.carbon fn F() { + // CHECK:STDERR: fail_else_unbraced.carbon:[[@LINE+2]]:9: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: if (a) - // CHECK:STDERR: fail_else_unbraced.carbon:[[@LINE+2]]:5-6: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: fail_else_unbraced.carbon:[[@LINE+2]]:11: error: expected braced code block [ExpectedCodeBlock] // CHECK:STDERR: if (b) - // CHECK:STDERR: fail_else_unbraced.carbon:[[@LINE+2]]:7: error: expected braced code block [ExpectedCodeBlock] - // CHECK:STDERR: c; - else - // CHECK:STDERR: fail_else_unbraced.carbon:[[@LINE+2]]:7: error: expected braced code block [ExpectedCodeBlock] - // CHECK:STDERR: - d; - else - // CHECK:STDERR: fail_else_unbraced.carbon:[[@LINE+2]]:5: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: fail_else_unbraced.carbon:[[@LINE+2]]:9: error: expected braced code block [ExpectedCodeBlock] // CHECK:STDERR: + else + d; + // CHECK:STDERR: fail_else_unbraced.carbon:[[@LINE+2]]:7: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: + else e; if (x) { f; } else if (x) { g; } diff --git a/toolchain/parse/testdata/if/fail_errors.carbon b/toolchain/parse/testdata/if/fail_errors.carbon index 4cb15fc7b218..3b1a5ecfedc6 100644 --- a/toolchain/parse/testdata/if/fail_errors.carbon +++ b/toolchain/parse/testdata/if/fail_errors.carbon @@ -15,12 +15,13 @@ fn F() { // CHECK:STDERR: fail_errors.carbon:[[@LINE+2]]:7: error: expected expression [ExpectedExpr] // CHECK:STDERR: if () {} - // CHECK:STDERR: fail_errors.carbon:[[@LINE+2]]:9: error: unexpected tokens before `)` [ExpectedCloseSymbol] + // CHECK:STDERR: fail_errors.carbon:[[@LINE+3]]:9: error: unexpected tokens before `)` [ExpectedCloseSymbol] + // CHECK:STDERR: fail_errors.carbon:[[@LINE+2]]:6: note: matching this `(` [GroupOpenedHere] // CHECK:STDERR: if (b c) {} + // CHECK:STDERR: fail_errors.carbon:[[@LINE+2]]:9: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: if (d) -// CHECK:STDERR: fail_errors.carbon:[[@LINE+4]]:1: error: expected braced code block [ExpectedCodeBlock] -// CHECK:STDERR: // CHECK:STDERR: fail_errors.carbon:[[@LINE+2]]:1: error: expected expression [ExpectedExpr] // CHECK:STDERR: } diff --git a/toolchain/parse/testdata/if/fail_square_brackets.carbon b/toolchain/parse/testdata/if/fail_square_brackets.carbon index 5d97ba1d9abf..6db4282a1578 100644 --- a/toolchain/parse/testdata/if/fail_square_brackets.carbon +++ b/toolchain/parse/testdata/if/fail_square_brackets.carbon @@ -11,7 +11,7 @@ fn F() { // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+8]]:6: error: expected `(` after `if` [ExpectedParenAfter] // CHECK:STDERR: - // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+6]]:6: error: expected expression [ExpectedExpr] + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+6]]:5: error: expected expression [ExpectedExpr] // CHECK:STDERR: // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+4]]:6: error: expected braced code block [ExpectedCodeBlock] // CHECK:STDERR: diff --git a/toolchain/parse/testdata/if/fail_unbraced.carbon b/toolchain/parse/testdata/if/fail_unbraced.carbon index 314dc6c7c275..c4ca19e5e45f 100644 --- a/toolchain/parse/testdata/if/fail_unbraced.carbon +++ b/toolchain/parse/testdata/if/fail_unbraced.carbon @@ -10,15 +10,15 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/if/fail_unbraced.carbon fn F() { + // CHECK:STDERR: fail_unbraced.carbon:[[@LINE+2]]:9: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: if (a) - // CHECK:STDERR: fail_unbraced.carbon:[[@LINE+2]]:5-6: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: fail_unbraced.carbon:[[@LINE+2]]:11: error: expected braced code block [ExpectedCodeBlock] // CHECK:STDERR: if (b) - // CHECK:STDERR: fail_unbraced.carbon:[[@LINE+2]]:7-8: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: fail_unbraced.carbon:[[@LINE+2]]:13: error: expected braced code block [ExpectedCodeBlock] // CHECK:STDERR: if (c) - // CHECK:STDERR: fail_unbraced.carbon:[[@LINE+2]]:9: error: expected braced code block [ExpectedCodeBlock] - // CHECK:STDERR: d; } diff --git a/toolchain/parse/testdata/if_expr/fail_top_level_if.carbon b/toolchain/parse/testdata/if_expr/fail_top_level_if.carbon index e44d0231ec1e..b0431453816d 100644 --- a/toolchain/parse/testdata/if_expr/fail_top_level_if.carbon +++ b/toolchain/parse/testdata/if_expr/fail_top_level_if.carbon @@ -9,9 +9,9 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/if_expr/fail_top_level_if.carbon fn F() { - // CHECK:STDERR: fail_top_level_if.carbon:[[@LINE+6]]:6-9: error: expected `(` after `if` [ExpectedParenAfter] + // CHECK:STDERR: fail_top_level_if.carbon:[[@LINE+6]]:6: error: expected `(` after `if` [ExpectedParenAfter] // CHECK:STDERR: - // CHECK:STDERR: fail_top_level_if.carbon:[[@LINE+4]]:11-14: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: fail_top_level_if.carbon:[[@LINE+4]]:11: error: expected braced code block [ExpectedCodeBlock] // CHECK:STDERR: // CHECK:STDERR: fail_top_level_if.carbon:[[@LINE+2]]:11-14: error: expected expression [ExpectedExpr] // CHECK:STDERR: diff --git a/toolchain/parse/testdata/index/fail_malformed_expr.carbon b/toolchain/parse/testdata/index/fail_malformed_expr.carbon index 5f95f879993b..eedfb47133be 100644 --- a/toolchain/parse/testdata/index/fail_malformed_expr.carbon +++ b/toolchain/parse/testdata/index/fail_malformed_expr.carbon @@ -8,7 +8,8 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/index/fail_malformed_expr.carbon -// CHECK:STDERR: fail_malformed_expr.carbon:[[@LINE+2]]:17: error: unexpected tokens before `]` [ExpectedCloseSymbol] +// CHECK:STDERR: fail_malformed_expr.carbon:[[@LINE+3]]:17: error: unexpected tokens before `]` [ExpectedCloseSymbol] +// CHECK:STDERR: fail_malformed_expr.carbon:[[@LINE+2]]:15: note: matching this `[` [GroupOpenedHere] // CHECK:STDERR: var v: i32 = t[0,]; diff --git a/toolchain/parse/testdata/lambda/lambda.carbon b/toolchain/parse/testdata/lambda/lambda.carbon index 8fc906cba68b..34d6ebdf7c22 100644 --- a/toolchain/parse/testdata/lambda/lambda.carbon +++ b/toolchain/parse/testdata/lambda/lambda.carbon @@ -20,24 +20,24 @@ var d: auto = fn (ref x: i32) ->? ref i32 { return x; }; // Tests that the compiler does not crash when a LambdaIntroducer (`fn`) // is left unconsumed inside an invalid binary operator sequence. -// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+6]]:10: error: whitespace missing around binary operator [BinaryOperatorRequiresWhitespace] +// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+12]]:10: error: whitespace missing around binary operator [BinaryOperatorRequiresWhitespace] // CHECK:STDERR: -// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+4]]:16: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+10]]:16: error: expected expression [ExpectedExpr] // CHECK:STDERR: -// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+2]]:16: error: whitespace missing before binary operator [BinaryOperatorRequiresWhitespace] +// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+8]]:16: error: whitespace missing before binary operator [BinaryOperatorRequiresWhitespace] +// CHECK:STDERR: +// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+6]]:17: error: expected expression [ExpectedExpr] +// CHECK:STDERR: +// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+4]]:17: error: expected `{` after return type [ExpectedLambdaBodyAfterReturnType] +// CHECK:STDERR: +// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+2]]:17: error: expected `impls` in `require` declaration [RequireExpectedImpls] // CHECK:STDERR: require j%-fn->> -// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+6]]:1: error: expected expression [ExpectedExpr] -// CHECK:STDERR: -// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+4]]:1: error: expected `{` after return type [ExpectedLambdaBodyAfterReturnType] -// CHECK:STDERR: -// CHECK:STDERR: fail_unconsumed_lambda_introducer.carbon:[[@LINE+2]]:1: error: expected `impls` in `require` declaration [RequireExpectedImpls] -// CHECK:STDERR: // --- fail_expected_body.carbon fn F() { - // CHECK:STDERR: fail_expected_body.carbon:[[@LINE+2]]:20: error: expected `->`, `=>`, or `{` [ExpectedLambdaBody] + // CHECK:STDERR: fail_expected_body.carbon:[[@LINE+2]]:19: error: expected `->`, `=>`, or `{` [ExpectedLambdaBody] // CHECK:STDERR: var x: auto = fn ; } @@ -45,7 +45,7 @@ fn F() { // --- fail_expected_body_after_return_type.carbon fn G() { - // CHECK:STDERR: fail_expected_body_after_return_type.carbon:[[@LINE+2]]:27: error: expected `{` after return type [ExpectedLambdaBodyAfterReturnType] + // CHECK:STDERR: fail_expected_body_after_return_type.carbon:[[@LINE+2]]:26: error: expected `{` after return type [ExpectedLambdaBodyAfterReturnType] // CHECK:STDERR: var x: auto = fn -> i32 ; } diff --git a/toolchain/parse/testdata/let/expression_pattern_precedence.carbon b/toolchain/parse/testdata/let/expression_pattern_precedence.carbon index ab934054210b..9eeb98ee70cb 100644 --- a/toolchain/parse/testdata/let/expression_pattern_precedence.carbon +++ b/toolchain/parse/testdata/let/expression_pattern_precedence.carbon @@ -21,7 +21,7 @@ fn F() { // --- fail_relational.carbon fn G() { - // CHECK:STDERR: fail_relational.carbon:[[@LINE+2]]:9-10: error: `let` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: fail_relational.carbon:[[@LINE+2]]:8: error: `let` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: let a == b = c; } @@ -29,7 +29,7 @@ fn G() { // --- fail_logical.carbon fn H() { - // CHECK:STDERR: fail_logical.carbon:[[@LINE+2]]:9-11: error: `let` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: fail_logical.carbon:[[@LINE+2]]:8: error: `let` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: let a and b = c; } diff --git a/toolchain/parse/testdata/let/fail_bad_name.carbon b/toolchain/parse/testdata/let/fail_bad_name.carbon index d5210c6c44a7..0ae8c0c1ddb7 100644 --- a/toolchain/parse/testdata/let/fail_bad_name.carbon +++ b/toolchain/parse/testdata/let/fail_bad_name.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/let/fail_bad_name.carbon -// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+2]]:5: error: expected pattern [ExpectedPattern] +// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+2]]:4: error: expected pattern [ExpectedPattern] // CHECK:STDERR: let ? = 4; diff --git a/toolchain/parse/testdata/let/fail_missing_name.carbon b/toolchain/parse/testdata/let/fail_missing_name.carbon index 52e95ba43ab7..1651069358e2 100644 --- a/toolchain/parse/testdata/let/fail_missing_name.carbon +++ b/toolchain/parse/testdata/let/fail_missing_name.carbon @@ -10,13 +10,13 @@ // --- fail_runtime_binding.carbon -// CHECK:STDERR: fail_runtime_binding.carbon:[[@LINE+2]]:5: error: expected pattern [ExpectedPattern] +// CHECK:STDERR: fail_runtime_binding.carbon:[[@LINE+2]]:4: error: expected pattern [ExpectedPattern] // CHECK:STDERR: let : i32 = 4; // --- fail_compiletime_binding.carbon -// CHECK:STDERR: fail_compiletime_binding.carbon:[[@LINE+2]]:13: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_compiletime_binding.carbon:[[@LINE+2]]:12: error: expected name in binding pattern [ExpectedBindingPattern] // CHECK:STDERR: let generic : bool = true; diff --git a/toolchain/parse/testdata/let/fail_no_semi.carbon b/toolchain/parse/testdata/let/fail_no_semi.carbon index 2ec62b07fb11..fb651f9d9b53 100644 --- a/toolchain/parse/testdata/let/fail_no_semi.carbon +++ b/toolchain/parse/testdata/let/fail_no_semi.carbon @@ -8,12 +8,12 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/let/fail_no_semi.carbon +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+4]]:4: error: expected pattern [ExpectedPattern] +// CHECK:STDERR: +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:4: error: `let` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: let -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+10]]:26: error: expected pattern [ExpectedPattern] -// CHECK:STDERR: -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+8]]:26: error: `let` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_no_semi.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─LetIntroducer 'let' diff --git a/toolchain/parse/testdata/match/fail_missing_case_arrow.carbon b/toolchain/parse/testdata/match/fail_missing_case_arrow.carbon index cea386d453bb..688c204fb671 100644 --- a/toolchain/parse/testdata/match/fail_missing_case_arrow.carbon +++ b/toolchain/parse/testdata/match/fail_missing_case_arrow.carbon @@ -10,10 +10,10 @@ fn f() -> i32 { match (3) { - // CHECK:STDERR: fail_missing_case_arrow.carbon:[[@LINE+2]]:17: error: expected `=>` introducing statement block [ExpectedMatchCaseArrow] + // CHECK:STDERR: fail_missing_case_arrow.carbon:[[@LINE+2]]:16: error: expected `=>` introducing statement block [ExpectedMatchCaseArrow] // CHECK:STDERR: case x: i32 { return 2; } - // CHECK:STDERR: fail_missing_case_arrow.carbon:[[@LINE+2]]:29: error: expected `=>` introducing statement block [ExpectedMatchCaseArrow] + // CHECK:STDERR: fail_missing_case_arrow.carbon:[[@LINE+2]]:28: error: expected `=>` introducing statement block [ExpectedMatchCaseArrow] // CHECK:STDERR: case x: i32 if (x == 3) { return 3; } } diff --git a/toolchain/parse/testdata/match/fail_missing_case_pattern.carbon b/toolchain/parse/testdata/match/fail_missing_case_pattern.carbon index 61dfd7a7c5eb..d80d96df4008 100644 --- a/toolchain/parse/testdata/match/fail_missing_case_pattern.carbon +++ b/toolchain/parse/testdata/match/fail_missing_case_pattern.carbon @@ -10,7 +10,7 @@ fn f() -> i32 { match (3) { - // CHECK:STDERR: fail_missing_case_pattern.carbon:[[@LINE+2]]:10-11: error: expected pattern [ExpectedPattern] + // CHECK:STDERR: fail_missing_case_pattern.carbon:[[@LINE+2]]:9: error: expected pattern [ExpectedPattern] // CHECK:STDERR: case => { return 2; } } diff --git a/toolchain/parse/testdata/match/fail_missing_case_statements_block.carbon b/toolchain/parse/testdata/match/fail_missing_case_statements_block.carbon index d296768a1e82..2f0af02a9f71 100644 --- a/toolchain/parse/testdata/match/fail_missing_case_statements_block.carbon +++ b/toolchain/parse/testdata/match/fail_missing_case_statements_block.carbon @@ -10,9 +10,9 @@ fn f() -> i32 { match (3) { - case x: i32 => - // CHECK:STDERR: fail_missing_case_statements_block.carbon:[[@LINE+2]]:5-11: error: expected `{` after `=>` [ExpectedMatchCaseBlock] + // CHECK:STDERR: fail_missing_case_statements_block.carbon:[[@LINE+2]]:19: error: expected `{` after `=>` [ExpectedMatchCaseBlock] // CHECK:STDERR: + case x: i32 => default => { return 3; } } return 0; diff --git a/toolchain/parse/testdata/match/fail_missing_cases_block.carbon b/toolchain/parse/testdata/match/fail_missing_cases_block.carbon index 4ade292016b0..905f068673f2 100644 --- a/toolchain/parse/testdata/match/fail_missing_cases_block.carbon +++ b/toolchain/parse/testdata/match/fail_missing_cases_block.carbon @@ -9,9 +9,9 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/match/fail_missing_cases_block.carbon fn f() -> i32 { - match (1) - // CHECK:STDERR: fail_missing_cases_block.carbon:[[@LINE+2]]:3-8: error: expected `{` starting block with cases [ExpectedMatchCasesBlock] + // CHECK:STDERR: fail_missing_cases_block.carbon:[[@LINE+2]]:12: error: expected `{` starting block with cases [ExpectedMatchCasesBlock] // CHECK:STDERR: + match (1) return 0; } diff --git a/toolchain/parse/testdata/match/fail_missing_default_arrow.carbon b/toolchain/parse/testdata/match/fail_missing_default_arrow.carbon index 2a91b8fd52dd..da9d66409924 100644 --- a/toolchain/parse/testdata/match/fail_missing_default_arrow.carbon +++ b/toolchain/parse/testdata/match/fail_missing_default_arrow.carbon @@ -10,7 +10,7 @@ fn f() -> i32 { match (3) { - // CHECK:STDERR: fail_missing_default_arrow.carbon:[[@LINE+2]]:13: error: expected `=>` introducing statement block [ExpectedMatchCaseArrow] + // CHECK:STDERR: fail_missing_default_arrow.carbon:[[@LINE+2]]:12: error: expected `=>` introducing statement block [ExpectedMatchCaseArrow] // CHECK:STDERR: default { return 1; } } diff --git a/toolchain/parse/testdata/match/fail_missing_default_statements_block.carbon b/toolchain/parse/testdata/match/fail_missing_default_statements_block.carbon index a04bef0070ae..166760dd1419 100644 --- a/toolchain/parse/testdata/match/fail_missing_default_statements_block.carbon +++ b/toolchain/parse/testdata/match/fail_missing_default_statements_block.carbon @@ -10,9 +10,9 @@ fn f() -> i32 { match (3) { + // CHECK:STDERR: fail_missing_default_statements_block.carbon:[[@LINE+2]]:15: error: expected `{` after `=>` [ExpectedMatchCaseBlock] + // CHECK:STDERR: default => - // CHECK:STDERR: fail_missing_default_statements_block.carbon:[[@LINE+2]]:3: error: expected `{` after `=>` [ExpectedMatchCaseBlock] - // CHECK:STDERR: } return 0; } diff --git a/toolchain/parse/testdata/match/fail_missing_guard_close_paren.carbon b/toolchain/parse/testdata/match/fail_missing_guard_close_paren.carbon index 83fd03795d28..f5145fe20593 100644 --- a/toolchain/parse/testdata/match/fail_missing_guard_close_paren.carbon +++ b/toolchain/parse/testdata/match/fail_missing_guard_close_paren.carbon @@ -13,7 +13,7 @@ fn f() -> i32 { // CHECK:STDERR: fail_missing_guard_close_paren.carbon:[[@LINE+5]]:21: error: opening symbol without a corresponding closing symbol [UnmatchedOpening] // CHECK:STDERR: fail_missing_guard_close_paren.carbon:[[@LINE+4]]:30: note: possibly missing `)` here [PossiblyMissingBracketHere] // CHECK:STDERR: - // CHECK:STDERR: fail_missing_guard_close_paren.carbon:[[@LINE+2]]:28-29: error: expected `)` [ExpectedMatchCaseGuardCloseParen] + // CHECK:STDERR: fail_missing_guard_close_paren.carbon:[[@LINE+2]]:27: error: expected `)` [ExpectedMatchCaseGuardCloseParen] // CHECK:STDERR: case x: bool if (false => { return 1; } } diff --git a/toolchain/parse/testdata/match/fail_missing_guard_parens_only_parse_errors.carbon b/toolchain/parse/testdata/match/fail_missing_guard_parens_only_parse_errors.carbon index 816876b68108..7cbeb415eacc 100644 --- a/toolchain/parse/testdata/match/fail_missing_guard_parens_only_parse_errors.carbon +++ b/toolchain/parse/testdata/match/fail_missing_guard_parens_only_parse_errors.carbon @@ -10,11 +10,11 @@ fn f() -> i32 { match (true) { - // CHECK:STDERR: fail_missing_guard_parens_only_parse_errors.carbon:[[@LINE+2]]:33-34: error: expected `)` [ExpectedMatchCaseGuardCloseParen] + // CHECK:STDERR: fail_missing_guard_parens_only_parse_errors.carbon:[[@LINE+2]]:32: error: expected `)` [ExpectedMatchCaseGuardCloseParen] // CHECK:STDERR: case x: bool if (x == false => { return 1; } case y: bool if (not y) => { return 2; } - // CHECK:STDERR: fail_missing_guard_parens_only_parse_errors.carbon:[[@LINE+2]]:21-24: error: expected `(` after `if` [ExpectedMatchCaseGuardOpenParen] + // CHECK:STDERR: fail_missing_guard_parens_only_parse_errors.carbon:[[@LINE+2]]:21: error: expected `(` after `if` [ExpectedMatchCaseGuardOpenParen] // CHECK:STDERR: case z: bool if true or false) => { return 3; } } diff --git a/toolchain/parse/testdata/namespace/fail_arrow.carbon b/toolchain/parse/testdata/namespace/fail_arrow.carbon index 7406a3d62cbd..2fc73826c808 100644 --- a/toolchain/parse/testdata/namespace/fail_arrow.carbon +++ b/toolchain/parse/testdata/namespace/fail_arrow.carbon @@ -10,7 +10,7 @@ namespace Foo; -// CHECK:STDERR: fail_arrow.carbon:[[@LINE+2]]:10-11: error: `class` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] +// CHECK:STDERR: fail_arrow.carbon:[[@LINE+2]]:10: error: `class` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition] // CHECK:STDERR: class Foo->Bar {} diff --git a/toolchain/parse/testdata/namespace/fail_incomplete.carbon b/toolchain/parse/testdata/namespace/fail_incomplete.carbon index 8b3829b3c4ec..468306b7104c 100644 --- a/toolchain/parse/testdata/namespace/fail_incomplete.carbon +++ b/toolchain/parse/testdata/namespace/fail_incomplete.carbon @@ -8,27 +8,27 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/namespace/fail_incomplete.carbon -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:11-13: error: `namespace` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:10: error: `namespace` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: namespace 123 -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:11-12: error: `namespace` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:10: error: `namespace` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: namespace "" -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:11: error: `namespace` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:10: error: `namespace` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: namespace + -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:11-14: error: `namespace` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:10: error: `namespace` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: namespace bool -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:11-19: error: `namespace` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:10: error: `namespace` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: namespace namespace namespace namespace namespace -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:1-9: error: `namespace` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+2]]:10: error: `namespace` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: namespace diff --git a/toolchain/parse/testdata/namespace/fail_modifiers.carbon b/toolchain/parse/testdata/namespace/fail_modifiers.carbon index 059ba916c4dd..f50b0818612c 100644 --- a/toolchain/parse/testdata/namespace/fail_modifiers.carbon +++ b/toolchain/parse/testdata/namespace/fail_modifiers.carbon @@ -8,10 +8,10 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/namespace/fail_modifiers.carbon +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+2]]:20: error: `namespace` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: virtual namespace B -// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+2]]:1-4: error: `namespace` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: impl namespace // CHECK:STDOUT: - filename: fail_modifiers.carbon diff --git a/toolchain/parse/testdata/observe/fail_invalid_operator.carbon b/toolchain/parse/testdata/observe/fail_invalid_operator.carbon index 35ff5a5c0026..5fea605d68ff 100644 --- a/toolchain/parse/testdata/observe/fail_invalid_operator.carbon +++ b/toolchain/parse/testdata/observe/fail_invalid_operator.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/observe/fail_invalid_operator.carbon -// CHECK:STDERR: fail_invalid_operator.carbon:[[@LINE+2]]:11: error: observe should use `==` or `impls` operator [ExpectedObserveOperator] +// CHECK:STDERR: fail_invalid_operator.carbon:[[@LINE+2]]:10: error: observe should use `==` or `impls` operator [ExpectedObserveOperator] // CHECK:STDERR: observe T = U impls I; diff --git a/toolchain/parse/testdata/observe/fail_missing_expr.carbon b/toolchain/parse/testdata/observe/fail_missing_expr.carbon index e70b03377224..3b72a3314dc0 100644 --- a/toolchain/parse/testdata/observe/fail_missing_expr.carbon +++ b/toolchain/parse/testdata/observe/fail_missing_expr.carbon @@ -10,19 +10,19 @@ // --- fail_missing_expr_after_observe.carbon -// CHECK:STDERR: fail_missing_expr_after_observe.carbon:[[@LINE+2]]:9-13: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_missing_expr_after_observe.carbon:[[@LINE+2]]:8: error: expected expression [ExpectedExpr] // CHECK:STDERR: observe impls I; // --- fail_missing_expr_after_impls.carbon -// CHECK:STDERR: fail_missing_expr_after_impls.carbon:[[@LINE+2]]:17: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_missing_expr_after_impls.carbon:[[@LINE+2]]:16: error: expected expression [ExpectedExpr] // CHECK:STDERR: observe T impls ; // --- fail_missing_impls.carbon -// CHECK:STDERR: fail_missing_impls.carbon:[[@LINE+2]]:11: error: observe should use `==` or `impls` operator [ExpectedObserveOperator] +// CHECK:STDERR: fail_missing_impls.carbon:[[@LINE+2]]:10: error: observe should use `==` or `impls` operator [ExpectedObserveOperator] // CHECK:STDERR: observe T ; diff --git a/toolchain/parse/testdata/operators/fail_infix_uneven_space_after.carbon b/toolchain/parse/testdata/operators/fail_infix_uneven_space_after.carbon index 1ecb8441f77d..e7617aaa3a1c 100644 --- a/toolchain/parse/testdata/operators/fail_infix_uneven_space_after.carbon +++ b/toolchain/parse/testdata/operators/fail_infix_uneven_space_after.carbon @@ -10,7 +10,7 @@ // TODO: We could figure out that this first Failed example is infix // with one-token lookahead. -// CHECK:STDERR: fail_infix_uneven_space_after.carbon:[[@LINE+2]]:16: error: `var` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_infix_uneven_space_after.carbon:[[@LINE+2]]:15: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var n: i8 = n* n; diff --git a/toolchain/parse/testdata/operators/fail_infix_uneven_space_before.carbon b/toolchain/parse/testdata/operators/fail_infix_uneven_space_before.carbon index bfd00cbd1836..98bef97b8c81 100644 --- a/toolchain/parse/testdata/operators/fail_infix_uneven_space_before.carbon +++ b/toolchain/parse/testdata/operators/fail_infix_uneven_space_before.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/operators/fail_infix_uneven_space_before.carbon -// CHECK:STDERR: fail_infix_uneven_space_before.carbon:[[@LINE+2]]:15: error: whitespace missing after binary operator [BinaryOperatorRequiresWhitespace] +// CHECK:STDERR: fail_infix_uneven_space_before.carbon:[[@LINE+2]]:16: error: whitespace missing after binary operator [BinaryOperatorRequiresWhitespace] // CHECK:STDERR: var n: i8 = n *n; diff --git a/toolchain/parse/testdata/operators/fail_invalid_infix.carbon b/toolchain/parse/testdata/operators/fail_invalid_infix.carbon index 34f28f573ca8..9caff315a13e 100644 --- a/toolchain/parse/testdata/operators/fail_invalid_infix.carbon +++ b/toolchain/parse/testdata/operators/fail_invalid_infix.carbon @@ -8,15 +8,15 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/operators/fail_invalid_infix.carbon -// CHECK:STDERR: fail_invalid_infix.carbon:[[@LINE+2]]:19: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_invalid_infix.carbon:[[@LINE+2]]:18: error: expected expression [ExpectedExpr] // CHECK:STDERR: var a: i32 = n == ; -// CHECK:STDERR: fail_invalid_infix.carbon:[[@LINE+2]]:14-15: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_invalid_infix.carbon:[[@LINE+2]]:13: error: expected expression [ExpectedExpr] // CHECK:STDERR: var b: i32 = == n; -// CHECK:STDERR: fail_invalid_infix.carbon:[[@LINE+4]]:14-15: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_invalid_infix.carbon:[[@LINE+4]]:13: error: expected expression [ExpectedExpr] // CHECK:STDERR: -// CHECK:STDERR: fail_invalid_infix.carbon:[[@LINE+2]]:17: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_invalid_infix.carbon:[[@LINE+2]]:16: error: expected expression [ExpectedExpr] // CHECK:STDERR: var c: i32 = == ; diff --git a/toolchain/parse/testdata/operators/fail_postincrement.carbon b/toolchain/parse/testdata/operators/fail_postincrement.carbon index fa3dd7035995..d62af9369b53 100644 --- a/toolchain/parse/testdata/operators/fail_postincrement.carbon +++ b/toolchain/parse/testdata/operators/fail_postincrement.carbon @@ -11,10 +11,10 @@ fn F() { var n: i32 = 0; // TODO: It'd be nice to produce a custom diagnostic here. - // CHECK:STDERR: fail_postincrement.carbon:[[@LINE+2]]:4-5: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_postincrement.carbon:[[@LINE+2]]:4: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: n++; - // CHECK:STDERR: fail_postincrement.carbon:[[@LINE+2]]:4-5: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_postincrement.carbon:[[@LINE+2]]:4: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: n--; } diff --git a/toolchain/parse/testdata/operators/fail_precedence_assign.carbon b/toolchain/parse/testdata/operators/fail_precedence_assign.carbon index 9904014d798e..2e70b7690607 100644 --- a/toolchain/parse/testdata/operators/fail_precedence_assign.carbon +++ b/toolchain/parse/testdata/operators/fail_precedence_assign.carbon @@ -12,12 +12,12 @@ fn F() { var a: i32; // Assignment can only appear as an expression statement, not as a subexpression. // TODO: Produce the "can only be used as a complete statement" diagnostic in each of these cases. - // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+2]]:10: error: expected `,` or `)` [UnexpectedTokenAfterListElement] + // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+2]]:9: error: expected `,` or `)` [UnexpectedTokenAfterListElement] // CHECK:STDERR: 1 + (a = 1); - // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+4]]:19-20: error: expected `else` after `if ... then ...` [ExpectedElseAfterIf] + // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+4]]:18: error: expected `else` after `if ... then ...` [ExpectedElseAfterIf] // CHECK:STDERR: - // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+2]]:19-20: error: expected `,` or `)` [UnexpectedTokenAfterListElement] + // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+2]]:18: error: expected `,` or `)` [UnexpectedTokenAfterListElement] // CHECK:STDERR: (if true then a += 1 else a /= 2); // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+2]]:7-8: error: operator `++` can only be used as a complete statement [StatementOperatorAsSubExpr] diff --git a/toolchain/parse/testdata/operators/fail_precedence_where.carbon b/toolchain/parse/testdata/operators/fail_precedence_where.carbon index 03f24eab63a4..62060e529070 100644 --- a/toolchain/parse/testdata/operators/fail_precedence_where.carbon +++ b/toolchain/parse/testdata/operators/fail_precedence_where.carbon @@ -19,7 +19,7 @@ fn AndWhere() { // --- fail_where_or.carbon fn WhereOr() { - // CHECK:STDERR: fail_where_or.carbon:[[@LINE+2]]:22-23: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_where_or.carbon:[[@LINE+2]]:21: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: U where .C impls D or false; } @@ -35,7 +35,7 @@ fn EqualsWhere() { // --- fail_where_neq.carbon fn WhereNEq() { - // CHECK:STDERR: fail_where_neq.carbon:[[@LINE+2]]:21-22: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_where_neq.carbon:[[@LINE+2]]:20: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: W where .H = bool != i; } @@ -43,7 +43,7 @@ fn WhereNEq() { // --- fail_less_requirement.carbon fn LessRequirement() { - // CHECK:STDERR: fail_less_requirement.carbon:[[@LINE+2]]:14: error: requirement should use `impls`, `=`, or `==` operator [ExpectedRequirementOperator] + // CHECK:STDERR: fail_less_requirement.carbon:[[@LINE+2]]:13: error: requirement should use `impls`, `=`, or `==` operator [ExpectedRequirementOperator] // CHECK:STDERR: X where .J < .K; } @@ -59,7 +59,7 @@ fn AsWhere() { // --- fail_where_as.carbon fn WhereAs() { - // CHECK:STDERR: fail_where_as.carbon:[[@LINE+2]]:21-22: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_where_as.carbon:[[@LINE+2]]:20: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: S where .N == i32 as type; } @@ -67,7 +67,7 @@ fn WhereAs() { // --- fail_as_in_argument.carbon fn AsInArgument() { - // CHECK:STDERR: fail_as_in_argument.carbon:[[@LINE+2]]:14-15: error: requirement should use `impls`, `=`, or `==` operator [ExpectedRequirementOperator] + // CHECK:STDERR: fail_as_in_argument.carbon:[[@LINE+2]]:13: error: requirement should use `impls`, `=`, or `==` operator [ExpectedRequirementOperator] // CHECK:STDERR: R where .O as type impls P; } @@ -75,7 +75,7 @@ fn AsInArgument() { // --- fail_greater_in_argument.carbon fn GreaterInArgument() { - // CHECK:STDERR: fail_greater_in_argument.carbon:[[@LINE+2]]:15: error: requirement should use `impls`, `=`, or `==` operator [ExpectedRequirementOperator] + // CHECK:STDERR: fail_greater_in_argument.carbon:[[@LINE+2]]:14: error: requirement should use `impls`, `=`, or `==` operator [ExpectedRequirementOperator] // CHECK:STDERR: Q where .AA > .BB impls CC; } diff --git a/toolchain/parse/testdata/operators/fail_star_minus.carbon b/toolchain/parse/testdata/operators/fail_star_minus.carbon index afa7b9c2a0da..0018039ea59c 100644 --- a/toolchain/parse/testdata/operators/fail_star_minus.carbon +++ b/toolchain/parse/testdata/operators/fail_star_minus.carbon @@ -13,7 +13,7 @@ // available because it has already accepted that first part of the expression, // so it is recovering by using the second option, but the diagnostic should // ideally offer (or consider) both fixes as alternatives. -// CHECK:STDERR: fail_star_minus.carbon:[[@LINE+2]]:16: error: whitespace missing after binary operator [BinaryOperatorRequiresWhitespace] +// CHECK:STDERR: fail_star_minus.carbon:[[@LINE+2]]:17: error: whitespace missing after binary operator [BinaryOperatorRequiresWhitespace] // CHECK:STDERR: var n: i8 = n* -n; diff --git a/toolchain/parse/testdata/operators/fail_star_star.carbon b/toolchain/parse/testdata/operators/fail_star_star.carbon index 2d45ecf19617..5b734130a8fa 100644 --- a/toolchain/parse/testdata/operators/fail_star_star.carbon +++ b/toolchain/parse/testdata/operators/fail_star_star.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/operators/fail_star_star.carbon -// CHECK:STDERR: fail_star_star.carbon:[[@LINE+2]]:16: error: whitespace missing after binary operator [BinaryOperatorRequiresWhitespace] +// CHECK:STDERR: fail_star_star.carbon:[[@LINE+2]]:17: error: whitespace missing after binary operator [BinaryOperatorRequiresWhitespace] // CHECK:STDERR: var n: i8 = n* *p; diff --git a/toolchain/parse/testdata/operators/modifier.carbon b/toolchain/parse/testdata/operators/modifier.carbon index 68cd744a4a64..374512b10bc2 100644 --- a/toolchain/parse/testdata/operators/modifier.carbon +++ b/toolchain/parse/testdata/operators/modifier.carbon @@ -25,7 +25,7 @@ fn G() { // CHECK:STDERR: a unsafe * b; - // CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:5-10: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: fail_invalid.carbon:[[@LINE+2]]:4: error: expected `;` after expression statement [ExpectedExprSemi] // CHECK:STDERR: a unsafe; diff --git a/toolchain/parse/testdata/operators/unary.carbon b/toolchain/parse/testdata/operators/unary.carbon index ba47185b9bde..d90efdd7df21 100644 --- a/toolchain/parse/testdata/operators/unary.carbon +++ b/toolchain/parse/testdata/operators/unary.carbon @@ -11,7 +11,7 @@ // --- fail_space_before.carbon fn F() { - // CHECK:STDERR: fail_space_before.carbon:[[@LINE+2]]:16-17: error: whitespace is required before this unary operator [UnaryOperatorRequiresWhitespace] + // CHECK:STDERR: fail_space_before.carbon:[[@LINE+2]]:16: error: whitespace is required before this unary operator [UnaryOperatorRequiresWhitespace] // CHECK:STDERR: if (false) {}--0; } diff --git a/toolchain/parse/testdata/package_expr/fail_in_name.carbon b/toolchain/parse/testdata/package_expr/fail_in_name.carbon index 2f5fb67b6337..6806f4473551 100644 --- a/toolchain/parse/testdata/package_expr/fail_in_name.carbon +++ b/toolchain/parse/testdata/package_expr/fail_in_name.carbon @@ -8,22 +8,22 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/package_expr/fail_in_name.carbon -// CHECK:STDERR: fail_in_name.carbon:[[@LINE+2]]:14: error: unexpected expression before : in binding pattern [ExpectedBindingName] +// CHECK:STDERR: fail_in_name.carbon:[[@LINE+2]]:5-13: error: unexpected expression before `:` in binding pattern [ExpectedBindingName] // CHECK:STDERR: var package.n: i32; // `val` is a keyword, but during error recovery we treat it as an identifier. // CHECK:STDERR: fail_in_name.carbon:[[@LINE+4]]:13-15: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: -// CHECK:STDERR: fail_in_name.carbon:[[@LINE+2]]:16: error: unexpected expression before : in binding pattern [ExpectedBindingName] +// CHECK:STDERR: fail_in_name.carbon:[[@LINE+2]]:5-15: error: unexpected expression before `:` in binding pattern [ExpectedBindingName] // CHECK:STDERR: var package.val: i32; -// CHECK:STDERR: fail_in_name.carbon:[[@LINE+2]]:11-17: error: `namespace` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_in_name.carbon:[[@LINE+2]]:11-17: error: `namespace` introducer should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclName] // CHECK:STDERR: namespace package.NS; -// CHECK:STDERR: fail_in_name.carbon:[[@LINE+2]]:7-13: error: `class` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_in_name.carbon:[[@LINE+2]]:7-13: error: `class` introducer should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclName] // CHECK:STDERR: class package.C { } @@ -43,10 +43,15 @@ class package.C { // CHECK:STDOUT: │ ├─VariablePattern 'var' has_error // CHECK:STDOUT: ├─VariableDecl ';' // CHECK:STDOUT: │ ╭─NamespaceStart 'namespace' -// CHECK:STDOUT: │ ├─InvalidParse 'package' has_error -// CHECK:STDOUT: ├─Namespace ';' has_error -// CHECK:STDOUT: │ ╭─ClassIntroducer 'class' -// CHECK:STDOUT: │ ├─InvalidParse 'package' has_error -// CHECK:STDOUT: ├─ClassDecl '}' has_error +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'package' +// CHECK:STDOUT: │ ├─IdentifierNameQualifierWithoutParams '.' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'NS' +// CHECK:STDOUT: ├─Namespace ';' +// CHECK:STDOUT: │ ╭─ClassIntroducer 'class' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'package' +// CHECK:STDOUT: │ ├─IdentifierNameQualifierWithoutParams '.' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'C' +// CHECK:STDOUT: │ ╭─ClassDefinitionStart '{' +// CHECK:STDOUT: ├─ClassDefinition '}' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) diff --git a/toolchain/parse/testdata/packages/export.carbon b/toolchain/parse/testdata/packages/export.carbon index e5e5d3243d7d..83de0ecdf73c 100644 --- a/toolchain/parse/testdata/packages/export.carbon +++ b/toolchain/parse/testdata/packages/export.carbon @@ -28,7 +28,7 @@ export Foo.Bar; package Pkg; -// CHECK:STDERR: fail_expr.carbon:[[@LINE+2]]:8: error: `export` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_expr.carbon:[[@LINE+2]]:7: error: `export` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: export (); @@ -36,7 +36,7 @@ export (); package Pkg; -// CHECK:STDERR: fail_package_in_name.carbon:[[@LINE+2]]:8-14: error: `export` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_package_in_name.carbon:[[@LINE+2]]:8-14: error: `export` introducer should be followed by a name; prefix reserved word with `r#` to form a valid identifier [ExpectedDeclName] // CHECK:STDERR: export package.Bar; @@ -44,7 +44,7 @@ export package.Bar; package Pkg; -// CHECK:STDERR: fail_keyword_only:[[@LINE+2]]:1-6: error: `export` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_keyword_only:[[@LINE+2]]:7: error: `export` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: export @@ -52,7 +52,7 @@ export package Pkg; -// CHECK:STDERR: fail_no_name.carbon:[[@LINE+2]]:8: error: `export` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_no_name.carbon:[[@LINE+2]]:7: error: `export` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: export ; @@ -60,9 +60,9 @@ export ; package Pkg; -export Foo -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:1: error: `export` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:11: error: `export` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: +export Foo // --- fail_incomplete_qual_name.carbon @@ -76,7 +76,7 @@ export Foo.; package Pkg; -// CHECK:STDERR: fail_incomplete_qual_name2.carbon:[[@LINE+2]]:8: error: `export` introducer should be followed by a name [ExpectedDeclName] +// CHECK:STDERR: fail_incomplete_qual_name2.carbon:[[@LINE+2]]:7: error: `export` introducer should be followed by a name [ExpectedDeclName] // CHECK:STDERR: export .Bar; @@ -158,8 +158,10 @@ export Foo; // CHECK:STDOUT: │ ├─IdentifierPackageName 'Pkg' // CHECK:STDOUT: ├─PackageDecl ';' // CHECK:STDOUT: │ ╭─ExportIntroducer 'export' -// CHECK:STDOUT: │ ├─InvalidParse 'package' has_error -// CHECK:STDOUT: ├─ExportDecl ';' has_error +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'package' +// CHECK:STDOUT: │ ├─IdentifierNameQualifierWithoutParams '.' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'Bar' +// CHECK:STDOUT: ├─ExportDecl ';' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) // CHECK:STDOUT: - filename: fail_keyword_only diff --git a/toolchain/parse/testdata/packages/import/cpp_inline.carbon b/toolchain/parse/testdata/packages/import/cpp_inline.carbon index 6719d012aace..f0897ad4c2c2 100644 --- a/toolchain/parse/testdata/packages/import/cpp_inline.carbon +++ b/toolchain/parse/testdata/packages/import/cpp_inline.carbon @@ -13,34 +13,34 @@ // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:18: error: expected string literal after `inline` [ExpectedStringAfterInline] // CHECK:STDERR: import Cpp inline; -// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:19-25: error: expected string literal after `inline` [ExpectedStringAfterInline] +// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:18: error: expected string literal after `inline` [ExpectedStringAfterInline] // CHECK:STDERR: import Cpp inline library "foo.h"; // TODO: We might want to allow this as a "preprocessed source" syntax. -// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:28-33: error: `import` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:27: error: `import` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: import Cpp library "foo.h" inline "bar"; -// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:24-29: error: `import` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+2]]:23: error: `import` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: import library "foo.h" inline "bar"; // --- fail_no_semi_at_eof.carbon -import Cpp inline -// CHECK:STDERR: fail_no_semi_at_eof.carbon:[[@LINE+2]]:1: error: expected string literal after `inline` [ExpectedStringAfterInline] +// CHECK:STDERR: fail_no_semi_at_eof.carbon:[[@LINE+2]]:18: error: expected string literal after `inline` [ExpectedStringAfterInline] // CHECK:STDERR: +import Cpp inline // --- fail_package_inline.carbon -// CHECK:STDERR: fail_package_inline.carbon:[[@LINE+2]]:16-21: error: `package` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_package_inline.carbon:[[@LINE+2]]:15: error: `package` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: package NotCpp inline "int n;"; // --- fail_library_inline.carbon -// CHECK:STDERR: fail_library_inline.carbon:[[@LINE+2]]:17-22: error: `library` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_library_inline.carbon:[[@LINE+2]]:16: error: `library` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: library "foo.h" inline "int n;"; @@ -70,7 +70,7 @@ int n; // CHECK:STDERR: inline; -// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+2]]:8-21: error: expected `Cpp` after `inline` [ExpectedCppAfterInline] +// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+2]]:7: error: expected `Cpp` after `inline` [ExpectedCppAfterInline] // CHECK:STDERR: inline ''' int n; @@ -78,7 +78,7 @@ int n; // Recovery skips to the `;` even when tokens follow the closing `'''` on its // line, so the `;` ends the declaration rather than starting an empty one. -// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+2]]:8-21: error: expected `Cpp` after `inline` [ExpectedCppAfterInline] +// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+2]]:7: error: expected `Cpp` after `inline` [ExpectedCppAfterInline] // CHECK:STDERR: inline ''' int n; @@ -88,19 +88,10 @@ int n; // CHECK:STDERR: inline Cpp; -// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+2]]:12: error: expected string literal after `inline Cpp` [ExpectedStringAfterInlineCpp] +// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+2]]:11: error: expected string literal after `inline Cpp` [ExpectedStringAfterInlineCpp] // CHECK:STDERR: inline Cpp x; -inline Cpp ''' -int n; -''' -// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+2]]:1: error: `inline` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: - -// --- eof.carbon -// This split exists to avoid the "no `;` at EOF" diagnostic above pointing to -// the last CHECK:STDOUT line below. // CHECK:STDOUT: - filename: fail_bad_syntax.carbon // CHECK:STDOUT: ╭─FileStart '' @@ -198,17 +189,5 @@ int n; // CHECK:STDOUT: │ ╭─InlineIntroducer 'inline' // CHECK:STDOUT: │ ├─CppNameExpr 'Cpp' // CHECK:STDOUT: ├─InlineCppDecl ';' has_error -// CHECK:STDOUT: │ ╭─InlineIntroducer 'inline' -// CHECK:STDOUT: │ ├─CppNameExpr 'Cpp' -// CHECK:STDOUT: │ ├─InlineImportBody '''' -// CHECK:STDOUT: int n; -// CHECK:STDOUT: '''' -// CHECK:STDOUT: ├─InlineCppDecl '''' -// CHECK:STDOUT: int n; -// CHECK:STDOUT: '''' has_error -// CHECK:STDOUT: ├─FileEnd '' -// CHECK:STDOUT: (root) -// CHECK:STDOUT: - filename: eof.carbon -// CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) diff --git a/toolchain/parse/testdata/packages/import/export.carbon b/toolchain/parse/testdata/packages/import/export.carbon index 07d47543eaf5..b1414c15d38b 100644 --- a/toolchain/parse/testdata/packages/import/export.carbon +++ b/toolchain/parse/testdata/packages/import/export.carbon @@ -12,7 +12,7 @@ package Pkg; -// CHECK:STDERR: fail_package.carbon:[[@LINE+2]]:15-17: error: `export` cannot be used when importing a package [ExportImportPackage] +// CHECK:STDERR: fail_package.carbon:[[@LINE+2]]:1-6: error: `export` cannot be used when importing a package [ExportImportPackage] // CHECK:STDERR: export import Bar; diff --git a/toolchain/parse/testdata/packages/import/fail_extra_string.carbon b/toolchain/parse/testdata/packages/import/fail_extra_string.carbon index c2a81afdc8f2..c4fdb34572bf 100644 --- a/toolchain/parse/testdata/packages/import/fail_extra_string.carbon +++ b/toolchain/parse/testdata/packages/import/fail_extra_string.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/import/fail_extra_string.carbon -// CHECK:STDERR: fail_extra_string.carbon:[[@LINE+2]]:26-30: error: `import` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_extra_string.carbon:[[@LINE+2]]:25: error: `import` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: import Foo library "bar" "baz"; diff --git a/toolchain/parse/testdata/packages/import/fail_inline_cpp_no_semi.carbon b/toolchain/parse/testdata/packages/import/fail_inline_cpp_no_semi.carbon new file mode 100644 index 000000000000..3493419f9d20 --- /dev/null +++ b/toolchain/parse/testdata/packages/import/fail_inline_cpp_no_semi.carbon @@ -0,0 +1,32 @@ +// 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 + +// The missing `;` goes just past the closing `'''`. +inline Cpp ''' +int n; +''' + +// The CHECK lines go below, because a diagnostic on the closing `'''` line +// otherwise puts them inside the block string. +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/packages/import/fail_inline_cpp_no_semi.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/import/fail_inline_cpp_no_semi.carbon +// CHECK:STDERR: fail_inline_cpp_no_semi.carbon:[[@LINE-10]]:4: error: `inline` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: + +// CHECK:STDOUT: - filename: fail_inline_cpp_no_semi.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─InlineIntroducer 'inline' +// CHECK:STDOUT: │ ├─CppNameExpr 'Cpp' +// CHECK:STDOUT: │ ├─InlineImportBody '''' +// CHECK:STDOUT: int n; +// CHECK:STDOUT: '''' +// CHECK:STDOUT: ├─InlineCppDecl '''' +// CHECK:STDOUT: int n; +// CHECK:STDOUT: '''' has_error +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) diff --git a/toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon b/toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon index b4dda701651f..b69f0cd5de72 100644 --- a/toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon +++ b/toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon -// CHECK:STDERR: fail_library_is_identifier.carbon:[[@LINE+2]]:25-30: error: expected a string literal to specify the library name [ExpectedLibraryName] +// CHECK:STDERR: fail_library_is_identifier.carbon:[[@LINE+2]]:24: error: expected a string literal to specify the library name [ExpectedLibraryName] // CHECK:STDERR: import Geometry library Shapes; diff --git a/toolchain/parse/testdata/packages/import/fail_name_is_keyword.carbon b/toolchain/parse/testdata/packages/import/fail_name_is_keyword.carbon index a97524223ad9..ae1aa14b046f 100644 --- a/toolchain/parse/testdata/packages/import/fail_name_is_keyword.carbon +++ b/toolchain/parse/testdata/packages/import/fail_name_is_keyword.carbon @@ -8,13 +8,14 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/import/fail_name_is_keyword.carbon -// CHECK:STDERR: fail_name_is_keyword.carbon:[[@LINE+2]]:8-9: error: expected identifier or `library` after `import` [ExpectedIdentifierAfterImport] +// CHECK:STDERR: fail_name_is_keyword.carbon:[[@LINE+2]]:8-9: error: expected identifier or `library` after `import`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterImport] // CHECK:STDERR: import fn; // CHECK:STDOUT: - filename: fail_name_is_keyword.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─ImportIntroducer 'import' -// CHECK:STDOUT: ├─ImportDecl ';' has_error +// CHECK:STDOUT: │ ├─IdentifierPackageName 'fn' +// CHECK:STDOUT: ├─ImportDecl ';' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) diff --git a/toolchain/parse/testdata/packages/import/fail_no_semi.carbon b/toolchain/parse/testdata/packages/import/fail_no_semi.carbon index 6378aa768c64..d15fa62d2850 100644 --- a/toolchain/parse/testdata/packages/import/fail_no_semi.carbon +++ b/toolchain/parse/testdata/packages/import/fail_no_semi.carbon @@ -8,10 +8,10 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/import/fail_no_semi.carbon +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:16: error: `import` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: import Geometry -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+8]]:26: error: `import` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_no_semi.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─ImportIntroducer 'import' diff --git a/toolchain/parse/testdata/packages/import/fail_omit_library_keyword.carbon b/toolchain/parse/testdata/packages/import/fail_omit_library_keyword.carbon index dc2126ac8285..477460d04ead 100644 --- a/toolchain/parse/testdata/packages/import/fail_omit_library_keyword.carbon +++ b/toolchain/parse/testdata/packages/import/fail_omit_library_keyword.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/import/fail_omit_library_keyword.carbon -// CHECK:STDERR: fail_omit_library_keyword.carbon:[[@LINE+2]]:17-24: error: missing `library` keyword [MissingLibraryKeyword] +// CHECK:STDERR: fail_omit_library_keyword.carbon:[[@LINE+2]]:16: error: missing `library` keyword [MissingLibraryKeyword] // CHECK:STDERR: import Geometry "Shapes"; diff --git a/toolchain/parse/testdata/packages/import/fail_type.carbon b/toolchain/parse/testdata/packages/import/fail_type.carbon index 2f8657d0bc0d..60f636f3170c 100644 --- a/toolchain/parse/testdata/packages/import/fail_type.carbon +++ b/toolchain/parse/testdata/packages/import/fail_type.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/import/fail_type.carbon -// CHECK:STDERR: fail_type.carbon:[[@LINE+2]]:17-19: error: `import` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_type.carbon:[[@LINE+2]]:16: error: `import` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: import Geometry api; diff --git a/toolchain/parse/testdata/packages/library/fail_invalid_name.carbon b/toolchain/parse/testdata/packages/library/fail_invalid_name.carbon index 432b42f3a655..7bc11e2f2801 100644 --- a/toolchain/parse/testdata/packages/library/fail_invalid_name.carbon +++ b/toolchain/parse/testdata/packages/library/fail_invalid_name.carbon @@ -10,13 +10,13 @@ // --- fail_identifier.carbon -// CHECK:STDERR: fail_identifier.carbon:[[@LINE+2]]:9-14: error: expected `default` or a string literal to specify the library name [ExpectedLibraryNameOrDefault] +// CHECK:STDERR: fail_identifier.carbon:[[@LINE+2]]:8: error: expected `default` or a string literal to specify the library name [ExpectedLibraryNameOrDefault] // CHECK:STDERR: library Shapes; // --- fail_raw_identifier.carbon -// CHECK:STDERR: fail_raw_identifier.carbon:[[@LINE+2]]:9-17: error: expected `default` or a string literal to specify the library name [ExpectedLibraryNameOrDefault] +// CHECK:STDERR: fail_raw_identifier.carbon:[[@LINE+2]]:8: error: expected `default` or a string literal to specify the library name [ExpectedLibraryNameOrDefault] // CHECK:STDERR: library r#default; @@ -34,16 +34,16 @@ library; // --- fail_trailing_impl.carbon -// CHECK:STDERR: fail_trailing_impl.carbon:[[@LINE+2]]:25-28: error: `library` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_trailing_impl.carbon:[[@LINE+2]]:24: error: `library` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: library "[[@TEST_NAME]]" impl // --- fail_no_semi.carbon +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:18: error: `library` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: library "[[@TEST_NAME]]" -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+39]]:26: error: `library` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_identifier.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─LibraryIntroducer 'library' diff --git a/toolchain/parse/testdata/packages/package/fail_extra_string.carbon b/toolchain/parse/testdata/packages/package/fail_extra_string.carbon index 02d90ab6e988..1c7109b5b174 100644 --- a/toolchain/parse/testdata/packages/package/fail_extra_string.carbon +++ b/toolchain/parse/testdata/packages/package/fail_extra_string.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/package/fail_extra_string.carbon -// CHECK:STDERR: fail_extra_string.carbon:[[@LINE+2]]:27-31: error: `package` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_extra_string.carbon:[[@LINE+2]]:26: error: `package` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: package Foo library "bar" "baz"; diff --git a/toolchain/parse/testdata/packages/package/fail_in_nested_scope.carbon b/toolchain/parse/testdata/packages/package/fail_in_nested_scope.carbon index 36b6ad5046d4..ec7c3a60d221 100644 --- a/toolchain/parse/testdata/packages/package/fail_in_nested_scope.carbon +++ b/toolchain/parse/testdata/packages/package/fail_in_nested_scope.carbon @@ -19,7 +19,7 @@ class C { fn F() { // `package` here is an expression naming the package scope. - // CHECK:STDERR: fail_in_nested_scope.carbon:[[@LINE+2]]:11: error: expected `.` after `package` expression [ExpectedPeriodAfterPackage] + // CHECK:STDERR: fail_in_nested_scope.carbon:[[@LINE+2]]:10: error: expected `.` after `package` expression [ExpectedPeriodAfterPackage] // CHECK:STDERR: package P; diff --git a/toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon b/toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon index 72e504ff1b27..be64adde337e 100644 --- a/toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon +++ b/toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon -// CHECK:STDERR: fail_library_is_identifier.carbon:[[@LINE+2]]:26-31: error: expected a string literal to specify the library name [ExpectedLibraryName] +// CHECK:STDERR: fail_library_is_identifier.carbon:[[@LINE+2]]:25: error: expected a string literal to specify the library name [ExpectedLibraryName] // CHECK:STDERR: package Geometry library Shapes; diff --git a/toolchain/parse/testdata/packages/package/fail_library_skips_name.carbon b/toolchain/parse/testdata/packages/package/fail_library_skips_name.carbon index 435515694546..c4f350a12ae0 100644 --- a/toolchain/parse/testdata/packages/package/fail_library_skips_name.carbon +++ b/toolchain/parse/testdata/packages/package/fail_library_skips_name.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/package/fail_library_skips_name.carbon -// CHECK:STDERR: fail_library_skips_name.carbon:[[@LINE+2]]:9-15: error: expected identifier after `package` [ExpectedIdentifierAfterPackage] +// CHECK:STDERR: fail_library_skips_name.carbon:[[@LINE+2]]:8: error: expected identifier after `package` [ExpectedIdentifierAfterPackage] // CHECK:STDERR: package library "Shapes"; diff --git a/toolchain/parse/testdata/packages/package/fail_name_is_keyword.carbon b/toolchain/parse/testdata/packages/package/fail_name_is_keyword.carbon index 6df8f6977a50..11084149dc25 100644 --- a/toolchain/parse/testdata/packages/package/fail_name_is_keyword.carbon +++ b/toolchain/parse/testdata/packages/package/fail_name_is_keyword.carbon @@ -8,13 +8,14 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/package/fail_name_is_keyword.carbon -// CHECK:STDERR: fail_name_is_keyword.carbon:[[@LINE+2]]:9-10: error: expected identifier after `package` [ExpectedIdentifierAfterPackage] +// CHECK:STDERR: fail_name_is_keyword.carbon:[[@LINE+2]]:9-10: error: expected identifier after `package`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPackage] // CHECK:STDERR: package fn; // CHECK:STDOUT: - filename: fail_name_is_keyword.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─PackageIntroducer 'package' -// CHECK:STDOUT: ├─PackageDecl ';' has_error +// CHECK:STDOUT: │ ├─IdentifierPackageName 'fn' +// CHECK:STDOUT: ├─PackageDecl ';' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) diff --git a/toolchain/parse/testdata/packages/package/fail_no_semi.carbon b/toolchain/parse/testdata/packages/package/fail_no_semi.carbon index 51c5fcdb3e86..ffc7ce5f83ce 100644 --- a/toolchain/parse/testdata/packages/package/fail_no_semi.carbon +++ b/toolchain/parse/testdata/packages/package/fail_no_semi.carbon @@ -8,10 +8,10 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/package/fail_no_semi.carbon +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:17: error: `package` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: package Geometry -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+8]]:26: error: `package` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_no_semi.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─PackageIntroducer 'package' diff --git a/toolchain/parse/testdata/packages/package/fail_omit_library_keyword.carbon b/toolchain/parse/testdata/packages/package/fail_omit_library_keyword.carbon index 257540f07b23..1fa2bc23afac 100644 --- a/toolchain/parse/testdata/packages/package/fail_omit_library_keyword.carbon +++ b/toolchain/parse/testdata/packages/package/fail_omit_library_keyword.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/package/fail_omit_library_keyword.carbon -// CHECK:STDERR: fail_omit_library_keyword.carbon:[[@LINE+2]]:18-25: error: missing `library` keyword [MissingLibraryKeyword] +// CHECK:STDERR: fail_omit_library_keyword.carbon:[[@LINE+2]]:17: error: missing `library` keyword [MissingLibraryKeyword] // CHECK:STDERR: package Geometry "Shapes"; diff --git a/toolchain/parse/testdata/packages/package/fail_trailing_impl.carbon b/toolchain/parse/testdata/packages/package/fail_trailing_impl.carbon index df8a6c44a7d2..10a2fbf64cb3 100644 --- a/toolchain/parse/testdata/packages/package/fail_trailing_impl.carbon +++ b/toolchain/parse/testdata/packages/package/fail_trailing_impl.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/package/fail_trailing_impl.carbon -// CHECK:STDERR: fail_trailing_impl.carbon:[[@LINE+2]]:18-21: error: `package` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_trailing_impl.carbon:[[@LINE+2]]:17: error: `package` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: package Geometry impl; diff --git a/toolchain/parse/testdata/pointer/fail_tuple_instead_of_compound_member_access.carbon b/toolchain/parse/testdata/pointer/fail_tuple_instead_of_compound_member_access.carbon index aec3aadc823a..ffb006f3bd48 100644 --- a/toolchain/parse/testdata/pointer/fail_tuple_instead_of_compound_member_access.carbon +++ b/toolchain/parse/testdata/pointer/fail_tuple_instead_of_compound_member_access.carbon @@ -21,7 +21,7 @@ fn F() { // CHECK:STDERR: z.(a, b); - // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+2]]:8: error: expected `)` [UnexpectedTokenInCompoundMemberAccess] + // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+2]]:7: error: expected `)` [UnexpectedTokenInCompoundMemberAccess] // CHECK:STDERR: w.(c d e ()); @@ -37,7 +37,7 @@ fn F() { // CHECK:STDERR: t->(2, 3); - // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+2]]:9: error: expected `)` [UnexpectedTokenInCompoundMemberAccess] + // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+2]]:8: error: expected `)` [UnexpectedTokenInCompoundMemberAccess] // CHECK:STDERR: s->(g h i ()); } diff --git a/toolchain/parse/testdata/return/fail_expr_no_semi.carbon b/toolchain/parse/testdata/return/fail_expr_no_semi.carbon index c3bf6c19c183..36a04827e407 100644 --- a/toolchain/parse/testdata/return/fail_expr_no_semi.carbon +++ b/toolchain/parse/testdata/return/fail_expr_no_semi.carbon @@ -9,9 +9,9 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/return/fail_expr_no_semi.carbon fn F() { + // CHECK:STDERR: fail_expr_no_semi.carbon:[[@LINE+2]]:11: error: `return` statements must end with a `;` [ExpectedStatementSemi] + // CHECK:STDERR: return x -// CHECK:STDERR: fail_expr_no_semi.carbon:[[@LINE+2]]:1: error: `return` statements must end with a `;` [ExpectedStatementSemi] -// CHECK:STDERR: } // CHECK:STDOUT: - filename: fail_expr_no_semi.carbon diff --git a/toolchain/parse/testdata/return/fail_no_semi.carbon b/toolchain/parse/testdata/return/fail_no_semi.carbon index 1982954ca8f7..6bc2ef414082 100644 --- a/toolchain/parse/testdata/return/fail_no_semi.carbon +++ b/toolchain/parse/testdata/return/fail_no_semi.carbon @@ -9,11 +9,11 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/return/fail_no_semi.carbon fn F() { + // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+4]]:9: error: expected expression [ExpectedExpr] + // CHECK:STDERR: + // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:9: error: `return` statements must end with a `;` [ExpectedStatementSemi] + // CHECK:STDERR: return -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+4]]:1: error: expected expression [ExpectedExpr] -// CHECK:STDERR: -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:1: error: `return` statements must end with a `;` [ExpectedStatementSemi] -// CHECK:STDERR: } // CHECK:STDOUT: - filename: fail_no_semi.carbon diff --git a/toolchain/parse/testdata/return/fail_returned_no_var.carbon b/toolchain/parse/testdata/return/fail_returned_no_var.carbon index f893a82d48d9..a4eaf9d52f86 100644 --- a/toolchain/parse/testdata/return/fail_returned_no_var.carbon +++ b/toolchain/parse/testdata/return/fail_returned_no_var.carbon @@ -9,7 +9,7 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/return/fail_returned_no_var.carbon fn F() -> str { - // CHECK:STDERR: fail_returned_no_var.carbon:[[@LINE+2]]:12-13: error: expected `var` after `returned` [ExpectedVarAfterReturned] + // CHECK:STDERR: fail_returned_no_var.carbon:[[@LINE+2]]:11: error: expected `var` after `returned` [ExpectedVarAfterReturned] // CHECK:STDERR: returned fn G() -> i32; } diff --git a/toolchain/parse/testdata/return/fail_var_no_semi.carbon b/toolchain/parse/testdata/return/fail_var_no_semi.carbon index 556b66e8bbd3..bf41ff1aba00 100644 --- a/toolchain/parse/testdata/return/fail_var_no_semi.carbon +++ b/toolchain/parse/testdata/return/fail_var_no_semi.carbon @@ -9,9 +9,9 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/return/fail_var_no_semi.carbon fn F() { + // CHECK:STDERR: fail_var_no_semi.carbon:[[@LINE+2]]:13: error: `return` statements must end with a `;` [ExpectedStatementSemi] + // CHECK:STDERR: return var -// CHECK:STDERR: fail_var_no_semi.carbon:[[@LINE+2]]:1: error: `return` statements must end with a `;` [ExpectedStatementSemi] -// CHECK:STDERR: } // CHECK:STDOUT: - filename: fail_var_no_semi.carbon diff --git a/toolchain/parse/testdata/struct/fail_extra_token_in_type.carbon b/toolchain/parse/testdata/struct/fail_extra_token_in_type.carbon index 5cb03bdf26dc..4520546c4728 100644 --- a/toolchain/parse/testdata/struct/fail_extra_token_in_type.carbon +++ b/toolchain/parse/testdata/struct/fail_extra_token_in_type.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/struct/fail_extra_token_in_type.carbon -// CHECK:STDERR: fail_extra_token_in_type.carbon:[[@LINE+2]]:17-22: error: expected `,` or `}` [UnexpectedTokenAfterListElement] +// CHECK:STDERR: fail_extra_token_in_type.carbon:[[@LINE+2]]:16: error: expected `,` or `}` [UnexpectedTokenAfterListElement] // CHECK:STDERR: var x: {.a: i32 banana} = {.a = 0}; diff --git a/toolchain/parse/testdata/struct/fail_extra_token_in_value.carbon b/toolchain/parse/testdata/struct/fail_extra_token_in_value.carbon index cf4befa971eb..7f0d4b573cba 100644 --- a/toolchain/parse/testdata/struct/fail_extra_token_in_value.carbon +++ b/toolchain/parse/testdata/struct/fail_extra_token_in_value.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/struct/fail_extra_token_in_value.carbon -// CHECK:STDERR: fail_extra_token_in_value.carbon:[[@LINE+2]]:28-33: error: expected `,` or `}` [UnexpectedTokenAfterListElement] +// CHECK:STDERR: fail_extra_token_in_value.carbon:[[@LINE+2]]:27: error: expected `,` or `}` [UnexpectedTokenAfterListElement] // CHECK:STDERR: var x: {.a: i32} = {.a = 0 banana}; diff --git a/toolchain/parse/testdata/struct/fail_period_string_colon.carbon b/toolchain/parse/testdata/struct/fail_period_string_colon.carbon index 9ba996f91f32..13fb3b71856b 100644 --- a/toolchain/parse/testdata/struct/fail_period_string_colon.carbon +++ b/toolchain/parse/testdata/struct/fail_period_string_colon.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/struct/fail_period_string_colon.carbon -// CHECK:STDERR: fail_period_string_colon.carbon:[[@LINE+2]]:10-16: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] +// CHECK:STDERR: fail_period_string_colon.carbon:[[@LINE+2]]:10: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: var x: {."hello": i32, .y: i32} = {}; diff --git a/toolchain/parse/testdata/struct/fail_period_string_equals.carbon b/toolchain/parse/testdata/struct/fail_period_string_equals.carbon index 5c5bc28933f8..a4ad1e988036 100644 --- a/toolchain/parse/testdata/struct/fail_period_string_equals.carbon +++ b/toolchain/parse/testdata/struct/fail_period_string_equals.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/struct/fail_period_string_equals.carbon -// CHECK:STDERR: fail_period_string_equals.carbon:[[@LINE+2]]:10-16: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] +// CHECK:STDERR: fail_period_string_equals.carbon:[[@LINE+2]]:10: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: var x: {."hello" = 0, .y = 4} = {}; diff --git a/toolchain/parse/testdata/struct/struct_pattern.carbon b/toolchain/parse/testdata/struct/struct_pattern.carbon index 2519e51111c7..b71a90d1c65b 100644 --- a/toolchain/parse/testdata/struct/struct_pattern.carbon +++ b/toolchain/parse/testdata/struct/struct_pattern.carbon @@ -100,13 +100,13 @@ let {.x = x: i32,,} = {}; // --- fail_struct_pattern_missing_comma.carbon -// CHECK:STDERR: fail_struct_pattern_missing_comma.carbon:[[@LINE+2]]:21: error: expected `,` or `}` [UnexpectedTokenAfterListElement] +// CHECK:STDERR: fail_struct_pattern_missing_comma.carbon:[[@LINE+2]]:20: error: expected `,` or `}` [UnexpectedTokenAfterListElement] // CHECK:STDERR: let {.x = x: i32 .y = y: i32} = {}; // --- fail_struct_pattern_missing_pattern.carbon -// CHECK:STDERR: fail_struct_pattern_missing_pattern.carbon:[[@LINE+2]]:11: error: expected pattern [ExpectedPattern] +// CHECK:STDERR: fail_struct_pattern_missing_pattern.carbon:[[@LINE+2]]:10: error: expected pattern [ExpectedPattern] // CHECK:STDERR: let {.x = } = {}; @@ -118,19 +118,19 @@ let {:i32} = {}; // --- fail_struct_pattern_shorthand_missing_expression.carbon -// CHECK:STDERR: fail_struct_pattern_shorthand_missing_expression.carbon:[[@LINE+2]]:9: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_struct_pattern_shorthand_missing_expression.carbon:[[@LINE+2]]:8: error: expected expression [ExpectedExpr] // CHECK:STDERR: let {x: } = {}; // --- fail_struct_pattern_shorthand_missing_comma.carbon -// CHECK:STDERR: fail_struct_pattern_shorthand_missing_comma.carbon:[[@LINE+2]]:13: error: expected `,` or `}` [UnexpectedTokenAfterListElement] +// CHECK:STDERR: fail_struct_pattern_shorthand_missing_comma.carbon:[[@LINE+2]]:12: error: expected `,` or `}` [UnexpectedTokenAfterListElement] // CHECK:STDERR: let {x: i32 y: i32} = {}; // --- fail_struct_pattern_missing_equals.carbon -// CHECK:STDERR: fail_struct_pattern_missing_equals.carbon:[[@LINE+2]]:9: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] +// CHECK:STDERR: fail_struct_pattern_missing_equals.carbon:[[@LINE+2]]:8: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] // CHECK:STDERR: let {.x x:i32} = {}; @@ -148,25 +148,25 @@ let{_, .x = x: i32} = {}; // --- fail_struct_pattern_extra_token_end.carbon -// CHECK:STDERR: fail_struct_pattern_extra_token_end.carbon:[[@LINE+2]]:18-20: error: expected `,` or `}` [UnexpectedTokenAfterListElement] +// CHECK:STDERR: fail_struct_pattern_extra_token_end.carbon:[[@LINE+2]]:17: error: expected `,` or `}` [UnexpectedTokenAfterListElement] // CHECK:STDERR: let {.x = x: i32 foo, .y = y: i32} = {}; // --- fail_struct_pattern_extra_token_start.carbon -// CHECK:STDERR: fail_struct_pattern_extra_token_start.carbon:[[@LINE+2]]:23: error: expected `:` or `:?` in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_struct_pattern_extra_token_start.carbon:[[@LINE+2]]:22: error: expected `:` or `:?` in binding pattern [ExpectedBindingPattern] // CHECK:STDERR: let {.x = x: i32, foo .y = y: i32} = {}; // --- fail_struct_pattern_shorthand_extra_token_end.carbon -// CHECK:STDERR: fail_struct_pattern_shorthand_extra_token_end.carbon:[[@LINE+2]]:13-15: error: expected `,` or `}` [UnexpectedTokenAfterListElement] +// CHECK:STDERR: fail_struct_pattern_shorthand_extra_token_end.carbon:[[@LINE+2]]:12: error: expected `,` or `}` [UnexpectedTokenAfterListElement] // CHECK:STDERR: let {x: i32 foo, y: i32} = {}; // --- fail_struct_pattern_shorthand_extra_token_start.carbon -// CHECK:STDERR: fail_struct_pattern_shorthand_extra_token_start.carbon:[[@LINE+2]]:18: error: expected `:` or `:?` in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_struct_pattern_shorthand_extra_token_start.carbon:[[@LINE+2]]:17: error: expected `:` or `:?` in binding pattern [ExpectedBindingPattern] // CHECK:STDERR: let {x: i32, foo y: i32} = {}; @@ -179,22 +179,22 @@ let {!x = x: i32} = {}; // --- fail_struct_pattern_extra_token_middle.carbon // recover on `=` -// CHECK:STDERR: fail_struct_pattern_extra_token_middle.carbon:[[@LINE+2]]:9-11: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] +// CHECK:STDERR: fail_struct_pattern_extra_token_middle.carbon:[[@LINE+2]]:8: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] // CHECK:STDERR: let {.x foo = x: ()} = {}; // no equals or comma - recover on `}` -// CHECK:STDERR: fail_struct_pattern_extra_token_middle.carbon:[[@LINE+2]]:9-11: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] +// CHECK:STDERR: fail_struct_pattern_extra_token_middle.carbon:[[@LINE+2]]:8: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] // CHECK:STDERR: let {.x foo x: ()} = {}; // no equals, trailing comma - recover on `,` -// CHECK:STDERR: fail_struct_pattern_extra_token_middle.carbon:[[@LINE+2]]:9-11: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] +// CHECK:STDERR: fail_struct_pattern_extra_token_middle.carbon:[[@LINE+2]]:8: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] // CHECK:STDERR: let {.x foo x: (),} = {}; // no equals, second field - recover on `,` -// CHECK:STDERR: fail_struct_pattern_extra_token_middle.carbon:[[@LINE+2]]:9-11: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] +// CHECK:STDERR: fail_struct_pattern_extra_token_middle.carbon:[[@LINE+2]]:8: error: expected `= value` after `.field` [ExpectedStructPatternDesignatedField] // CHECK:STDERR: let {.x foo x: (), .y = y: ()} = {}; @@ -224,13 +224,13 @@ let{x: i32, {y: i32, z: i32}} = {}; // --- fail_struct_pattern_unused_shorthand.carbon -// CHECK:STDERR: fail_struct_pattern_unused_shorthand.carbon:[[@LINE+2]]:7: error: anonymous binding found in struct pattern; use `.field = _: field_type` or `unused field: field_type` [AnonymousBindingInStructPattern] +// CHECK:STDERR: fail_struct_pattern_unused_shorthand.carbon:[[@LINE+2]]:6: error: anonymous binding found in struct pattern; use `.field = _: field_type` or `unused field: field_type` [AnonymousBindingInStructPattern] // CHECK:STDERR: var {_: i32} = {}; -// CHECK:STDERR: fail_struct_pattern_unused_shorthand.carbon:[[@LINE+2]]:11: error: anonymous binding found in struct pattern; use `.field = _: field_type` or `unused field: field_type` [AnonymousBindingInStructPattern] +// CHECK:STDERR: fail_struct_pattern_unused_shorthand.carbon:[[@LINE+2]]:10: error: anonymous binding found in struct pattern; use `.field = _: field_type` or `unused field: field_type` [AnonymousBindingInStructPattern] // CHECK:STDERR: let {ref _: i32} = {}; -// CHECK:STDERR: fail_struct_pattern_unused_shorthand.carbon:[[@LINE+2]]:11: error: anonymous binding found in struct pattern; use `.field = _: field_type` or `unused field: field_type` [AnonymousBindingInStructPattern] +// CHECK:STDERR: fail_struct_pattern_unused_shorthand.carbon:[[@LINE+2]]:10: error: anonymous binding found in struct pattern; use `.field = _: field_type` or `unused field: field_type` [AnonymousBindingInStructPattern] // CHECK:STDERR: let {var _: i32} = {}; diff --git a/toolchain/parse/testdata/typeof/basic.carbon b/toolchain/parse/testdata/typeof/basic.carbon index c9981e53e08a..5fcf1221291d 100644 --- a/toolchain/parse/testdata/typeof/basic.carbon +++ b/toolchain/parse/testdata/typeof/basic.carbon @@ -32,30 +32,39 @@ var x: typeof(); // --- fail_no_close_paren.carbon -// CHECK:STDERR: fail_no_close_paren.carbon:[[@LINE+2]]:17: error: unexpected tokens before `)` [ExpectedCloseSymbol] +// CHECK:STDERR: fail_no_close_paren.carbon:[[@LINE+3]]:17: error: unexpected tokens before `)` [ExpectedCloseSymbol] +// CHECK:STDERR: fail_no_close_paren.carbon:[[@LINE+2]]:14: note: matching this `(` [GroupOpenedHere] // CHECK:STDERR: var x: typeof(y z); +// --- fail_no_close_paren_multiline.carbon + +var x: typeof(y + // CHECK:STDERR: fail_no_close_paren_multiline.carbon:[[@LINE+3]]:15: error: unexpected tokens before `)` [ExpectedCloseSymbol] + // CHECK:STDERR: fail_no_close_paren_multiline.carbon:[[@LINE-2]]:14: note: matching this `(` [GroupOpenedHere] + // CHECK:STDERR: + z); + // --- fail_no_close_paren_before_eof_at_eof.carbon -// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+5]]:14: error: opening symbol without a corresponding closing symbol [UnmatchedOpening] -// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+4]]:15: note: possibly missing `)` here [PossiblyMissingBracketHere] +// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+7]]:14: error: opening symbol without a corresponding closing symbol [UnmatchedOpening] +// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+6]]:15: note: possibly missing `)` here [PossiblyMissingBracketHere] // CHECK:STDERR: -// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+2]]:15: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+4]]:15: error: expected expression [ExpectedExpr] +// CHECK:STDERR: +// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+2]]:15: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var x: typeof( -// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+2]]:1: error: `var` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // --- fail_no_close_paren_after_expr_at_eof.carbon -// CHECK:STDERR: fail_no_close_paren_after_expr_at_eof.carbon:[[@LINE+3]]:14: error: opening symbol without a corresponding closing symbol [UnmatchedOpening] -// CHECK:STDERR: fail_no_close_paren_after_expr_at_eof.carbon:[[@LINE+2]]:16: note: possibly missing `)` here [PossiblyMissingBracketHere] +// CHECK:STDERR: fail_no_close_paren_after_expr_at_eof.carbon:[[@LINE+5]]:14: error: opening symbol without a corresponding closing symbol [UnmatchedOpening] +// CHECK:STDERR: fail_no_close_paren_after_expr_at_eof.carbon:[[@LINE+4]]:16: note: possibly missing `)` here [PossiblyMissingBracketHere] +// CHECK:STDERR: +// CHECK:STDERR: fail_no_close_paren_after_expr_at_eof.carbon:[[@LINE+2]]:16: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var x: typeof(y -// CHECK:STDERR: fail_no_close_paren_after_expr_at_eof.carbon:[[@LINE+104]]:26: error: `var` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: basic.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─VariableIntroducer 'var' @@ -131,6 +140,20 @@ var x: typeof(y // CHECK:STDOUT: ├─VariableDecl ';' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_no_close_paren_multiline.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ │ ╭─TypeOfExprKeyword 'typeof' +// CHECK:STDOUT: │ │ │ ├─TypeOfExprOpenParen '(' +// CHECK:STDOUT: │ │ │ ├─IdentifierNameExpr 'y' +// CHECK:STDOUT: │ │ ├─TypeOfExpr ')' has_error +// CHECK:STDOUT: │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ ├─VariablePattern 'var' +// CHECK:STDOUT: ├─VariableDecl ';' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) // CHECK:STDOUT: - filename: fail_no_close_paren_before_eof_at_eof.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─VariableIntroducer 'var' diff --git a/toolchain/parse/testdata/var/fail_bad_name.carbon b/toolchain/parse/testdata/var/fail_bad_name.carbon index 323781d1c490..f7fe1cf20448 100644 --- a/toolchain/parse/testdata/var/fail_bad_name.carbon +++ b/toolchain/parse/testdata/var/fail_bad_name.carbon @@ -12,11 +12,11 @@ // CHECK:STDERR: var *; -// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+2]]:11: error: expected name in binding pattern; prefix reserved word with `r#` to form a valid identifier [ExpectedBindingPattern] +// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+2]]:5-10: error: expected name in binding pattern; prefix reserved word with `r#` to form a valid identifier [ExpectedBindingPattern] // CHECK:STDERR: var choice: i32; -// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+2]]:7: error: expected name in binding pattern; prefix reserved word with `r#` to form a valid identifier [ExpectedBindingPattern] +// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+2]]:5-6: error: expected name in binding pattern; prefix reserved word with `r#` to form a valid identifier [ExpectedBindingPattern] // CHECK:STDERR: var f2: f32; diff --git a/toolchain/parse/testdata/var/fail_in_interface.carbon b/toolchain/parse/testdata/var/fail_in_interface.carbon index d31f6d712d0d..02d03547cc00 100644 --- a/toolchain/parse/testdata/var/fail_in_interface.carbon +++ b/toolchain/parse/testdata/var/fail_in_interface.carbon @@ -9,7 +9,7 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/var/fail_in_interface.carbon interface I { - // CHECK:STDERR: fail_in_interface.carbon:[[@LINE+2]]:7-9: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] + // CHECK:STDERR: fail_in_interface.carbon:[[@LINE+2]]:6: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] // CHECK:STDERR: let var a: (); @@ -17,7 +17,7 @@ interface I { // CHECK:STDERR: var b: (); - // CHECK:STDERR: fail_in_interface.carbon:[[@LINE+2]]:7-9: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] + // CHECK:STDERR: fail_in_interface.carbon:[[@LINE+2]]:6: error: expected identifier in associated constant declaration [ExpectedAssociatedConstantIdentifier] // CHECK:STDERR: let var c: (); diff --git a/toolchain/parse/testdata/var/fail_no_semi.carbon b/toolchain/parse/testdata/var/fail_no_semi.carbon index 15edf405b0db..f700b77c3609 100644 --- a/toolchain/parse/testdata/var/fail_no_semi.carbon +++ b/toolchain/parse/testdata/var/fail_no_semi.carbon @@ -8,12 +8,12 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/var/fail_no_semi.carbon +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+4]]:4: error: expected pattern [ExpectedPattern] +// CHECK:STDERR: +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:4: error: `var` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: var -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+11]]:26: error: expected pattern [ExpectedPattern] -// CHECK:STDERR: -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+9]]:26: error: `var` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_no_semi.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─VariableIntroducer 'var' diff --git a/toolchain/parse/testdata/var/fail_runtime.carbon b/toolchain/parse/testdata/var/fail_runtime.carbon index 7e8de0d49030..44f0c477a33c 100644 --- a/toolchain/parse/testdata/var/fail_runtime.carbon +++ b/toolchain/parse/testdata/var/fail_runtime.carbon @@ -8,19 +8,19 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/var/fail_runtime.carbon -// CHECK:STDERR: fail_runtime.carbon:[[@LINE+6]]:5-11: error: `runtime` is redundant here; this binding is runtime by default [RedundantRuntimeModifier] +// CHECK:STDERR: fail_runtime.carbon:[[@LINE+10]]:5-11: error: `runtime` is redundant here; this binding is runtime by default [RedundantRuntimeModifier] // CHECK:STDERR: -// CHECK:STDERR: fail_runtime.carbon:[[@LINE+4]]:15: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_runtime.carbon:[[@LINE+8]]:15: error: expected expression [ExpectedExpr] // CHECK:STDERR: -// CHECK:STDERR: fail_runtime.carbon:[[@LINE+2]]:15: error: whitespace missing before binary operator [BinaryOperatorRequiresWhitespace] +// CHECK:STDERR: fail_runtime.carbon:[[@LINE+6]]:15: error: whitespace missing before binary operator [BinaryOperatorRequiresWhitespace] +// CHECK:STDERR: +// CHECK:STDERR: fail_runtime.carbon:[[@LINE+4]]:16: error: expected expression [ExpectedExpr] +// CHECK:STDERR: +// CHECK:STDERR: fail_runtime.carbon:[[@LINE+2]]:16: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var runtime u:| -// CHECK:STDERR: fail_runtime.carbon:[[@LINE+17]]:26: error: expected expression [ExpectedExpr] -// CHECK:STDERR: -// CHECK:STDERR: fail_runtime.carbon:[[@LINE+15]]:26: error: `var` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: fail_runtime.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─VariableIntroducer 'var' diff --git a/toolchain/parse/testdata/variadics/expand.carbon b/toolchain/parse/testdata/variadics/expand.carbon index 535e527b375f..d72a7e9bd5ce 100644 --- a/toolchain/parse/testdata/variadics/expand.carbon +++ b/toolchain/parse/testdata/variadics/expand.carbon @@ -34,7 +34,7 @@ var a: auto = not expand x; // --- fail_as_postfix.carbon -// CHECK:STDERR: fail_as_postfix.carbon:[[@LINE+2]]:17-22: error: `var` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_as_postfix.carbon:[[@LINE+2]]:16: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: var a: auto = x expand; diff --git a/toolchain/parse/testdata/variadics/pack_expansion_statement.carbon b/toolchain/parse/testdata/variadics/pack_expansion_statement.carbon index 2a4542c4e771..e657322940dc 100644 --- a/toolchain/parse/testdata/variadics/pack_expansion_statement.carbon +++ b/toolchain/parse/testdata/variadics/pack_expansion_statement.carbon @@ -49,9 +49,9 @@ fn F() { // --- fail_missing_semi.carbon fn F() { + // CHECK:STDERR: fail_missing_semi.carbon:[[@LINE+2]]:10: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: ... G() -// CHECK:STDERR: fail_missing_semi.carbon:[[@LINE+2]]:1: error: expected `;` after expression statement [ExpectedExprSemi] -// CHECK:STDERR: } // CHECK:STDOUT: - filename: expr_statement.carbon diff --git a/toolchain/parse/testdata/where_expr/designators.carbon b/toolchain/parse/testdata/where_expr/designators.carbon index 59c763c850d3..8d4fa4a37d09 100644 --- a/toolchain/parse/testdata/where_expr/designators.carbon +++ b/toolchain/parse/testdata/where_expr/designators.carbon @@ -26,7 +26,7 @@ fn FailNumeric() { // --- fail_keyword_designator.carbon fn FailKeyword() { - // CHECK:STDERR: fail_keyword_designator.carbon:[[@LINE+2]]:4-7: error: expected identifier or `Self` after `.` [ExpectedIdentifierOrSelfAfterPeriod] + // CHECK:STDERR: fail_keyword_designator.carbon:[[@LINE+2]]:4-7: error: expected identifier or `Self` after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierOrSelfAfterPeriod] // CHECK:STDERR: .base; } @@ -49,12 +49,12 @@ fn FailStar() { // --- fail_designator_eof.carbon +// CHECK:STDERR: fail_designator_eof.carbon:[[@LINE+4]]:15: error: expected identifier or `Self` after `.` [ExpectedIdentifierOrSelfAfterPeriod] +// CHECK:STDERR: +// CHECK:STDERR: fail_designator_eof.carbon:[[@LINE+2]]:15: error: `var` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: var x: i32 = . -// CHECK:STDERR: fail_designator_eof.carbon:[[@LINE+86]]:26: error: expected identifier or `Self` after `.` [ExpectedIdentifierOrSelfAfterPeriod] -// CHECK:STDERR: -// CHECK:STDERR: fail_designator_eof.carbon:[[@LINE+84]]:26: error: `var` declarations must end with a `;` [ExpectedDeclSemi] -// CHECK:STDERR: // CHECK:STDOUT: - filename: designator.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' diff --git a/toolchain/parse/testdata/where_expr/fail_rewrite.carbon b/toolchain/parse/testdata/where_expr/fail_rewrite.carbon index 8bc4f26a1453..4bf6f2f09dd0 100644 --- a/toolchain/parse/testdata/where_expr/fail_rewrite.carbon +++ b/toolchain/parse/testdata/where_expr/fail_rewrite.carbon @@ -31,7 +31,7 @@ fn DotSelf(generic W: K where .Self = f32); // --- fail_dot_keyword.carbon -// CHECK:STDERR: fail_dot_keyword.carbon:[[@LINE+2]]:35-37: error: expected identifier or `Self` after `.` [ExpectedIdentifierOrSelfAfterPeriod] +// CHECK:STDERR: fail_dot_keyword.carbon:[[@LINE+2]]:35-37: error: expected identifier or `Self` after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierOrSelfAfterPeriod] // CHECK:STDERR: fn DotKeyword(generic W: K where .and = u8); diff --git a/toolchain/parse/testdata/where_expr/where_and.carbon b/toolchain/parse/testdata/where_expr/where_and.carbon index ce7f0292ec87..ac27531ade8d 100644 --- a/toolchain/parse/testdata/where_expr/where_and.carbon +++ b/toolchain/parse/testdata/where_expr/where_and.carbon @@ -16,7 +16,7 @@ fn TwoAnd(generic Y: I where .J impls K and .L == .M and .N = bool); // --- fail_and_prefix.carbon -// CHECK:STDERR: fail_and_prefix.carbon:[[@LINE+2]]:36-38: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_and_prefix.carbon:[[@LINE+2]]:35: error: expected expression [ExpectedExpr] // CHECK:STDERR: fn AndPrefix(generic T: type where and .V == .W); @@ -28,7 +28,7 @@ fn AndSuffix(generic Y: I where .J impls K and); // --- fail_and_early.carbon -// CHECK:STDERR: fail_and_early.carbon:[[@LINE+2]]:35-37: error: requirement should use `impls`, `=`, or `==` operator [ExpectedRequirementOperator] +// CHECK:STDERR: fail_and_early.carbon:[[@LINE+2]]:34: error: requirement should use `impls`, `=`, or `==` operator [ExpectedRequirementOperator] // CHECK:STDERR: fn AndEarly(generic Z: L where .M and N); diff --git a/toolchain/parse/testdata/while/fail_no_semi.carbon b/toolchain/parse/testdata/while/fail_no_semi.carbon index 7e05199d5ff9..cbc2d4ab4804 100644 --- a/toolchain/parse/testdata/while/fail_no_semi.carbon +++ b/toolchain/parse/testdata/while/fail_no_semi.carbon @@ -11,14 +11,14 @@ fn F() { while (a) { if (b) { + // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:12: error: `break` statements must end with a `;` [ExpectedStatementSemi] + // CHECK:STDERR: break - // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:5: error: `break` statements must end with a `;` [ExpectedStatementSemi] - // CHECK:STDERR: } if (c) { + // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:15: error: `continue` statements must end with a `;` [ExpectedStatementSemi] + // CHECK:STDERR: continue - // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:5: error: `continue` statements must end with a `;` [ExpectedStatementSemi] - // CHECK:STDERR: } } } diff --git a/toolchain/parse/testdata/while/fail_unbraced.carbon b/toolchain/parse/testdata/while/fail_unbraced.carbon index ac021dfbbea2..15393ccee75b 100644 --- a/toolchain/parse/testdata/while/fail_unbraced.carbon +++ b/toolchain/parse/testdata/while/fail_unbraced.carbon @@ -10,9 +10,9 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/while/fail_unbraced.carbon fn F() { + // CHECK:STDERR: fail_unbraced.carbon:[[@LINE+2]]:12: error: expected braced code block [ExpectedCodeBlock] + // CHECK:STDERR: while (a) - // CHECK:STDERR: fail_unbraced.carbon:[[@LINE+2]]:5-9: error: expected braced code block [ExpectedCodeBlock] - // CHECK:STDERR: break; } diff --git a/toolchain/parse/tree_and_subtrees.cpp b/toolchain/parse/tree_and_subtrees.cpp index d7ff2390a73d..b45f111ca4ab 100644 --- a/toolchain/parse/tree_and_subtrees.cpp +++ b/toolchain/parse/tree_and_subtrees.cpp @@ -327,25 +327,8 @@ auto TreeAndSubtrees::NodeToDiagnosticLoc(NodeId node_id, bool token_only) const // Construct a location that encompasses all tokens that descend from this // node (including the root). - Lex::InclusiveTokenRange token_range = GetSubtreeTokenRange(node_id); - auto begin_loc = tree_->tokens().TokenToDiagnosticLoc(token_range.begin); - if (token_range.begin == token_range.end) { - return begin_loc; - } - auto end_loc = tree_->tokens().TokenToDiagnosticLoc(token_range.end); - begin_loc.last_byte_offset = end_loc.last_byte_offset; - // For multiline locations we simply return the rest of the line for now - // since true multiline locations are not yet supported. - if (begin_loc.loc.line_number != end_loc.loc.line_number) { - begin_loc.loc.length = - begin_loc.loc.line.size() - begin_loc.loc.column_number + 1; - } else { - if (begin_loc.loc.column_number != end_loc.loc.column_number) { - begin_loc.loc.length = end_loc.loc.column_number + end_loc.loc.length - - begin_loc.loc.column_number; - } - } - return begin_loc; + return tree_->tokens().TokenRangeToDiagnosticLoc( + GetSubtreeTokenRange(node_id)); } auto TreeAndSubtrees::SiblingIterator::Print(llvm::raw_ostream& output) const