Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better method call error messages #71827

Closed
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_typeck/src/check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty
}

fn check_expr_kind(
pub(super) fn check_expr_kind(
&self,
expr: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
Expand Down
185 changes: 131 additions & 54 deletions compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let check_compatible = |arg_idx, input_idx| {
let formal_input_ty: Ty<'tcx> = formal_input_tys[input_idx];
let expected_input_ty: Ty<'tcx> = expected_input_tys[input_idx];


// If either is an error type, we defy the usual convention and consider them to *not* be
// coercible. This prevents our error message heuristic from trying to pass errors into
Expand All @@ -244,25 +245,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}

let provided_arg: &hir::Expr<'tcx> = &provided_args[arg_idx];
let tables = self.in_progress_typeck_results.map(|t| t.borrow()).unwrap();
let maybe_ty = tables.node_type_opt(provided_arg.hir_id);
if let Some(checked_ty) = maybe_ty {
let expectation = Expectation::rvalue_hint(self, expected_input_ty);
let coerced_ty = expectation.only_has_type(self).unwrap_or(formal_input_ty);
let can_coerce = self.can_coerce(checked_ty, coerced_ty);

let is_super = self
.at(&self.misc(provided_arg.span), self.param_env)
.sup(formal_input_ty, coerced_ty)
.is_ok();
// Same as above: if either the coerce type or the checked type is an error type,
// consider them *not* compatible.
return !coerced_ty.references_error()
&& !checked_ty.references_error()
&& can_coerce
&& is_super;
}
return false;
let expectation = Expectation::rvalue_hint(self, expected_input_ty);
// TODO: check that this is safe; I don't believe this commits any of the obligations, but I can't be sure.
//
// I had another method of "soft" type checking before,
// but it was failing to find the type of some expressions (like "")
// so I prodded this method and made it pub(super) so I could call it, and it seems to work well.
let checked_ty = self.check_expr_kind(provided_arg, expectation);

let coerced_ty = expectation.only_has_type(self).unwrap_or(formal_input_ty);
let can_coerce = self.can_coerce(checked_ty, coerced_ty);

let is_super = self
.at(&self.misc(provided_arg.span), self.param_env)
.sup(formal_input_ty, coerced_ty)
.is_ok();
// Same as above: if either the coerce type or the checked type is an error type,
// consider them *not* compatible.
return !coerced_ty.references_error()
&& !checked_ty.references_error()
&& can_coerce
&& is_super;
};

// Check each argument, to satisfy the input it was provided for
Expand Down Expand Up @@ -379,6 +382,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
eliminate_arg(mat, ai, arg_idx);
};

let eliminate_satisfied = |mat: &mut Vec<Vec<bool>>, ii: &mut Vec<usize>, ai: &mut Vec<usize>| {
let mut i = cmp::min(ii.len(), ai.len());
while i > 0 {
let idx = i - 1;
if mat[idx][idx] {
satisfy_input(
mat,
ii,
ai,
idx,
idx,
);
}
i -= 1;
}
};

// A list of the issues we might find
enum Issue {
Invalid(usize),
Expand Down Expand Up @@ -528,6 +548,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return None;
};

// 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, _a: 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.
eliminate_satisfied(&mut compatibility_matrix, &mut input_indexes, &mut arg_indexes);

// As we encounter issues, we'll transcribe them to their actual indices
let mut issues: Vec<Issue> = vec![];
// Until we've elimineated / satisfied all arguments/inputs
Expand Down Expand Up @@ -601,20 +632,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
None => {
// We didn't find any issues, so we need to push the algorithm forward
// First, eliminate any arguments that currently satisfy their inputs
let mut i = cmp::min(arg_indexes.len(), input_indexes.len());
while i > 0 {
let idx = i - 1;
if compatibility_matrix[idx][idx] {
satisfy_input(
&mut compatibility_matrix,
&mut input_indexes,
&mut arg_indexes,
idx,
idx,
);
}
i -= 1;
}
eliminate_satisfied(&mut compatibility_matrix, &mut input_indexes, &mut arg_indexes);
}
};
}
Expand All @@ -637,33 +655,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let mut labels = vec![];
let mut suggestions = vec![];
let mut suggestion_type = NoSuggestion;
let mut eliminated_args = vec![false; provided_arg_count];
let source_map = self.sess().source_map();
for issue in issues {
match issue {
Issue::Invalid(arg) => {
let span = provided_args[arg].span;
let expected_type = expected_input_tys[arg];
let found_type = final_arg_types[arg].unwrap().1;
let found_type = final_arg_types[arg].unwrap().0;
labels.push((span, format!("expected {}, found {}", expected_type, found_type)));
suggestion_type = match suggestion_type {
NoSuggestion | Provide => Provide,
_ => Changes,
};
suggestions.push((span, format!(" {{{}}},", expected_type)));
suggestions.push((span, format!("{{{}}}", expected_type)));
}
Issue::Extra(arg) => {
// FIXME: This could probably be a lot cleaner, but I dunno how
let span = provided_args[arg].span;
let hungry_span = if arg < provided_args.len() - 1 {
// Eat the comma
Span::new(
span.lo(),
span.hi() + BytePos(2),
span.ctxt(),
)
} else {
span
};

// We want to suggest deleting this arg,
// but dealing with formatting before and after is tricky
// so we mark it as deleted, so we can do a scan afterwards
eliminated_args[arg] = true;
let found_type = self.check_expr(&provided_args[arg]);
// TODO: if someone knows the method-chain-foo to achieve this, let me know
// but I didn't have the patience for it lol
Expand All @@ -673,23 +686,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let descr = def_kind.descr(def_id);
if let Some(node) = node {
if let Some(ident) = node.ident() {
format!("{}", ident)
format!("for {}", ident)
} else {
format!("this {}", descr)
format!("for this {}", descr)
}
} else {
format!("this {}", descr)
format!("for this {}", descr)
}
} else {
"here".to_string()
};

labels.push((span, format!("no parameter of type {} is needed {}", found_type, fn_name)));
labels.push((span, format!("this parameter of type {} isn't needed {}", found_type, fn_name)));
suggestion_type = match suggestion_type {
NoSuggestion | Remove => Remove,
_ => Changes,
};
suggestions.push((hungry_span, format!("")));
}
Issue::Missing(arg) => {
// FIXME: The spans here are all kinds of wrong for multiple missing arguments etc.
Quantumplation marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -793,15 +805,80 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
for (span, label) in labels {
err.span_label(span, label);
}

// Before constructing our multipart suggestions, we need to add in all the suggestions
// to eliminate extra parameters
// We can't overlap spans in a suggestion or weird things happen
let mut lo = None;
let mut hi = None;
let mut in_block = false;
for (idx, eliminated) in eliminated_args.iter().enumerate() {
match (in_block, eliminated, idx == eliminated_args.len() - 1) {
(false, true, false) => {
// We just encountered the start of a block of eliminated parameters
lo = Some(idx);
in_block = true;
},
(false, true, true) => {
// We encountered a single eliminated arg at the end of the arg list
lo = Some(idx);
hi = Some(idx);
}
(true, false, _) => {
// We encountered the end of a block, set the hi so the logic below kicks in
hi = Some(idx - 1);
in_block = false;
},
(true, true, true) => {
hi = Some(idx);
in_block = false;
}
_ => {}
}
if lo.is_some() && hi.is_some() {
let (lo_idx, hi_idx) = (lo.unwrap(), hi.unwrap());
// We've found a contiguous block, so emit the elimination
// be careful abound the boundaries
let ctxt = provided_args[0].span.ctxt();
let (lo_bp, hi_bp) = match (lo_idx == 0, hi_idx == provided_arg_count - 1) {
// If we have an arg to our left, chop off it's comma
// a, xx, xx, xx
// [-----------)
(false, _) => {
(provided_args[lo_idx - 1].span.hi(), provided_args[hi_idx].span.hi())
},
// If we start from the first arg, and we have an arg to our right, chop of the last params comma
// xx, xx, xx, a
// [-----------)
(true, false) => {
(provided_args[lo_idx].span.lo(), provided_args[hi_idx + 1].span.lo())
},
// If every argument was eliminated, don't need to worry about commas before or after
// xx, xx, xx, xx
// [-------------)
(true, true) => {
(provided_args[lo_idx].span.lo(), provided_args[hi_idx].span.hi())
},
};
let eliminated_span = Span::new(
lo_bp,
hi_bp,
ctxt,
);
suggestions.push((eliminated_span, "".to_string()));
lo = None;
hi = None;
}
}

// And add a series of suggestions
// FIXME: for simpler cases, this might be overkill
if suggestions.len() > 0 {
let suggestion_text = match (suggestion_type, issue_count) {
(Remove, 1) => Some("removing this argument may help"),
(Remove, _) => Some("removing these argument may help"),
(Provide, 1) => None,
(Provide, _) => Some("providing these parameters may help"),
(Remove, _) => Some("removing these arguments may help"),
(Provide, 1) => Some("provideing a parameter of the correct type here may help"),
(Provide, _) => Some("providing parameters of these types may help"),
(Swap, 1) => Some("swapping these two arguments might help"),
(Swap, _) => Some("swapping these sets of arguments might help"),
(Reorder, _) => Some("reordering these parameters might help"),
Expand Down
12 changes: 10 additions & 2 deletions src/test/ui/argument-suggestions/basic.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ error[E0059]: arguments to this function are incorrect
--> $DIR/basic.rs:20:13
|
LL | invalid(1.0);
| ^^^ expected u32, found u32
| ^^^ expected u32, found {float}
|
note: function defined here
--> $DIR/basic.rs:13:4
|
LL | fn invalid(_i: u32) {}
| ^^^^^^^ -------
help: provideing a parameter of the correct type here may help
Quantumplation marked this conversation as resolved.
Show resolved Hide resolved
|
LL | invalid({u32});
| ^^^^^

error[E0059]: arguments to this function are incorrect
--> $DIR/basic.rs:21:11
|
LL | extra("");
| ^^
| |
| no parameter of type &'static str is needed extra
| this parameter of type &'static str isn't needed for extra
| help: removing this argument may help
Quantumplation marked this conversation as resolved.
Show resolved Hide resolved
|
note: function defined here
Expand All @@ -36,6 +40,10 @@ note: function defined here
|
LL | fn missing(_i: u32) {}
| ^^^^^^^ -------
help: provideing a parameter of the correct type here may help
|
LL | missing( {u32},);
| ^^^^^^

error[E0059]: arguments to this function are incorrect
--> $DIR/basic.rs:23:13
Expand Down
8 changes: 4 additions & 4 deletions src/test/ui/argument-suggestions/complex.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ LL | complex(1.0, H {}, &"", G{}, F::X2, Z {}, X {}, Y {});
| | | || expected G, found G
| | | |expected F, found F
| | | missing argument of type E
| | no parameter of type H is needed complex
| expected u32, found u32
| | this parameter of type H isn't needed for complex
| expected u32, found {float}
|
note: function defined here
--> $DIR/complex.rs:11:4
Expand All @@ -19,8 +19,8 @@ LL | fn complex(_i: u32, _s: &str, _e: E, _f: F, _g: G, _x: X, _y: Y, _z: Z ) {}
| ^^^^^^^ ------- -------- ----- ----- ----- ----- ----- ------
help: the following changes might help
|
LL | complex( {u32},, &"", {E}, F::X2, G{}, X {}, Y {}, Z {});
| ^^^^^^ -- ^^^^ ^^^^^ ^^^ ^^^^ ^^^^ ^^^^
LL | complex({u32}, &"", {E}, F::X2, G{}, X {}, Y {}, Z {});
| ^^--^ ^^^^ ^^^^^ ^^^ ^^^^ ^^^^ ^^^^
Quantumplation marked this conversation as resolved.
Show resolved Hide resolved

error: aborting due to previous error

Expand Down
15 changes: 15 additions & 0 deletions src/test/ui/argument-suggestions/extra_arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,19 @@ fn main() {
two_arg_diff(1, "", ""); //~ ERROR arguments to this function are incorrect
two_arg_diff(1, 1, "", ""); //~ ERROR arguments to this function are incorrect
two_arg_diff(1, "", 1, ""); //~ ERROR arguments to this function are incorrect

// Check with weird spacing and newlines
two_arg_same(1, 1, ""); //~ ERROR arguments to this function are incorrect
two_arg_diff(1, 1, ""); //~ ERROR arguments to this function are incorrect
two_arg_same(
1,
1,
"" //~ ERROR arguments to this function are incorrect
);

two_arg_diff(
1,
1, //~ ERROR arguments to this function are incorrect
""
);
}
Loading