Преглед на файлове

Add s plural format to IntAsSelect (#4423)

Per discussion on #toolchain, add "s" as a special-case for the common
plural format.

Note this removes periods from a few diagnostics; the periods shouldn't
be there per message style. Also, while I'm ignoring llvm::StringLiteral
uses, those should be addressed as #4416 -- this'll probably conflict
and make me clean up one or the other.
Jon Ross-Perkins преди 1 година
родител
ревизия
7c22348461
променени са 26 файла, в които са добавени 81 реда и са изтрити 62 реда
  1. 4 3
      toolchain/check/call.cpp
  2. 11 11
      toolchain/check/convert.cpp
  3. 1 1
      toolchain/check/testdata/array/fail_out_of_bound.carbon
  4. 2 2
      toolchain/check/testdata/array/fail_type_mismatch.carbon
  5. 3 3
      toolchain/check/testdata/builtins/float/negate.carbon
  6. 1 1
      toolchain/check/testdata/builtins/int/sadd.carbon
  7. 4 4
      toolchain/check/testdata/builtins/int/snegate.carbon
  8. 1 1
      toolchain/check/testdata/builtins/int/uadd.carbon
  9. 4 4
      toolchain/check/testdata/builtins/int/unegate.carbon
  10. 2 2
      toolchain/check/testdata/class/fail_init.carbon
  11. 1 1
      toolchain/check/testdata/class/fail_method.carbon
  12. 2 2
      toolchain/check/testdata/class/generic/call.carbon
  13. 1 1
      toolchain/check/testdata/class/virtual_modifiers.carbon
  14. 6 6
      toolchain/check/testdata/function/call/fail_param_count.carbon
  15. 3 3
      toolchain/check/testdata/function/generic/redeclare.carbon
  16. 1 1
      toolchain/check/testdata/struct/fail_assign_empty.carbon
  17. 1 1
      toolchain/check/testdata/struct/fail_assign_to_empty.carbon
  18. 1 1
      toolchain/check/testdata/struct/fail_too_few_values.carbon
  19. 1 1
      toolchain/check/testdata/tuple/fail_assign_nested.carbon
  20. 1 1
      toolchain/check/testdata/tuple/fail_too_few_element.carbon
  21. 1 1
      toolchain/check/testdata/tuple/import.carbon
  22. 1 1
      toolchain/check/testdata/tuple/no_prelude/fail_assign_empty.carbon
  23. 1 1
      toolchain/check/testdata/tuple/no_prelude/fail_assign_to_empty.carbon
  24. 6 1
      toolchain/diagnostics/format_providers.cpp
  25. 13 7
      toolchain/diagnostics/format_providers.h
  26. 8 1
      toolchain/diagnostics/format_providers_test.cpp

+ 4 - 3
toolchain/check/call.cpp

@@ -9,6 +9,7 @@
 #include "toolchain/check/convert.h"
 #include "toolchain/check/deduce.h"
 #include "toolchain/check/function.h"
+#include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/sem_ir/builtin_function_kind.h"
 #include "toolchain/sem_ir/builtin_inst_kind.h"
 #include "toolchain/sem_ir/entity_with_params_base.h"
@@ -42,9 +43,9 @@ static auto ResolveCalleeInCall(Context& context, SemIR::LocId loc_id,
   auto params = context.inst_blocks().GetOrEmpty(callee_info.param_refs_id);
   if (arg_ids.size() != params.size()) {
     CARBON_DIAGNOSTIC(CallArgCountMismatch, Error,
-                      "{0} argument(s) passed to {1} expecting "
-                      "{2} argument(s).",
-                      int, llvm::StringLiteral, int);
+                      "{0} argument{0:s} passed to {1} expecting "
+                      "{2} argument{2:s}",
+                      IntAsSelect, llvm::StringLiteral, IntAsSelect);
     CARBON_DIAGNOSTIC(InCallToEntity, Note, "calling {0} declared here",
                       llvm::StringLiteral);
     context.emitter()

+ 11 - 11
toolchain/check/convert.cpp

@@ -236,12 +236,12 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
   if (tuple_elem_types.size() != array_bound) {
     CARBON_DIAGNOSTIC(
         ArrayInitFromLiteralArgCountMismatch, Error,
-        "cannot initialize array of {0} element(s) from {1} initializer(s)",
-        uint64_t, size_t);
+        "cannot initialize array of {0} element{0:s} from {1} initializer{1:s}",
+        IntAsSelect, IntAsSelect);
     CARBON_DIAGNOSTIC(ArrayInitFromExprArgCountMismatch, Error,
-                      "cannot initialize array of {0} element(s) from tuple "
-                      "with {1} element(s).",
-                      uint64_t, size_t);
+                      "cannot initialize array of {0} element{0:s} from tuple "
+                      "with {1} element{1:s}",
+                      IntAsSelect, IntAsSelect);
     context.emitter().Emit(value_loc_id,
                            literal_elems.empty()
                                ? ArrayInitFromExprArgCountMismatch
@@ -320,9 +320,9 @@ static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type,
   // Check that the tuples are the same size.
   if (src_elem_types.size() != dest_elem_types.size()) {
     CARBON_DIAGNOSTIC(TupleInitElementCountMismatch, Error,
-                      "cannot initialize tuple of {0} element(s) from tuple "
-                      "with {1} element(s).",
-                      size_t, size_t);
+                      "cannot initialize tuple of {0} element{0:s} from tuple "
+                      "with {1} element{1:s}",
+                      IntAsSelect, IntAsSelect);
     context.emitter().Emit(value_loc_id, TupleInitElementCountMismatch,
                            dest_elem_types.size(), src_elem_types.size());
     return SemIR::InstId::BuiltinError;
@@ -414,9 +414,9 @@ static auto ConvertStructToStructOrClass(Context& context,
   if (src_elem_fields.size() != dest_elem_fields.size()) {
     CARBON_DIAGNOSTIC(
         StructInitElementCountMismatch, Error,
-        "cannot initialize {0:class|struct} with {1} field(s) from struct "
-        "with {2} field(s).",
-        BoolAsSelect, size_t, size_t);
+        "cannot initialize {0:class|struct} with {1} field{1:s} from struct "
+        "with {2} field{2:s}",
+        BoolAsSelect, IntAsSelect, IntAsSelect);
     context.emitter().Emit(value_loc_id, StructInitElementCountMismatch,
                            ToClass, dest_elem_fields.size(),
                            src_elem_fields.size());

+ 1 - 1
toolchain/check/testdata/array/fail_out_of_bound.carbon

@@ -8,7 +8,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/array/fail_out_of_bound.carbon
 
-// CHECK:STDERR: fail_out_of_bound.carbon:[[@LINE+3]]:19: error: cannot initialize array of 1 element(s) from 3 initializer(s)
+// CHECK:STDERR: fail_out_of_bound.carbon:[[@LINE+3]]:19: error: cannot initialize array of 1 element from 3 initializers
 // CHECK:STDERR: var a: [i32; 1] = (1, 2, 3);
 // CHECK:STDERR:                   ^~~~~~~~~
 var a: [i32; 1] = (1, 2, 3);

+ 2 - 2
toolchain/check/testdata/array/fail_type_mismatch.carbon

@@ -27,14 +27,14 @@ var t1: (i32, String, String);
 // CHECK:STDERR:
 var b: [i32; 3] = t1;
 
-// CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+4]]:19: error: cannot initialize array of 3 element(s) from 2 initializer(s)
+// CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+4]]:19: error: cannot initialize array of 3 elements from 2 initializers
 // CHECK:STDERR: var c: [i32; 3] = (1, 2);
 // CHECK:STDERR:                   ^~~~~~
 // CHECK:STDERR:
 var c: [i32; 3] = (1, 2);
 
 var t2: (i32, i32);
-// CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+3]]:19: error: cannot initialize array of 3 element(s) from tuple with 2 element(s).
+// CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+3]]:19: error: cannot initialize array of 3 elements from tuple with 2 elements
 // CHECK:STDERR: var d: [i32; 3] = t2;
 // CHECK:STDERR:                   ^~
 var d: [i32; 3] = t2;

+ 3 - 3
toolchain/check/testdata/builtins/float/negate.carbon

@@ -40,7 +40,7 @@ fn BadReturnType(a: f64) -> bool = "float.negate";
 fn JustRight(a: f64) -> f64 = "float.negate";
 
 fn RuntimeCallTooFew(a: f64) -> f64 {
-  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 1 argument(s) passed to function expecting 0 argument(s).
+  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 1 argument passed to function expecting 0 arguments
   // CHECK:STDERR:   return TooFew(a);
   // CHECK:STDERR:          ^~~~~~~
   // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-17]]:1: note: calling function declared here
@@ -51,7 +51,7 @@ fn RuntimeCallTooFew(a: f64) -> f64 {
 }
 
 fn RuntimeCallTooMany(a: f64, b: f64, c: f64) -> f64 {
-  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 3 argument(s) passed to function expecting 2 argument(s).
+  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 3 arguments passed to function expecting 2 arguments
   // CHECK:STDERR:   return TooMany(a, b, c);
   // CHECK:STDERR:          ^~~~~~~~
   // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-23]]:1: note: calling function declared here
@@ -62,7 +62,7 @@ fn RuntimeCallTooMany(a: f64, b: f64, c: f64) -> f64 {
 }
 
 fn RuntimeCallBadReturnType(a: f64, b: f64) -> bool {
-  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+6]]:10: error: 2 argument(s) passed to function expecting 1 argument(s).
+  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+6]]:10: error: 2 arguments passed to function expecting 1 argument
   // CHECK:STDERR:   return BadReturnType(a, b);
   // CHECK:STDERR:          ^~~~~~~~~~~~~~
   // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-29]]:1: note: calling function declared here

+ 1 - 1
toolchain/check/testdata/builtins/int/sadd.carbon

@@ -56,7 +56,7 @@ var too_many: [i32; TooMany(1, 2, 3)];
 // CHECK:STDERR:
 var bad_return_type: [i32; BadReturnType(1, 2)];
 
-// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:21: error: 3 argument(s) passed to function expecting 2 argument(s).
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:21: error: 3 arguments passed to function expecting 2 arguments
 // CHECK:STDERR: var bad_call: [i32; JustRight(1, 2, 3)];
 // CHECK:STDERR:                     ^~~~~~~~~~
 // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-21]]:1: note: calling function declared here

+ 4 - 4
toolchain/check/testdata/builtins/int/snegate.carbon

@@ -58,7 +58,7 @@ var too_many: [i32; TooMany(1, 2)];
 // CHECK:STDERR:
 var bad_return_type: [i32; BadReturnType(1)];
 
-// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:21: error: 2 argument(s) passed to function expecting 1 argument(s).
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:21: error: 2 arguments passed to function expecting 1 argument
 // CHECK:STDERR: var bad_call: [i32; JustRight(1, 2)];
 // CHECK:STDERR:                     ^~~~~~~~~~
 // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-21]]:1: note: calling function declared here
@@ -68,7 +68,7 @@ var bad_return_type: [i32; BadReturnType(1)];
 var bad_call: [i32; JustRight(1, 2)];
 
 fn RuntimeCallTooFew(a: i32) -> i32 {
-  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 1 argument(s) passed to function expecting 0 argument(s).
+  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 1 argument passed to function expecting 0 arguments
   // CHECK:STDERR:   return TooFew(a);
   // CHECK:STDERR:          ^~~~~~~
   // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-42]]:1: note: calling function declared here
@@ -79,7 +79,7 @@ fn RuntimeCallTooFew(a: i32) -> i32 {
 }
 
 fn RuntimeCallTooMany(a: i32, b: i32, c: i32) -> i32 {
-  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 3 argument(s) passed to function expecting 2 argument(s).
+  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 3 arguments passed to function expecting 2 arguments
   // CHECK:STDERR:   return TooMany(a, b, c);
   // CHECK:STDERR:          ^~~~~~~~
   // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-48]]:1: note: calling function declared here
@@ -90,7 +90,7 @@ fn RuntimeCallTooMany(a: i32, b: i32, c: i32) -> i32 {
 }
 
 fn RuntimeCallBadReturnType(a: i32, b: i32) -> bool {
-  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 2 argument(s) passed to function expecting 1 argument(s).
+  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 2 arguments passed to function expecting 1 argument
   // CHECK:STDERR:   return BadReturnType(a, b);
   // CHECK:STDERR:          ^~~~~~~~~~~~~~
   // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-54]]:1: note: calling function declared here

+ 1 - 1
toolchain/check/testdata/builtins/int/uadd.carbon

@@ -56,7 +56,7 @@ var too_many: [i32; TooMany(1, 2, 3)];
 // CHECK:STDERR:
 var bad_return_type: [i32; BadReturnType(1, 2)];
 
-// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+6]]:21: error: 3 argument(s) passed to function expecting 2 argument(s).
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+6]]:21: error: 3 arguments passed to function expecting 2 arguments
 // CHECK:STDERR: var bad_call: [i32; JustRight(1, 2, 3)];
 // CHECK:STDERR:                     ^~~~~~~~~~
 // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-21]]:1: note: calling function declared here

+ 4 - 4
toolchain/check/testdata/builtins/int/unegate.carbon

@@ -58,7 +58,7 @@ var too_many: [i32; TooMany(1, 2)];
 // CHECK:STDERR:
 var bad_return_type: [i32; BadReturnType(1)];
 
-// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:21: error: 2 argument(s) passed to function expecting 1 argument(s).
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:21: error: 2 arguments passed to function expecting 1 argument
 // CHECK:STDERR: var bad_call: [i32; JustRight(1, 2)];
 // CHECK:STDERR:                     ^~~~~~~~~~
 // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-21]]:1: note: calling function declared here
@@ -68,7 +68,7 @@ var bad_return_type: [i32; BadReturnType(1)];
 var bad_call: [i32; JustRight(1, 2)];
 
 fn RuntimeCallTooFew(a: i32) -> i32 {
-  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 1 argument(s) passed to function expecting 0 argument(s).
+  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 1 argument passed to function expecting 0 arguments
   // CHECK:STDERR:   return TooFew(a);
   // CHECK:STDERR:          ^~~~~~~
   // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-42]]:1: note: calling function declared here
@@ -79,7 +79,7 @@ fn RuntimeCallTooFew(a: i32) -> i32 {
 }
 
 fn RuntimeCallTooMany(a: i32, b: i32, c: i32) -> i32 {
-  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 3 argument(s) passed to function expecting 2 argument(s).
+  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+7]]:10: error: 3 arguments passed to function expecting 2 arguments
   // CHECK:STDERR:   return TooMany(a, b, c);
   // CHECK:STDERR:          ^~~~~~~~
   // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-48]]:1: note: calling function declared here
@@ -90,7 +90,7 @@ fn RuntimeCallTooMany(a: i32, b: i32, c: i32) -> i32 {
 }
 
 fn RuntimeCallBadReturnType(a: i32, b: i32) -> bool {
-  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+6]]:10: error: 2 argument(s) passed to function expecting 1 argument(s).
+  // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+6]]:10: error: 2 arguments passed to function expecting 1 argument
   // CHECK:STDERR:   return BadReturnType(a, b);
   // CHECK:STDERR:          ^~~~~~~~~~~~~~
   // CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-54]]:1: note: calling function declared here

+ 2 - 2
toolchain/check/testdata/class/fail_init.carbon

@@ -14,7 +14,7 @@ class Class {
 }
 
 fn F() {
-  // CHECK:STDERR: fail_init.carbon:[[@LINE+4]]:3: error: cannot initialize class with 2 field(s) from struct with 1 field(s).
+  // CHECK:STDERR: fail_init.carbon:[[@LINE+4]]:3: error: cannot initialize class with 2 fields from struct with 1 field
   // CHECK:STDERR:   {.a = 1} as Class;
   // CHECK:STDERR:   ^~~~~~~~
   // CHECK:STDERR:
@@ -24,7 +24,7 @@ fn F() {
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   {.a = 1, .c = 2} as Class;
-  // CHECK:STDERR: fail_init.carbon:[[@LINE+3]]:3: error: cannot initialize class with 2 field(s) from struct with 3 field(s).
+  // CHECK:STDERR: fail_init.carbon:[[@LINE+3]]:3: error: cannot initialize class with 2 fields from struct with 3 fields
   // CHECK:STDERR:   {.a = 1, .b = 2, .c = 3} as Class;
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~
   {.a = 1, .b = 2, .c = 3} as Class;

+ 1 - 1
toolchain/check/testdata/class/fail_method.carbon

@@ -28,7 +28,7 @@ fn F(c: Class) {
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   Class.WithSelf();
-  // CHECK:STDERR: fail_method.carbon:[[@LINE+7]]:3: error: 1 argument(s) passed to function expecting 0 argument(s).
+  // CHECK:STDERR: fail_method.carbon:[[@LINE+7]]:3: error: 1 argument passed to function expecting 0 arguments
   // CHECK:STDERR:   Class.WithSelf(c);
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~
   // CHECK:STDERR: fail_method.carbon:[[@LINE-21]]:3: note: calling function declared here

+ 2 - 2
toolchain/check/testdata/class/generic/call.carbon

@@ -25,7 +25,7 @@ library "[[@TEST_NAME]]";
 
 class Class(T:! type, N:! i32) {}
 
-// CHECK:STDERR: fail_too_few.carbon:[[@LINE+7]]:8: error: 1 argument(s) passed to generic class expecting 2 argument(s).
+// CHECK:STDERR: fail_too_few.carbon:[[@LINE+7]]:8: error: 1 argument passed to generic class expecting 2 arguments
 // CHECK:STDERR: var a: Class(i32*);
 // CHECK:STDERR:        ^~~~~~
 // CHECK:STDERR: fail_too_few.carbon:[[@LINE-5]]:1: note: calling generic class declared here
@@ -40,7 +40,7 @@ library "[[@TEST_NAME]]";
 
 class Class(T:! type, N:! i32) {}
 
-// CHECK:STDERR: fail_too_many.carbon:[[@LINE+7]]:8: error: 3 argument(s) passed to generic class expecting 2 argument(s).
+// CHECK:STDERR: fail_too_many.carbon:[[@LINE+7]]:8: error: 3 arguments passed to generic class expecting 2 arguments
 // CHECK:STDERR: var a: Class(i32*, 1, 2);
 // CHECK:STDERR:        ^~~~~~
 // CHECK:STDERR: fail_too_many.carbon:[[@LINE-5]]:1: note: calling generic class declared here

+ 1 - 1
toolchain/check/testdata/class/virtual_modifiers.carbon

@@ -45,7 +45,7 @@ import Modifiers;
 
 fn F() {
   // TODO: The vptr shouldn't be counted for programmer-facing behavior.
-  // CHECK:STDERR: fail_todo_init.carbon:[[@LINE+3]]:27: error: cannot initialize class with 1 field(s) from struct with 0 field(s).
+  // CHECK:STDERR: fail_todo_init.carbon:[[@LINE+3]]:27: error: cannot initialize class with 1 field from struct with 0 fields
   // CHECK:STDERR:   var v: Modifiers.Base = {};
   // CHECK:STDERR:                           ^~
   var v: Modifiers.Base = {};

+ 6 - 6
toolchain/check/testdata/function/call/fail_param_count.carbon

@@ -13,7 +13,7 @@ fn Run1(a: i32) {}
 fn Run2(a: i32, b: i32) {}
 
 fn Main() {
-  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 1 argument(s) passed to function expecting 0 argument(s).
+  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 1 argument passed to function expecting 0 arguments
   // CHECK:STDERR:   Run0(1);
   // CHECK:STDERR:   ^~~~~
   // CHECK:STDERR: fail_param_count.carbon:[[@LINE-8]]:1: note: calling function declared here
@@ -21,7 +21,7 @@ fn Main() {
   // CHECK:STDERR: ^~~~~~~~~~~
   // CHECK:STDERR:
   Run0(1);
-  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 2 argument(s) passed to function expecting 0 argument(s).
+  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 2 arguments passed to function expecting 0 arguments
   // CHECK:STDERR:   Run0(0, 1);
   // CHECK:STDERR:   ^~~~~
   // CHECK:STDERR: fail_param_count.carbon:[[@LINE-16]]:1: note: calling function declared here
@@ -30,7 +30,7 @@ fn Main() {
   // CHECK:STDERR:
   Run0(0, 1);
 
-  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 0 argument(s) passed to function expecting 1 argument(s).
+  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 0 arguments passed to function expecting 1 argument
   // CHECK:STDERR:   Run1();
   // CHECK:STDERR:   ^~~~~
   // CHECK:STDERR: fail_param_count.carbon:[[@LINE-24]]:1: note: calling function declared here
@@ -38,7 +38,7 @@ fn Main() {
   // CHECK:STDERR: ^~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   Run1();
-  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 2 argument(s) passed to function expecting 1 argument(s).
+  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 2 arguments passed to function expecting 1 argument
   // CHECK:STDERR:   Run1(0, 1);
   // CHECK:STDERR:   ^~~~~
   // CHECK:STDERR: fail_param_count.carbon:[[@LINE-32]]:1: note: calling function declared here
@@ -47,7 +47,7 @@ fn Main() {
   // CHECK:STDERR:
   Run1(0, 1);
 
-  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 0 argument(s) passed to function expecting 2 argument(s).
+  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+7]]:3: error: 0 arguments passed to function expecting 2 arguments
   // CHECK:STDERR:   Run2();
   // CHECK:STDERR:   ^~~~~
   // CHECK:STDERR: fail_param_count.carbon:[[@LINE-40]]:1: note: calling function declared here
@@ -55,7 +55,7 @@ fn Main() {
   // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   Run2();
-  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+6]]:3: error: 1 argument(s) passed to function expecting 2 argument(s).
+  // CHECK:STDERR: fail_param_count.carbon:[[@LINE+6]]:3: error: 1 argument passed to function expecting 2 arguments
   // CHECK:STDERR:   Run2(0);
   // CHECK:STDERR:   ^~~~~
   // CHECK:STDERR: fail_param_count.carbon:[[@LINE-48]]:1: note: calling function declared here

+ 3 - 3
toolchain/check/testdata/function/generic/redeclare.carbon

@@ -32,7 +32,7 @@ fn F(T:! type, U:! type) -> T*;
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
 fn F(T:! type, U:! type) -> U* {
-  // CHECK:STDERR: fail_different_return_type.carbon:[[@LINE+7]]:10: error: 1 argument(s) passed to function expecting 2 argument(s).
+  // CHECK:STDERR: fail_different_return_type.carbon:[[@LINE+7]]:10: error: 1 argument passed to function expecting 2 arguments
   // CHECK:STDERR:   return F(T);
   // CHECK:STDERR:          ^~
   // CHECK:STDERR: fail_different_return_type.carbon:[[@LINE-13]]:1: note: calling function declared here
@@ -56,7 +56,7 @@ fn F(T:! type, U:! type) -> T*;
 // CHECK:STDERR:      ^~~~~~~~
 // CHECK:STDERR:
 fn F(U:! type, T:! type) -> T* {
-  // CHECK:STDERR: fail_reorder.carbon:[[@LINE+7]]:10: error: 1 argument(s) passed to function expecting 2 argument(s).
+  // CHECK:STDERR: fail_reorder.carbon:[[@LINE+7]]:10: error: 1 argument passed to function expecting 2 arguments
   // CHECK:STDERR:   return F(T);
   // CHECK:STDERR:          ^~
   // CHECK:STDERR: fail_reorder.carbon:[[@LINE-13]]:1: note: calling function declared here
@@ -80,7 +80,7 @@ fn F(T:! type, U:! type) -> T*;
 // CHECK:STDERR:      ^~~~~~~~
 // CHECK:STDERR:
 fn F(U:! type, T:! type) -> U* {
-  // CHECK:STDERR: fail_rename.carbon:[[@LINE+6]]:10: error: 1 argument(s) passed to function expecting 2 argument(s).
+  // CHECK:STDERR: fail_rename.carbon:[[@LINE+6]]:10: error: 1 argument passed to function expecting 2 arguments
   // CHECK:STDERR:   return F(T);
   // CHECK:STDERR:          ^~
   // CHECK:STDERR: fail_rename.carbon:[[@LINE-13]]:1: note: calling function declared here

+ 1 - 1
toolchain/check/testdata/struct/fail_assign_empty.carbon

@@ -8,7 +8,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/struct/fail_assign_empty.carbon
 
-// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:20: error: cannot initialize struct with 1 field(s) from struct with 0 field(s).
+// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:20: error: cannot initialize struct with 1 field from struct with 0 fields
 // CHECK:STDERR: var x: {.a: i32} = {};
 // CHECK:STDERR:                    ^~
 var x: {.a: i32} = {};

+ 1 - 1
toolchain/check/testdata/struct/fail_assign_to_empty.carbon

@@ -8,7 +8,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/struct/fail_assign_to_empty.carbon
 
-// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:13: error: cannot initialize struct with 0 field(s) from struct with 1 field(s).
+// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:13: error: cannot initialize struct with 0 fields from struct with 1 field
 // CHECK:STDERR: var x: {} = {.a = 1};
 // CHECK:STDERR:             ^~~~~~~~
 var x: {} = {.a = 1};

+ 1 - 1
toolchain/check/testdata/struct/fail_too_few_values.carbon

@@ -8,7 +8,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/struct/fail_too_few_values.carbon
 
-// CHECK:STDERR: fail_too_few_values.carbon:[[@LINE+3]]:29: error: cannot initialize struct with 2 field(s) from struct with 1 field(s).
+// CHECK:STDERR: fail_too_few_values.carbon:[[@LINE+3]]:29: error: cannot initialize struct with 2 fields from struct with 1 field
 // CHECK:STDERR: var x: {.a: i32, .b: i32} = {.a = 1};
 // CHECK:STDERR:                             ^~~~~~~~
 var x: {.a: i32, .b: i32} = {.a = 1};

+ 1 - 1
toolchain/check/testdata/tuple/fail_assign_nested.carbon

@@ -8,7 +8,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/tuple/fail_assign_nested.carbon
 
-// CHECK:STDERR: fail_assign_nested.carbon:[[@LINE+3]]:36: error: cannot initialize tuple of 2 element(s) from tuple with 3 element(s).
+// CHECK:STDERR: fail_assign_nested.carbon:[[@LINE+3]]:36: error: cannot initialize tuple of 2 elements from tuple with 3 elements
 // CHECK:STDERR: var x: ((i32, i32), (i32, i32)) = ((1, 2, 3), (4, 5, 6));
 // CHECK:STDERR:                                    ^~~~~~~~~
 var x: ((i32, i32), (i32, i32)) = ((1, 2, 3), (4, 5, 6));

+ 1 - 1
toolchain/check/testdata/tuple/fail_too_few_element.carbon

@@ -8,7 +8,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/tuple/fail_too_few_element.carbon
 
-// CHECK:STDERR: fail_too_few_element.carbon:[[@LINE+3]]:21: error: cannot initialize tuple of 2 element(s) from tuple with 1 element(s).
+// CHECK:STDERR: fail_too_few_element.carbon:[[@LINE+3]]:21: error: cannot initialize tuple of 2 elements from tuple with 1 element
 // CHECK:STDERR: var x: (i32, i32) = (2, );
 // CHECK:STDERR:                     ^~~~~
 var x: (i32, i32) = (2, );

+ 1 - 1
toolchain/check/testdata/tuple/import.carbon

@@ -31,7 +31,7 @@ var c: C((1, 2)) = F();
 
 impl package Implicit;
 
-// CHECK:STDERR: fail_bad_type.impl.carbon:[[@LINE+4]]:14: error: cannot initialize tuple of 2 element(s) from tuple with 3 element(s).
+// CHECK:STDERR: fail_bad_type.impl.carbon:[[@LINE+4]]:14: error: cannot initialize tuple of 2 elements from tuple with 3 elements
 // CHECK:STDERR: var c_bad: C((1, 2, 3)) = F();
 // CHECK:STDERR:              ^~~~~~~~~
 // CHECK:STDERR:

+ 1 - 1
toolchain/check/testdata/tuple/no_prelude/fail_assign_empty.carbon

@@ -8,7 +8,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/tuple/no_prelude/fail_assign_empty.carbon
 
-// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:16: error: cannot initialize tuple of 1 element(s) from tuple with 0 element(s).
+// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:16: error: cannot initialize tuple of 1 element from tuple with 0 elements
 // CHECK:STDERR: var x: ((),) = ();
 // CHECK:STDERR:                ^~
 var x: ((),) = ();

+ 1 - 1
toolchain/check/testdata/tuple/no_prelude/fail_assign_to_empty.carbon

@@ -8,7 +8,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/tuple/no_prelude/fail_assign_to_empty.carbon
 
-// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:13: error: cannot initialize tuple of 0 element(s) from tuple with 1 element(s).
+// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:13: error: cannot initialize tuple of 0 elements from tuple with 1 element
 // CHECK:STDERR: var x: () = ((),);
 // CHECK:STDERR:             ^~~~~
 var x: () = ((),);

+ 6 - 1
toolchain/diagnostics/format_providers.cpp

@@ -38,7 +38,12 @@ auto llvm::format_provider<Carbon::BoolAsSelect>::format(
 auto llvm::format_provider<Carbon::IntAsSelect>::format(
     const Carbon::IntAsSelect& wrapper, raw_ostream& out, StringRef style)
     -> void {
-  if (style.empty()) {
+  if (style == "s") {
+    if (wrapper.value != 1) {
+      out << "s";
+    }
+    return;
+  } else if (style.empty()) {
     llvm::format_provider<int>::format(wrapper.value, out, style);
     return;
   }

+ 13 - 7
toolchain/diagnostics/format_providers.h

@@ -10,12 +10,13 @@
 
 namespace Carbon {
 
-// Selects a formatv string based on the value. If the format style is not
-// provided, as in `{0}`, the value uses standard formatting.
+// Selects a formatv string based on the value.
 //
-// When used, the true and false outputs are separated by a `|`.
-//
-// For example, `{0:true|false}` would yield standard bool formatting.
+// Supported format styles are:
+// - None, as in `{0}`. This uses standard integer formatting.
+// - Selector, as in `{0:true|false}`. The output string used is separated by a
+//   `|`, with the true case first. the example would yield standard bool
+//   formatting.
 //
 // If needed, the _full_ style string can be wrapped with `'` in order to
 // preserve prefix or suffix whitespace (which is stripped by formatv). For
@@ -28,8 +29,13 @@ struct BoolAsSelect {
   bool value;
 };
 
-// Selects a formatv string based on the value. If the format style is not
-// provided, as in `{0}`, the value uses standard formatting.
+// Selects a formatv string based on the value.
+//
+// Supported format styles are:
+// - None, as in `{0}`. This uses standard integer formatting.
+// - Selector, as in `{0:=0:zero|:default}`. This is detailed below.
+// - Plural `s`, as in `{0:s}`. This outputs an `s` when the value is not 1,
+//   equivalent to `{0:=1:|:s}`.
 //
 // The style is a series of match cases, separated by `|`. Each case is a pair
 // formatted as `<selector>:<output string>`.

+ 8 - 1
toolchain/diagnostics/format_providers_test.cpp

@@ -75,12 +75,19 @@ TEST(IntAsSelect, QuotedSpaces) {
   EXPECT_THAT(llvm::formatv(Format, IntAsSelect(2)).str(), Eq(" default "));
 }
 
-TEST(IntAsSelect, PluralExample) {
+TEST(IntAsSelect, CasesWithNormalFormat) {
   constexpr char Format[] = "{0} argument{0:=1:|:s}";
   EXPECT_THAT(llvm::formatv(Format, IntAsSelect(0)).str(), Eq("0 arguments"));
   EXPECT_THAT(llvm::formatv(Format, IntAsSelect(1)).str(), Eq("1 argument"));
   EXPECT_THAT(llvm::formatv(Format, IntAsSelect(2)).str(), Eq("2 arguments"));
 }
 
+TEST(IntAsSelect, PluralS) {
+  constexpr char Format[] = "{0} argument{0:s}";
+  EXPECT_THAT(llvm::formatv(Format, IntAsSelect(0)).str(), Eq("0 arguments"));
+  EXPECT_THAT(llvm::formatv(Format, IntAsSelect(1)).str(), Eq("1 argument"));
+  EXPECT_THAT(llvm::formatv(Format, IntAsSelect(2)).str(), Eq("2 arguments"));
+}
+
 }  // namespace
 }  // namespace Carbon