Skip to content

Commit 3d2f265

Browse files
authored
Rollup merge of rust-lang#136368 - estebank:listify, r=fee1-dead
Make comma separated lists of anything easier to make for errors Provide a new function `listify`, meant to be used in cases similar to `pluralize!`. When you have a slice of arbitrary elements that need to be presented to the user, `listify` allows you to turn that into a list of comma separated strings. This reduces a lot of redundant logic that happens often in diagnostics.
2 parents 87dad0f + 8e9422f commit 3d2f265

File tree

11 files changed

+88
-148
lines changed

11 files changed

+88
-148
lines changed

compiler/rustc_borrowck/src/diagnostics/mod.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::collections::BTreeMap;
44

55
use rustc_abi::{FieldIdx, VariantIdx};
66
use rustc_data_structures::fx::FxIndexMap;
7-
use rustc_errors::{Applicability, Diag, EmissionGuarantee, MultiSpan};
7+
use rustc_errors::{Applicability, Diag, EmissionGuarantee, MultiSpan, listify};
88
use rustc_hir::def::{CtorKind, Namespace};
99
use rustc_hir::{self as hir, CoroutineKind, LangItem};
1010
use rustc_index::IndexSlice;
@@ -29,7 +29,7 @@ use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
2929
use rustc_trait_selection::error_reporting::traits::call_kind::{CallDesugaringKind, call_kind};
3030
use rustc_trait_selection::infer::InferCtxtExt;
3131
use rustc_trait_selection::traits::{
32-
FulfillmentErrorCode, type_known_to_meet_bound_modulo_regions,
32+
FulfillmentError, FulfillmentErrorCode, type_known_to_meet_bound_modulo_regions,
3333
};
3434
use tracing::debug;
3535

@@ -1433,17 +1433,15 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
14331433
error.obligation.predicate,
14341434
)
14351435
}
1436-
[errors @ .., last] => {
1436+
_ => {
14371437
format!(
14381438
"you could `clone` the value and consume it, if the \
1439-
following trait bounds could be satisfied: \
1440-
{} and `{}`",
1441-
errors
1442-
.iter()
1443-
.map(|e| format!("`{}`", e.obligation.predicate))
1444-
.collect::<Vec<_>>()
1445-
.join(", "),
1446-
last.obligation.predicate,
1439+
following trait bounds could be satisfied: {}",
1440+
listify(&errors, |e: &FulfillmentError<'tcx>| format!(
1441+
"`{}`",
1442+
e.obligation.predicate
1443+
))
1444+
.unwrap(),
14471445
)
14481446
}
14491447
};

compiler/rustc_builtin_macros/src/format.rs

+8-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use rustc_ast::{
88
token,
99
};
1010
use rustc_data_structures::fx::FxHashSet;
11-
use rustc_errors::{Applicability, Diag, MultiSpan, PResult, SingleLabelManySpans};
11+
use rustc_errors::{
12+
Applicability, Diag, MultiSpan, PResult, SingleLabelManySpans, listify, pluralize,
13+
};
1214
use rustc_expand::base::*;
1315
use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
1416
use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, LintId};
@@ -975,15 +977,11 @@ fn report_invalid_references(
975977
} else {
976978
MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect())
977979
};
978-
let arg_list = if let &[index] = &indexes[..] {
979-
format!("argument {index}")
980-
} else {
981-
let tail = indexes.pop().unwrap();
982-
format!(
983-
"arguments {head} and {tail}",
984-
head = indexes.into_iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ")
985-
)
986-
};
980+
let arg_list = format!(
981+
"argument{} {}",
982+
pluralize!(indexes.len()),
983+
listify(&indexes, |i: &usize| i.to_string()).unwrap_or_default()
984+
);
987985
e = ecx.dcx().struct_span_err(
988986
span,
989987
format!("invalid reference to positional {arg_list} ({num_args_desc})"),

compiler/rustc_errors/src/lib.rs

+1-13
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ pub use rustc_error_messages::{
6565
SubdiagMessage, fallback_fluent_bundle, fluent_bundle,
6666
};
6767
use rustc_lint_defs::LintExpectationId;
68-
pub use rustc_lint_defs::{Applicability, pluralize};
68+
pub use rustc_lint_defs::{Applicability, listify, pluralize};
6969
use rustc_macros::{Decodable, Encodable};
7070
pub use rustc_span::ErrorGuaranteed;
7171
pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker};
@@ -1999,18 +1999,6 @@ pub fn a_or_an(s: &str) -> &'static str {
19991999
}
20002000
}
20012001

2002-
/// Grammatical tool for displaying messages to end users in a nice form.
2003-
///
2004-
/// Take a list ["a", "b", "c"] and output a display friendly version "a, b and c"
2005-
pub fn display_list_with_comma_and<T: std::fmt::Display>(v: &[T]) -> String {
2006-
match v {
2007-
[] => "".to_string(),
2008-
[a] => a.to_string(),
2009-
[a, b] => format!("{a} and {b}"),
2010-
[a, v @ ..] => format!("{a}, {}", display_list_with_comma_and(v)),
2011-
}
2012-
}
2013-
20142002
#[derive(Clone, Copy, PartialEq, Hash, Debug)]
20152003
pub enum TerminalUrl {
20162004
No,

compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs

+10-36
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use rustc_data_structures::sorted_map::SortedMap;
33
use rustc_data_structures::unord::UnordMap;
44
use rustc_errors::codes::*;
55
use rustc_errors::{
6-
Applicability, Diag, ErrorGuaranteed, MultiSpan, pluralize, struct_span_code_err,
6+
Applicability, Diag, ErrorGuaranteed, MultiSpan, listify, pluralize, struct_span_code_err,
77
};
88
use rustc_hir as hir;
99
use rustc_hir::def::{DefKind, Res};
@@ -808,14 +808,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
808808
.map(|(trait_, mut assocs)| {
809809
assocs.sort();
810810
let trait_ = trait_.print_trait_sugared();
811-
format!("{} in `{trait_}`", match &assocs[..] {
812-
[] => String::new(),
813-
[only] => format!("`{only}`"),
814-
[assocs @ .., last] => format!(
815-
"{} and `{last}`",
816-
assocs.iter().map(|a| format!("`{a}`")).collect::<Vec<_>>().join(", ")
817-
),
818-
})
811+
format!(
812+
"{} in `{trait_}`",
813+
listify(&assocs[..], |a| format!("`{a}`")).unwrap_or_default()
814+
)
819815
})
820816
.collect::<Vec<String>>();
821817
names.sort();
@@ -1075,18 +1071,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
10751071
}
10761072
})
10771073
.collect();
1078-
let this_type = match &types_and_spans[..] {
1079-
[.., _, (last, _)] => format!(
1080-
"{} and {last}",
1081-
types_and_spans[..types_and_spans.len() - 1]
1082-
.iter()
1083-
.map(|(x, _)| x.as_str())
1084-
.intersperse(", ")
1085-
.collect::<String>()
1086-
),
1087-
[(only, _)] => only.to_string(),
1088-
[] => bug!("expected one segment to deny"),
1089-
};
1074+
let this_type = listify(&types_and_spans, |(t, _)| t.to_string())
1075+
.expect("expected one segment to deny");
10901076

10911077
let arg_spans: Vec<Span> = segments
10921078
.clone()
@@ -1102,21 +1088,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
11021088
ProhibitGenericsArg::Infer => kinds.push("generic"),
11031089
});
11041090

1105-
let (kind, s) = match kinds[..] {
1106-
[.., _, last] => (
1107-
format!(
1108-
"{} and {last}",
1109-
kinds[..kinds.len() - 1]
1110-
.iter()
1111-
.map(|&x| x)
1112-
.intersperse(", ")
1113-
.collect::<String>()
1114-
),
1115-
"s",
1116-
),
1117-
[only] => (only.to_string(), ""),
1118-
[] => bug!("expected at least one generic to prohibit"),
1119-
};
1091+
let s = pluralize!(kinds.len());
1092+
let kind =
1093+
listify(&kinds, |k| k.to_string()).expect("expected at least one generic to prohibit");
11201094
let last_span = *arg_spans.last().unwrap();
11211095
let span: MultiSpan = arg_spans.into();
11221096
let mut err = struct_span_code_err!(

compiler/rustc_hir_typeck/src/demand.rs

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_errors::{Applicability, Diag, MultiSpan};
1+
use rustc_errors::{Applicability, Diag, MultiSpan, listify};
22
use rustc_hir as hir;
33
use rustc_hir::def::Res;
44
use rustc_hir::intravisit::Visitor;
@@ -1016,18 +1016,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10161016
},
10171017
self.tcx.def_path_str(candidate.item.container_id(self.tcx))
10181018
),
1019-
[.., last] if other_methods_in_scope.len() < 5 => {
1019+
_ if other_methods_in_scope.len() < 5 => {
10201020
format!(
1021-
"the methods of the same name on {} and `{}`",
1022-
other_methods_in_scope[..other_methods_in_scope.len() - 1]
1023-
.iter()
1024-
.map(|c| format!(
1025-
"`{}`",
1026-
self.tcx.def_path_str(c.item.container_id(self.tcx))
1027-
))
1028-
.collect::<Vec<String>>()
1029-
.join(", "),
1030-
self.tcx.def_path_str(last.item.container_id(self.tcx))
1021+
"the methods of the same name on {}",
1022+
listify(
1023+
&other_methods_in_scope[..other_methods_in_scope.len() - 1],
1024+
|c| format!("`{}`", self.tcx.def_path_str(c.item.container_id(self.tcx)))
1025+
)
1026+
.unwrap_or_default(),
10311027
)
10321028
}
10331029
_ => format!(

compiler/rustc_hir_typeck/src/expr.rs

+12-23
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
1111
use rustc_data_structures::unord::UnordMap;
1212
use rustc_errors::codes::*;
1313
use rustc_errors::{
14-
Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, pluralize,
14+
Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, listify, pluralize,
1515
struct_span_code_err,
1616
};
1717
use rustc_hir::def::{CtorKind, DefKind, Res};
@@ -2190,13 +2190,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
21902190
}
21912191
if !missing_mandatory_fields.is_empty() {
21922192
let s = pluralize!(missing_mandatory_fields.len());
2193-
let fields: Vec<_> =
2194-
missing_mandatory_fields.iter().map(|f| format!("`{f}`")).collect();
2195-
let fields = match &fields[..] {
2196-
[] => unreachable!(),
2197-
[only] => only.to_string(),
2198-
[start @ .., last] => format!("{} and {last}", start.join(", ")),
2199-
};
2193+
let fields = listify(&missing_mandatory_fields, |f| format!("`{f}`")).unwrap();
22002194
self.dcx()
22012195
.struct_span_err(
22022196
span.shrink_to_hi(),
@@ -2553,25 +2547,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
25532547
.partition(|field| field.2);
25542548
err.span_labels(used_private_fields.iter().map(|(_, span, _)| *span), "private field");
25552549
if !remaining_private_fields.is_empty() {
2556-
let remaining_private_fields_len = remaining_private_fields.len();
2557-
let names = match &remaining_private_fields
2558-
.iter()
2559-
.map(|(name, _, _)| name)
2560-
.collect::<Vec<_>>()[..]
2561-
{
2562-
_ if remaining_private_fields_len > 6 => String::new(),
2563-
[name] => format!("`{name}` "),
2564-
[names @ .., last] => {
2565-
let names = names.iter().map(|name| format!("`{name}`")).collect::<Vec<_>>();
2566-
format!("{} and `{last}` ", names.join(", "))
2567-
}
2568-
[] => bug!("expected at least one private field to report"),
2550+
let names = if remaining_private_fields.len() > 6 {
2551+
String::new()
2552+
} else {
2553+
format!(
2554+
"{} ",
2555+
listify(&remaining_private_fields, |(name, _, _)| format!("`{name}`"))
2556+
.expect("expected at least one private field to report")
2557+
)
25692558
};
25702559
err.note(format!(
25712560
"{}private field{s} {names}that {were} not provided",
25722561
if used_fields.is_empty() { "" } else { "...and other " },
2573-
s = pluralize!(remaining_private_fields_len),
2574-
were = pluralize!("was", remaining_private_fields_len),
2562+
s = pluralize!(remaining_private_fields.len()),
2563+
were = pluralize!("was", remaining_private_fields.len()),
25752564
));
25762565
}
25772566

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use itertools::Itertools;
44
use rustc_data_structures::fx::FxIndexSet;
55
use rustc_errors::codes::*;
66
use rustc_errors::{
7-
Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, a_or_an,
8-
display_list_with_comma_and, pluralize,
7+
Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, a_or_an, listify, pluralize,
98
};
109
use rustc_hir::def::{CtorOf, DefKind, Res};
1110
use rustc_hir::def_id::DefId;
@@ -2462,7 +2461,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
24622461
param.span,
24632462
format!(
24642463
"{} need{} to match the {} type of this parameter",
2465-
display_list_with_comma_and(&other_param_matched_names),
2464+
listify(&other_param_matched_names, |n| n.to_string())
2465+
.unwrap_or_default(),
24662466
pluralize!(if other_param_matched_names.len() == 1 {
24672467
0
24682468
} else {
@@ -2477,7 +2477,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
24772477
format!(
24782478
"this parameter needs to match the {} type of {}",
24792479
matched_ty,
2480-
display_list_with_comma_and(&other_param_matched_names),
2480+
listify(&other_param_matched_names, |n| n.to_string())
2481+
.unwrap_or_default(),
24812482
),
24822483
);
24832484
}
@@ -2523,7 +2524,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
25232524
generic_param.span,
25242525
format!(
25252526
"{} {} reference this parameter `{}`",
2526-
display_list_with_comma_and(&param_idents_matching),
2527+
listify(&param_idents_matching, |n| n.to_string())
2528+
.unwrap_or_default(),
25272529
if param_idents_matching.len() == 2 { "both" } else { "all" },
25282530
generic_param.name.ident().name,
25292531
),

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

+5-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use core::iter;
44
use hir::def_id::LocalDefId;
55
use rustc_ast::util::parser::ExprPrecedence;
66
use rustc_data_structures::packed::Pu128;
7-
use rustc_errors::{Applicability, Diag, MultiSpan};
7+
use rustc_errors::{Applicability, Diag, MultiSpan, listify};
88
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
99
use rustc_hir::lang_items::LangItem;
1010
use rustc_hir::{
@@ -1836,16 +1836,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18361836
error.obligation.predicate,
18371837
));
18381838
}
1839-
[errors @ .., last] => {
1839+
_ => {
18401840
diag.help(format!(
18411841
"`Clone` is not implemented because the following trait bounds \
1842-
could not be satisfied: {} and `{}`",
1843-
errors
1844-
.iter()
1845-
.map(|e| format!("`{}`", e.obligation.predicate))
1846-
.collect::<Vec<_>>()
1847-
.join(", "),
1848-
last.obligation.predicate,
1842+
could not be satisfied: {}",
1843+
listify(&errors, |e| format!("`{}`", e.obligation.predicate))
1844+
.unwrap(),
18491845
));
18501846
}
18511847
}

compiler/rustc_lint_defs/src/lib.rs

+17
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ macro_rules! pluralize {
4242
};
4343
}
4444

45+
/// Grammatical tool for displaying messages to end users in a nice form.
46+
///
47+
/// Take a list of items and a function to turn those items into a `String`, and output a display
48+
/// friendly comma separated list of those items.
49+
// FIXME(estebank): this needs to be changed to go through the translation machinery.
50+
pub fn listify<T>(list: &[T], fmt: impl Fn(&T) -> String) -> Option<String> {
51+
Some(match list {
52+
[only] => fmt(&only),
53+
[others @ .., last] => format!(
54+
"{} and {}",
55+
others.iter().map(|i| fmt(i)).collect::<Vec<_>>().join(", "),
56+
fmt(&last),
57+
),
58+
[] => return None,
59+
})
60+
}
61+
4562
/// Indicates the confidence in the correctness of a suggestion.
4663
///
4764
/// All suggestions are marked with an `Applicability`. Tools use the applicability of a suggestion

compiler/rustc_middle/src/ty/diagnostics.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::ops::ControlFlow;
55

66
use rustc_data_structures::fx::FxHashMap;
77
use rustc_errors::{
8-
Applicability, Diag, DiagArgValue, IntoDiagArg, into_diag_arg_using_display, pluralize,
8+
Applicability, Diag, DiagArgValue, IntoDiagArg, into_diag_arg_using_display, listify, pluralize,
99
};
1010
use rustc_hir::def::DefKind;
1111
use rustc_hir::def_id::DefId;
@@ -362,11 +362,8 @@ pub fn suggest_constraining_type_params<'a>(
362362
let n = trait_names.len();
363363
let stable = if all_stable { "" } else { "unstable " };
364364
let trait_ = if all_known { format!("trait{}", pluralize!(n)) } else { String::new() };
365-
format!("{stable}{trait_}{}", match &trait_names[..] {
366-
[t] => format!(" {t}"),
367-
[ts @ .., last] => format!(" {} and {last}", ts.join(", ")),
368-
[] => return false,
369-
},)
365+
let Some(trait_names) = listify(&trait_names, |n| n.to_string()) else { return false };
366+
format!("{stable}{trait_} {trait_names}")
370367
} else {
371368
// We're more explicit when there's a mix of stable and unstable traits.
372369
let mut trait_names = constraints
@@ -378,10 +375,9 @@ pub fn suggest_constraining_type_params<'a>(
378375
.collect::<Vec<_>>();
379376
trait_names.sort();
380377
trait_names.dedup();
381-
match &trait_names[..] {
382-
[t] => t.to_string(),
383-
[ts @ .., last] => format!("{} and {last}", ts.join(", ")),
384-
[] => return false,
378+
match listify(&trait_names, |t| t.to_string()) {
379+
Some(names) => names,
380+
None => return false,
385381
}
386382
};
387383
let constraint = constraint.join(" + ");

0 commit comments

Comments
 (0)