rustc_hir_typeck/fn_ctxt/
arg_matrix.rs

1use core::cmp::Ordering;
2use std::cmp;
3
4use rustc_index::IndexVec;
5use rustc_middle::ty::error::TypeError;
6
7rustc_index::newtype_index! {
8    #[orderable]
9    #[debug_format = "ExpectedIdx({})"]
10    pub(crate) struct ExpectedIdx {}
11}
12
13rustc_index::newtype_index! {
14    #[orderable]
15    #[debug_format = "ProvidedIdx({})"]
16    pub(crate) struct ProvidedIdx {}
17}
18
19impl ExpectedIdx {
20    pub(crate) fn to_provided_idx(self) -> ProvidedIdx {
21        ProvidedIdx::from_usize(self.as_usize())
22    }
23}
24
25impl ProvidedIdx {
26    pub(crate) fn to_expected_idx(self) -> ExpectedIdx {
27        ExpectedIdx::from_u32(self.as_u32())
28    }
29}
30
31// An issue that might be found in the compatibility matrix
32#[derive(Debug)]
33enum Issue {
34    /// The given argument is the invalid type for the input
35    Invalid(usize),
36    /// There is a missing input
37    Missing(usize),
38    /// There's a superfluous argument
39    Extra(usize),
40    /// Two arguments should be swapped
41    Swap(usize, usize),
42    /// Several arguments should be reordered
43    Permutation(Vec<Option<usize>>),
44}
45
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub(crate) enum Compatibility<'tcx> {
48    Compatible,
49    Incompatible(Option<TypeError<'tcx>>),
50}
51
52/// Similar to `Issue`, but contains some extra information
53#[derive(Debug, PartialEq, Eq)]
54pub(crate) enum Error<'tcx> {
55    /// The provided argument is the invalid type for the expected input
56    Invalid(ProvidedIdx, ExpectedIdx, Compatibility<'tcx>),
57    /// There is a missing input
58    Missing(ExpectedIdx),
59    /// There's a superfluous argument
60    Extra(ProvidedIdx),
61    /// Two arguments should be swapped
62    Swap(ProvidedIdx, ProvidedIdx, ExpectedIdx, ExpectedIdx),
63    /// Several arguments should be reordered
64    Permutation(Vec<(ExpectedIdx, ProvidedIdx)>),
65}
66
67impl Ord for Error<'_> {
68    fn cmp(&self, other: &Self) -> Ordering {
69        let key = |error: &Error<'_>| -> usize {
70            match error {
71                Error::Invalid(..) => 0,
72                Error::Extra(_) => 1,
73                Error::Missing(_) => 2,
74                Error::Swap(..) => 3,
75                Error::Permutation(..) => 4,
76            }
77        };
78        match (self, other) {
79            (Error::Invalid(a, _, _), Error::Invalid(b, _, _)) => a.cmp(b),
80            (Error::Extra(a), Error::Extra(b)) => a.cmp(b),
81            (Error::Missing(a), Error::Missing(b)) => a.cmp(b),
82            (Error::Swap(a, b, ..), Error::Swap(c, d, ..)) => a.cmp(c).then(b.cmp(d)),
83            (Error::Permutation(a), Error::Permutation(b)) => a.cmp(b),
84            _ => key(self).cmp(&key(other)),
85        }
86    }
87}
88
89impl PartialOrd for Error<'_> {
90    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
91        Some(self.cmp(other))
92    }
93}
94
95pub(crate) struct ArgMatrix<'tcx> {
96    /// Maps the indices in the `compatibility_matrix` rows to the indices of
97    /// the *user provided* inputs
98    provided_indices: Vec<ProvidedIdx>,
99    /// Maps the indices in the `compatibility_matrix` columns to the indices
100    /// of the *expected* args
101    expected_indices: Vec<ExpectedIdx>,
102    /// The first dimension (rows) are the remaining user provided inputs to
103    /// match and the second dimension (cols) are the remaining expected args
104    /// to match
105    compatibility_matrix: Vec<Vec<Compatibility<'tcx>>>,
106}
107
108impl<'tcx> ArgMatrix<'tcx> {
109    pub(crate) fn new<F: FnMut(ProvidedIdx, ExpectedIdx) -> Compatibility<'tcx>>(
110        provided_count: usize,
111        expected_input_count: usize,
112        mut is_compatible: F,
113    ) -> Self {
114        let compatibility_matrix = (0..provided_count)
115            .map(|i| {
116                (0..expected_input_count)
117                    .map(|j| is_compatible(ProvidedIdx::from_usize(i), ExpectedIdx::from_usize(j)))
118                    .collect()
119            })
120            .collect();
121        ArgMatrix {
122            provided_indices: (0..provided_count).map(ProvidedIdx::from_usize).collect(),
123            expected_indices: (0..expected_input_count).map(ExpectedIdx::from_usize).collect(),
124            compatibility_matrix,
125        }
126    }
127
128    /// Remove a given input from consideration
129    fn eliminate_provided(&mut self, idx: usize) {
130        self.provided_indices.remove(idx);
131        self.compatibility_matrix.remove(idx);
132    }
133
134    /// Remove a given argument from consideration
135    fn eliminate_expected(&mut self, idx: usize) {
136        self.expected_indices.remove(idx);
137        for row in &mut self.compatibility_matrix {
138            row.remove(idx);
139        }
140    }
141
142    /// "satisfy" an input with a given arg, removing both from consideration
143    fn satisfy_input(&mut self, provided_idx: usize, expected_idx: usize) {
144        self.eliminate_provided(provided_idx);
145        self.eliminate_expected(expected_idx);
146    }
147
148    // Returns a `Vec` of (user input, expected arg) of matched arguments. These
149    // are inputs on the remaining diagonal that match.
150    fn eliminate_satisfied(&mut self) -> Vec<(ProvidedIdx, ExpectedIdx)> {
151        let num_args = cmp::min(self.provided_indices.len(), self.expected_indices.len());
152        let mut eliminated = vec![];
153        for i in (0..num_args).rev() {
154            if matches!(self.compatibility_matrix[i][i], Compatibility::Compatible) {
155                eliminated.push((self.provided_indices[i], self.expected_indices[i]));
156                self.satisfy_input(i, i);
157            }
158        }
159        eliminated
160    }
161
162    // Find some issue in the compatibility matrix
163    fn find_issue(&self) -> Option<Issue> {
164        let mat = &self.compatibility_matrix;
165        let ai = &self.expected_indices;
166        let ii = &self.provided_indices;
167
168        // Issue: 100478, when we end the iteration,
169        // `next_unmatched_idx` will point to the index of the first unmatched
170        let mut next_unmatched_idx = 0;
171        for i in 0..cmp::max(ai.len(), ii.len()) {
172            // If we eliminate the last row, any left-over arguments are considered missing
173            if i >= mat.len() {
174                return Some(Issue::Missing(next_unmatched_idx));
175            }
176            // If we eliminate the last column, any left-over inputs are extra
177            if mat[i].len() == 0 {
178                return Some(Issue::Extra(next_unmatched_idx));
179            }
180
181            // Make sure we don't pass the bounds of our matrix
182            let is_arg = i < ai.len();
183            let is_input = i < ii.len();
184            if is_arg && is_input && matches!(mat[i][i], Compatibility::Compatible) {
185                // This is a satisfied input, so move along
186                next_unmatched_idx += 1;
187                continue;
188            }
189
190            let mut useless = true;
191            let mut unsatisfiable = true;
192            if is_arg {
193                for j in 0..ii.len() {
194                    // If we find at least one input this argument could satisfy
195                    // this argument isn't unsatisfiable
196                    if matches!(mat[j][i], Compatibility::Compatible) {
197                        unsatisfiable = false;
198                        break;
199                    }
200                }
201            }
202            if is_input {
203                for j in 0..ai.len() {
204                    // If we find at least one argument that could satisfy this input
205                    // this input isn't useless
206                    if matches!(mat[i][j], Compatibility::Compatible) {
207                        useless = false;
208                        break;
209                    }
210                }
211            }
212
213            match (is_input, is_arg, useless, unsatisfiable) {
214                // If an argument is unsatisfied, and the input in its position is useless
215                // then the most likely explanation is that we just got the types wrong
216                (true, true, true, true) => return Some(Issue::Invalid(i)),
217                // Otherwise, if an input is useless then indicate that this is an extra input
218                (true, _, true, _) => return Some(Issue::Extra(i)),
219                // Otherwise, if an argument is unsatisfiable, indicate that it's missing
220                (_, true, _, true) => return Some(Issue::Missing(i)),
221                (true, true, _, _) => {
222                    // The argument isn't useless, and the input isn't unsatisfied,
223                    // so look for a parameter we might swap it with
224                    // We look for swaps explicitly, instead of just falling back on permutations
225                    // so that cases like (A,B,C,D) given (B,A,D,C) show up as two swaps,
226                    // instead of a large permutation of 4 elements.
227                    for j in 0..cmp::min(ai.len(), ii.len()) {
228                        if i == j || matches!(mat[j][j], Compatibility::Compatible) {
229                            continue;
230                        }
231                        if matches!(mat[i][j], Compatibility::Compatible)
232                            && matches!(mat[j][i], Compatibility::Compatible)
233                        {
234                            return Some(Issue::Swap(i, j));
235                        }
236                    }
237                }
238                _ => {
239                    continue;
240                }
241            }
242        }
243
244        // We didn't find any of the individual issues above, but
245        // there might be a larger permutation of parameters, so we now check for that
246        // by checking for cycles
247        // We use a double option at position i in this vec to represent:
248        // - None: We haven't computed anything about this argument yet
249        // - Some(None): This argument definitely doesn't participate in a cycle
250        // - Some(Some(x)): the i-th argument could permute to the x-th position
251        let mut permutation: Vec<Option<Option<usize>>> = vec![None; mat.len()];
252        let mut permutation_found = false;
253        for i in 0..mat.len() {
254            if permutation[i].is_some() {
255                // We've already decided whether this argument is or is not in a loop
256                continue;
257            }
258
259            let mut stack = vec![];
260            let mut j = i;
261            let mut last = i;
262            let mut is_cycle = true;
263            loop {
264                stack.push(j);
265                // Look for params this one could slot into
266                let compat: Vec<_> =
267                    mat[j]
268                        .iter()
269                        .enumerate()
270                        .filter_map(|(i, c)| {
271                            if matches!(c, Compatibility::Compatible) { Some(i) } else { None }
272                        })
273                        .collect();
274                if compat.len() < 1 {
275                    // try to find a cycle even when this could go into multiple slots, see #101097
276                    is_cycle = false;
277                    break;
278                }
279                j = compat[0];
280                if stack.contains(&j) {
281                    last = j;
282                    break;
283                }
284            }
285            if stack.len() <= 2 {
286                // If we encounter a cycle of 1 or 2 elements, we'll let the
287                // "satisfy" and "swap" code above handle those
288                is_cycle = false;
289            }
290            // We've built up some chain, some of which might be a cycle
291            // ex: [1,2,3,4]; last = 2; j = 2;
292            // So, we want to mark 4, 3, and 2 as part of a permutation
293            permutation_found = is_cycle;
294            while let Some(x) = stack.pop() {
295                if is_cycle {
296                    permutation[x] = Some(Some(j));
297                    j = x;
298                    if j == last {
299                        // From here on out, we're a tail leading into a cycle,
300                        // not the cycle itself
301                        is_cycle = false;
302                    }
303                } else {
304                    // Some(None) ensures we save time by skipping this argument again
305                    permutation[x] = Some(None);
306                }
307            }
308        }
309
310        if permutation_found {
311            // Map unwrap to remove the first layer of Some
312            let final_permutation: Vec<Option<usize>> =
313                permutation.into_iter().map(|x| x.unwrap()).collect();
314            return Some(Issue::Permutation(final_permutation));
315        }
316        None
317    }
318
319    // Obviously, detecting exact user intention is impossible, so the goal here is to
320    // come up with as likely of a story as we can to be helpful.
321    //
322    // We'll iteratively removed "satisfied" input/argument pairs,
323    // then check for the cases above, until we've eliminated the entire grid
324    //
325    // We'll want to know which arguments and inputs these rows and columns correspond to
326    // even after we delete them.
327    pub(crate) fn find_errors(
328        mut self,
329    ) -> (Vec<Error<'tcx>>, IndexVec<ExpectedIdx, Option<ProvidedIdx>>) {
330        let provided_arg_count = self.provided_indices.len();
331
332        let mut errors: Vec<Error<'tcx>> = vec![];
333        // For each expected argument, the matched *actual* input
334        let mut matched_inputs: IndexVec<ExpectedIdx, Option<ProvidedIdx>> =
335            IndexVec::from_elem_n(None, self.expected_indices.len());
336
337        // Before we start looking for issues, eliminate any arguments that are already satisfied,
338        // so that an argument which is already spoken for by the input it's in doesn't
339        // spill over into another similarly typed input
340        // ex:
341        //   fn some_func(_a: i32, _b: i32) {}
342        //   some_func(1, "");
343        // Without this elimination, the first argument causes the second argument
344        // to show up as both a missing input and extra argument, rather than
345        // just an invalid type.
346        for (provided, expected) in self.eliminate_satisfied() {
347            matched_inputs[expected] = Some(provided);
348        }
349
350        while !self.provided_indices.is_empty() || !self.expected_indices.is_empty() {
351            let res = self.find_issue();
352            match res {
353                Some(Issue::Invalid(idx)) => {
354                    let compatibility = self.compatibility_matrix[idx][idx].clone();
355                    let input_idx = self.provided_indices[idx];
356                    let arg_idx = self.expected_indices[idx];
357                    self.satisfy_input(idx, idx);
358                    errors.push(Error::Invalid(input_idx, arg_idx, compatibility));
359                }
360                Some(Issue::Extra(idx)) => {
361                    let input_idx = self.provided_indices[idx];
362                    self.eliminate_provided(idx);
363                    errors.push(Error::Extra(input_idx));
364                }
365                Some(Issue::Missing(idx)) => {
366                    let arg_idx = self.expected_indices[idx];
367                    self.eliminate_expected(idx);
368                    errors.push(Error::Missing(arg_idx));
369                }
370                Some(Issue::Swap(idx, other)) => {
371                    let input_idx = self.provided_indices[idx];
372                    let other_input_idx = self.provided_indices[other];
373                    let arg_idx = self.expected_indices[idx];
374                    let other_arg_idx = self.expected_indices[other];
375                    let (min, max) = (cmp::min(idx, other), cmp::max(idx, other));
376                    self.satisfy_input(min, max);
377                    // Subtract 1 because we already removed the "min" row
378                    self.satisfy_input(max - 1, min);
379                    errors.push(Error::Swap(input_idx, other_input_idx, arg_idx, other_arg_idx));
380                    matched_inputs[other_arg_idx] = Some(input_idx);
381                    matched_inputs[arg_idx] = Some(other_input_idx);
382                }
383                Some(Issue::Permutation(args)) => {
384                    let mut idxs: Vec<usize> = args.iter().filter_map(|&a| a).collect();
385
386                    let mut real_idxs: IndexVec<ProvidedIdx, Option<(ExpectedIdx, ProvidedIdx)>> =
387                        IndexVec::from_elem_n(None, provided_arg_count);
388                    for (src, dst) in
389                        args.iter().enumerate().filter_map(|(src, dst)| dst.map(|dst| (src, dst)))
390                    {
391                        let src_input_idx = self.provided_indices[src];
392                        let dst_input_idx = self.provided_indices[dst];
393                        let dest_arg_idx = self.expected_indices[dst];
394                        real_idxs[src_input_idx] = Some((dest_arg_idx, dst_input_idx));
395                        matched_inputs[dest_arg_idx] = Some(src_input_idx);
396                    }
397                    idxs.sort();
398                    idxs.reverse();
399                    for i in idxs {
400                        self.satisfy_input(i, i);
401                    }
402                    errors.push(Error::Permutation(real_idxs.into_iter().flatten().collect()));
403                }
404                None => {
405                    // We didn't find any issues, so we need to push the algorithm forward
406                    // First, eliminate any arguments that currently satisfy their inputs
407                    let eliminated = self.eliminate_satisfied();
408                    assert!(!eliminated.is_empty(), "didn't eliminated any indice in this round");
409                    for (inp, arg) in eliminated {
410                        matched_inputs[arg] = Some(inp);
411                    }
412                }
413            };
414        }
415
416        // sort errors with same type by the order they appear in the source
417        // so that suggestion will be handled properly, see #112507
418        errors.sort();
419        (errors, matched_inputs)
420    }
421}