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