summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs')
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs376
1 files changed, 376 insertions, 0 deletions
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs b/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs
new file mode 100644
index 000000000..7602f2550
--- /dev/null
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs
@@ -0,0 +1,376 @@
+use std::cmp;
+
+use rustc_index::vec::IndexVec;
+use rustc_middle::ty::error::TypeError;
+
+rustc_index::newtype_index! {
+ pub(crate) struct ExpectedIdx {
+ DEBUG_FORMAT = "ExpectedIdx({})",
+ }
+}
+
+rustc_index::newtype_index! {
+ pub(crate) struct ProvidedIdx {
+ DEBUG_FORMAT = "ProvidedIdx({})",
+ }
+}
+
+impl ExpectedIdx {
+ pub fn to_provided_idx(self) -> ProvidedIdx {
+ ProvidedIdx::from_usize(self.as_usize())
+ }
+}
+
+// An issue that might be found in the compatibility matrix
+#[derive(Debug)]
+enum Issue {
+ /// The given argument is the invalid type for the input
+ Invalid(usize),
+ /// There is a missing input
+ Missing(usize),
+ /// There's a superfluous argument
+ Extra(usize),
+ /// Two arguments should be swapped
+ Swap(usize, usize),
+ /// Several arguments should be reordered
+ Permutation(Vec<Option<usize>>),
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum Compatibility<'tcx> {
+ Compatible,
+ Incompatible(Option<TypeError<'tcx>>),
+}
+
+/// Similar to `Issue`, but contains some extra information
+#[derive(Debug)]
+pub(crate) enum Error<'tcx> {
+ /// The provided argument is the invalid type for the expected input
+ Invalid(ProvidedIdx, ExpectedIdx, Compatibility<'tcx>),
+ /// There is a missing input
+ Missing(ExpectedIdx),
+ /// There's a superfluous argument
+ Extra(ProvidedIdx),
+ /// Two arguments should be swapped
+ Swap(ProvidedIdx, ProvidedIdx, ExpectedIdx, ExpectedIdx),
+ /// Several arguments should be reordered
+ Permutation(Vec<(ExpectedIdx, ProvidedIdx)>),
+}
+
+pub(crate) struct ArgMatrix<'tcx> {
+ /// Maps the indices in the `compatibility_matrix` rows to the indices of
+ /// the *user provided* inputs
+ provided_indices: Vec<ProvidedIdx>,
+ /// Maps the indices in the `compatibility_matrix` columns to the indices
+ /// of the *expected* args
+ expected_indices: Vec<ExpectedIdx>,
+ /// The first dimension (rows) are the remaining user provided inputs to
+ /// match and the second dimension (cols) are the remaining expected args
+ /// to match
+ compatibility_matrix: Vec<Vec<Compatibility<'tcx>>>,
+}
+
+impl<'tcx> ArgMatrix<'tcx> {
+ pub(crate) fn new<F: FnMut(ProvidedIdx, ExpectedIdx) -> Compatibility<'tcx>>(
+ provided_count: usize,
+ expected_input_count: usize,
+ mut is_compatible: F,
+ ) -> Self {
+ let compatibility_matrix = (0..provided_count)
+ .map(|i| {
+ (0..expected_input_count)
+ .map(|j| is_compatible(ProvidedIdx::from_usize(i), ExpectedIdx::from_usize(j)))
+ .collect()
+ })
+ .collect();
+ ArgMatrix {
+ provided_indices: (0..provided_count).map(ProvidedIdx::from_usize).collect(),
+ expected_indices: (0..expected_input_count).map(ExpectedIdx::from_usize).collect(),
+ compatibility_matrix,
+ }
+ }
+
+ /// Remove a given input from consideration
+ fn eliminate_provided(&mut self, idx: usize) {
+ self.provided_indices.remove(idx);
+ self.compatibility_matrix.remove(idx);
+ }
+
+ /// Remove a given argument from consideration
+ fn eliminate_expected(&mut self, idx: usize) {
+ self.expected_indices.remove(idx);
+ for row in &mut self.compatibility_matrix {
+ row.remove(idx);
+ }
+ }
+
+ /// "satisfy" an input with a given arg, removing both from consideration
+ fn satisfy_input(&mut self, provided_idx: usize, expected_idx: usize) {
+ self.eliminate_provided(provided_idx);
+ self.eliminate_expected(expected_idx);
+ }
+
+ // Returns a `Vec` of (user input, expected arg) of matched arguments. These
+ // are inputs on the remaining diagonal that match.
+ fn eliminate_satisfied(&mut self) -> Vec<(ProvidedIdx, ExpectedIdx)> {
+ let num_args = cmp::min(self.provided_indices.len(), self.expected_indices.len());
+ let mut eliminated = vec![];
+ for i in (0..num_args).rev() {
+ if matches!(self.compatibility_matrix[i][i], Compatibility::Compatible) {
+ eliminated.push((self.provided_indices[i], self.expected_indices[i]));
+ self.satisfy_input(i, i);
+ }
+ }
+ eliminated
+ }
+
+ // Find some issue in the compatibility matrix
+ fn find_issue(&self) -> Option<Issue> {
+ let mat = &self.compatibility_matrix;
+ let ai = &self.expected_indices;
+ let ii = &self.provided_indices;
+
+ for i in 0..cmp::max(ai.len(), ii.len()) {
+ // If we eliminate the last row, any left-over inputs are considered missing
+ if i >= mat.len() {
+ return Some(Issue::Missing(i));
+ }
+ // If we eliminate the last column, any left-over arguments are extra
+ if mat[i].len() == 0 {
+ return Some(Issue::Extra(i));
+ }
+
+ // Make sure we don't pass the bounds of our matrix
+ let is_arg = i < ai.len();
+ let is_input = i < ii.len();
+ if is_arg && is_input && matches!(mat[i][i], Compatibility::Compatible) {
+ // This is a satisfied input, so move along
+ continue;
+ }
+
+ let mut useless = true;
+ let mut unsatisfiable = true;
+ if is_arg {
+ for j in 0..ii.len() {
+ // If we find at least one input this argument could satisfy
+ // this argument isn't unsatisfiable
+ if matches!(mat[j][i], Compatibility::Compatible) {
+ unsatisfiable = false;
+ break;
+ }
+ }
+ }
+ if is_input {
+ for j in 0..ai.len() {
+ // If we find at least one argument that could satisfy this input
+ // this argument isn't useless
+ if matches!(mat[i][j], Compatibility::Compatible) {
+ useless = false;
+ break;
+ }
+ }
+ }
+
+ match (is_input, is_arg, useless, unsatisfiable) {
+ // If an argument is unsatisfied, and the input in its position is useless
+ // then the most likely explanation is that we just got the types wrong
+ (true, true, true, true) => return Some(Issue::Invalid(i)),
+ // Otherwise, if an input is useless, then indicate that this is an extra argument
+ (true, _, true, _) => return Some(Issue::Extra(i)),
+ // Otherwise, if an argument is unsatisfiable, indicate that it's missing
+ (_, true, _, true) => return Some(Issue::Missing(i)),
+ (true, true, _, _) => {
+ // The argument isn't useless, and the input isn't unsatisfied,
+ // so look for a parameter we might swap it with
+ // We look for swaps explicitly, instead of just falling back on permutations
+ // so that cases like (A,B,C,D) given (B,A,D,C) show up as two swaps,
+ // instead of a large permutation of 4 elements.
+ for j in 0..cmp::min(ai.len(), ii.len()) {
+ if i == j || matches!(mat[j][j], Compatibility::Compatible) {
+ continue;
+ }
+ if matches!(mat[i][j], Compatibility::Compatible)
+ && matches!(mat[j][i], Compatibility::Compatible)
+ {
+ return Some(Issue::Swap(i, j));
+ }
+ }
+ }
+ _ => {
+ continue;
+ }
+ }
+ }
+
+ // We didn't find any of the individual issues above, but
+ // there might be a larger permutation of parameters, so we now check for that
+ // by checking for cycles
+ // We use a double option at position i in this vec to represent:
+ // - None: We haven't computed anything about this argument yet
+ // - Some(None): This argument definitely doesn't participate in a cycle
+ // - Some(Some(x)): the i-th argument could permute to the x-th position
+ let mut permutation: Vec<Option<Option<usize>>> = vec![None; mat.len()];
+ let mut permutation_found = false;
+ for i in 0..mat.len() {
+ if permutation[i].is_some() {
+ // We've already decided whether this argument is or is not in a loop
+ continue;
+ }
+
+ let mut stack = vec![];
+ let mut j = i;
+ let mut last = i;
+ let mut is_cycle = true;
+ loop {
+ stack.push(j);
+ // Look for params this one could slot into
+ let compat: Vec<_> =
+ mat[j]
+ .iter()
+ .enumerate()
+ .filter_map(|(i, c)| {
+ if matches!(c, Compatibility::Compatible) { Some(i) } else { None }
+ })
+ .collect();
+ if compat.len() != 1 {
+ // this could go into multiple slots, don't bother exploring both
+ is_cycle = false;
+ break;
+ }
+ j = compat[0];
+ if stack.contains(&j) {
+ last = j;
+ break;
+ }
+ }
+ if stack.len() <= 2 {
+ // If we encounter a cycle of 1 or 2 elements, we'll let the
+ // "satisfy" and "swap" code above handle those
+ is_cycle = false;
+ }
+ // We've built up some chain, some of which might be a cycle
+ // ex: [1,2,3,4]; last = 2; j = 2;
+ // So, we want to mark 4, 3, and 2 as part of a permutation
+ permutation_found = is_cycle;
+ while let Some(x) = stack.pop() {
+ if is_cycle {
+ permutation[x] = Some(Some(j));
+ j = x;
+ if j == last {
+ // From here on out, we're a tail leading into a cycle,
+ // not the cycle itself
+ is_cycle = false;
+ }
+ } else {
+ // Some(None) ensures we save time by skipping this argument again
+ permutation[x] = Some(None);
+ }
+ }
+ }
+
+ if permutation_found {
+ // Map unwrap to remove the first layer of Some
+ let final_permutation: Vec<Option<usize>> =
+ permutation.into_iter().map(|x| x.unwrap()).collect();
+ return Some(Issue::Permutation(final_permutation));
+ }
+ return None;
+ }
+
+ // Obviously, detecting exact user intention is impossible, so the goal here is to
+ // come up with as likely of a story as we can to be helpful.
+ //
+ // We'll iteratively removed "satisfied" input/argument pairs,
+ // then check for the cases above, until we've eliminated the entire grid
+ //
+ // We'll want to know which arguments and inputs these rows and columns correspond to
+ // even after we delete them.
+ pub(crate) fn find_errors(
+ mut self,
+ ) -> (Vec<Error<'tcx>>, IndexVec<ExpectedIdx, Option<ProvidedIdx>>) {
+ let provided_arg_count = self.provided_indices.len();
+
+ let mut errors: Vec<Error<'tcx>> = vec![];
+ // For each expected argument, the matched *actual* input
+ let mut matched_inputs: IndexVec<ExpectedIdx, Option<ProvidedIdx>> =
+ IndexVec::from_elem_n(None, self.expected_indices.len());
+
+ // Before we start looking for issues, eliminate any arguments that are already satisfied,
+ // so that an argument which is already spoken for by the input it's in doesn't
+ // spill over into another similarly typed input
+ // ex:
+ // fn some_func(_a: i32, _b: i32) {}
+ // some_func(1, "");
+ // Without this elimination, the first argument causes the second argument
+ // to show up as both a missing input and extra argument, rather than
+ // just an invalid type.
+ for (provided, expected) in self.eliminate_satisfied() {
+ matched_inputs[expected] = Some(provided);
+ }
+
+ while !self.provided_indices.is_empty() || !self.expected_indices.is_empty() {
+ match self.find_issue() {
+ Some(Issue::Invalid(idx)) => {
+ let compatibility = self.compatibility_matrix[idx][idx].clone();
+ let input_idx = self.provided_indices[idx];
+ let arg_idx = self.expected_indices[idx];
+ self.satisfy_input(idx, idx);
+ errors.push(Error::Invalid(input_idx, arg_idx, compatibility));
+ }
+ Some(Issue::Extra(idx)) => {
+ let input_idx = self.provided_indices[idx];
+ self.eliminate_provided(idx);
+ errors.push(Error::Extra(input_idx));
+ }
+ Some(Issue::Missing(idx)) => {
+ let arg_idx = self.expected_indices[idx];
+ self.eliminate_expected(idx);
+ errors.push(Error::Missing(arg_idx));
+ }
+ Some(Issue::Swap(idx, other)) => {
+ let input_idx = self.provided_indices[idx];
+ let other_input_idx = self.provided_indices[other];
+ let arg_idx = self.expected_indices[idx];
+ let other_arg_idx = self.expected_indices[other];
+ let (min, max) = (cmp::min(idx, other), cmp::max(idx, other));
+ self.satisfy_input(min, max);
+ // Subtract 1 because we already removed the "min" row
+ self.satisfy_input(max - 1, min);
+ errors.push(Error::Swap(input_idx, other_input_idx, arg_idx, other_arg_idx));
+ matched_inputs[other_arg_idx] = Some(input_idx);
+ matched_inputs[arg_idx] = Some(other_input_idx);
+ }
+ Some(Issue::Permutation(args)) => {
+ let mut idxs: Vec<usize> = args.iter().filter_map(|&a| a).collect();
+
+ let mut real_idxs: IndexVec<ProvidedIdx, Option<(ExpectedIdx, ProvidedIdx)>> =
+ IndexVec::from_elem_n(None, provided_arg_count);
+ for (src, dst) in
+ args.iter().enumerate().filter_map(|(src, dst)| dst.map(|dst| (src, dst)))
+ {
+ let src_input_idx = self.provided_indices[src];
+ let dst_input_idx = self.provided_indices[dst];
+ let dest_arg_idx = self.expected_indices[dst];
+ real_idxs[src_input_idx] = Some((dest_arg_idx, dst_input_idx));
+ matched_inputs[dest_arg_idx] = Some(src_input_idx);
+ }
+ idxs.sort();
+ idxs.reverse();
+ for i in idxs {
+ self.satisfy_input(i, i);
+ }
+ errors.push(Error::Permutation(real_idxs.into_iter().flatten().collect()));
+ }
+ None => {
+ // We didn't find any issues, so we need to push the algorithm forward
+ // First, eliminate any arguments that currently satisfy their inputs
+ for (inp, arg) in self.eliminate_satisfied() {
+ matched_inputs[arg] = Some(inp);
+ }
+ }
+ };
+ }
+
+ return (errors, matched_inputs);
+ }
+}