1use crate::Report;
2
3pub trait TryReportTupleExt<C> {
15 type Output;
17
18 fn try_collect(self) -> Result<Self::Output, Report<[C]>>;
55}
56
57impl<T, R, C> TryReportTupleExt<C> for (core::result::Result<T, R>,)
58where
59 R: Into<Report<[C]>>,
60{
61 type Output = (T,);
62
63 fn try_collect(self) -> Result<Self::Output, Report<[C]>> {
64 let (result,) = self;
65
66 match result {
67 Ok(value) => Ok((value,)),
68 Err(report) => Err(report.into()),
69 }
70 }
71}
72
73#[rustfmt::skip]
74macro_rules! all_the_tuples {
75 ($macro:ident) => {
76 $macro!([A, AO]);
77 $macro!([A, AO], [B, BO]);
78 $macro!([A, AO], [B, BO], [C, CO]);
79 $macro!([A, AO], [B, BO], [C, CO], [D, DO]);
80 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO]);
81 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO]);
82 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO]);
83 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO], [H, HO]);
84 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO], [H, HO], [I, IO]);
85 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO], [H, HO], [I, IO], [J, JO]);
86 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO], [H, HO], [I, IO], [J, JO], [K, KO]);
87 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO], [H, HO], [I, IO], [J, JO], [K, KO], [L, LO]);
88 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO], [H, HO], [I, IO], [J, JO], [K, KO], [L, LO], [M, MO]);
89 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO], [H, HO], [I, IO], [J, JO], [K, KO], [L, LO], [M, MO], [N, NO]);
90 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO], [H, HO], [I, IO], [J, JO], [K, KO], [L, LO], [M, MO], [N, NO], [O, OO]);
91 $macro!([A, AO], [B, BO], [C, CO], [D, DO], [E, EO], [F, FO], [G, GO], [H, HO], [I, IO], [J, JO], [K, KO], [L, LO], [M, MO], [N, NO], [O, OO], [P, PO]);
92 };
93}
94
95macro_rules! impl_ext {
96 ($([$type:ident, $output:ident]),+) => {
97 impl<$($type, $output),*, T, R, Context> TryReportTupleExt<Context> for ($($type),*, core::result::Result<T, R>)
98 where
99 R: Into<Report<[Context]>>,
100 ($($type,)*): TryReportTupleExt<Context, Output = ($($output,)*)>,
101 {
102 type Output = ($($output),*, T);
103
104 #[expect(non_snake_case, clippy::min_ident_chars)]
105 fn try_collect(self) -> Result<Self::Output, Report<[Context]>> {
106 let ($($type),*, result) = self;
107 let prefix = ($($type,)*).try_collect();
108
109 match (prefix, result) {
110 (Ok(($($type,)*)), Ok(value)) => Ok(($($type),*, value)),
111 (Err(report), Ok(_)) => Err(report),
112 (Ok(_), Err(report)) => Err(report.into()),
113 (Err(mut report), Err(error)) => {
114 report.append(error.into());
115 Err(report)
116 }
117 }
118 }
119 }
120 };
121}
122
123all_the_tuples!(impl_ext);
124
125#[cfg(test)]
126mod test {
127 use alloc::{borrow::ToOwned as _, collections::BTreeSet, string::String};
128 use core::{error::Error, fmt::Display};
129
130 use super::TryReportTupleExt as _;
131 use crate::Report;
132
133 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
134 struct TestError(usize);
135
136 impl Display for TestError {
137 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
138 fmt.write_str("TestError")
139 }
140 }
141
142 impl Error for TestError {}
143
144 #[test]
145 fn single_error() {
146 let result1: Result<i32, Report<TestError>> = Ok(1);
147 let result2: Result<String, Report<TestError>> = Ok("test".to_owned());
148 let result3: Result<bool, Report<TestError>> = Err(Report::new(TestError(0)));
149
150 let combined = (result1, result2, result3).try_collect();
151 let report = combined.expect_err("should have error");
152
153 let contexts: BTreeSet<_> = report.current_contexts().collect();
154 assert_eq!(contexts.len(), 1);
155 assert!(contexts.contains(&TestError(0)));
156 }
157
158 #[test]
159 fn no_error() {
160 let result1: Result<i32, Report<TestError>> = Ok(1);
161 let result2: Result<String, Report<TestError>> = Ok("test".to_owned());
162 let result3: Result<bool, Report<TestError>> = Ok(true);
163
164 let combined = (result1, result2, result3).try_collect();
165 let (ok1, ok2, ok3) = combined.expect("should have no error");
166
167 assert_eq!(ok1, 1);
168 assert_eq!(ok2, "test");
169 assert!(ok3);
170 }
171
172 #[test]
173 fn expanded_error() {
174 let result1: Result<i32, Report<[TestError]>> = Ok(1);
175 let result2: Result<String, Report<[TestError]>> = Ok("test".to_owned());
176 let result3: Result<bool, Report<[TestError]>> = Err(Report::new(TestError(0)).expand());
177
178 let combined = (result1, result2, result3).try_collect();
179 let report = combined.expect_err("should have error");
180
181 let contexts: BTreeSet<_> = report.current_contexts().collect();
183 assert_eq!(contexts.len(), 1);
184 assert!(contexts.contains(&TestError(0)));
185 }
186
187 #[test]
188 fn single_and_expanded_mixed() {
189 let result1: Result<i32, Report<[TestError]>> = Ok(1);
190 let result2: Result<String, Report<TestError>> = Err(Report::new(TestError(0)));
191 let result3: Result<bool, Report<[TestError]>> = Err(Report::new(TestError(1)).expand());
192
193 let combined = (result1, result2, result3).try_collect();
194 let report = combined.expect_err("should have error");
195
196 let contexts: BTreeSet<_> = report.current_contexts().collect();
198 assert_eq!(contexts.len(), 2);
199 assert!(contexts.contains(&TestError(0)));
200 assert!(contexts.contains(&TestError(1)));
201 }
202
203 #[test]
204 fn multiple_errors() {
205 let result1: Result<i32, Report<TestError>> = Err(Report::new(TestError(0)));
206 let result2: Result<String, Report<TestError>> = Ok("test".to_owned());
207 let result3: Result<bool, Report<TestError>> = Err(Report::new(TestError(1)));
208
209 let combined = (result1, result2, result3).try_collect();
210 let report = combined.expect_err("should have error");
211
212 let contexts: BTreeSet<_> = report.current_contexts().collect();
214 assert_eq!(contexts.len(), 2);
215 assert!(contexts.contains(&TestError(0)));
216 assert!(contexts.contains(&TestError(1)));
217 }
218}