Closure argument mismatch tweaks

- use consistent phrasing for expected and found arguments
 - suggest changing arugments to tuple if possible
 - suggest changing single tuple argument to arguments if possible
This commit is contained in:
Esteban Küber 2018-01-19 01:28:20 -08:00
parent bb345a0be3
commit 7ed00caacc
3 changed files with 214 additions and 177 deletions

View file

@ -717,93 +717,40 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
self.tcx.hir.span_if_local(did) self.tcx.hir.span_if_local(did)
}).map(|sp| self.tcx.sess.codemap().def_span(sp)); // the sp could be an fn def }).map(|sp| self.tcx.sess.codemap().def_span(sp)); // the sp could be an fn def
let found_ty_count = let found = match found_trait_ref.skip_binder().substs.type_at(1).sty {
match found_trait_ref.skip_binder().substs.type_at(1).sty { ty::TyTuple(ref tys, _) => tys.iter()
ty::TyTuple(ref tys, _) => tys.len(), .map(|_| ArgKind::empty()).collect::<Vec<_>>(),
_ => 1, _ => vec![ArgKind::empty()],
}; };
let (expected_tys, expected_ty_count) = let expected = match expected_trait_ref.skip_binder().substs.type_at(1).sty {
match expected_trait_ref.skip_binder().substs.type_at(1).sty { ty::TyTuple(ref tys, _) => tys.iter()
ty::TyTuple(ref tys, _) => .map(|t| match t.sty {
(tys.iter().map(|t| &t.sty).collect(), tys.len()), ty::TypeVariants::TyTuple(ref tys, _) => ArgKind::Tuple(
ref sty => (vec![sty], 1), span,
}; tys.iter()
if found_ty_count == expected_ty_count { .map(|ty| ("_".to_owned(), format!("{}", ty.sty)))
.collect::<Vec<_>>()
),
_ => ArgKind::Arg("_".to_owned(), format!("{}", t.sty)),
}).collect(),
ref sty => vec![ArgKind::Arg("_".to_owned(), format!("{}", sty))],
};
if found.len()== expected.len() {
self.report_closure_arg_mismatch(span, self.report_closure_arg_mismatch(span,
found_span, found_span,
found_trait_ref, found_trait_ref,
expected_trait_ref) expected_trait_ref)
} else { } else {
let expected_tuple = if expected_ty_count == 1 { let (closure_span, found) = found_did
expected_tys.first().and_then(|t| {
if let &&ty::TyTuple(ref tuptys, _) = t {
Some(tuptys.len())
} else {
None
}
})
} else {
None
};
// FIXME(#44150): Expand this to "N args expected but a N-tuple found."
// Type of the 1st expected argument is somehow provided as type of a
// found one in that case.
//
// ```
// [1i32, 2, 3].sort_by(|(a, b)| ..)
// // ^^^^^^^ --------
// // expected_trait_ref: std::ops::FnMut<(&i32, &i32)>
// // found_trait_ref: std::ops::FnMut<(&i32,)>
// ```
let (closure_span, closure_args) = found_did
.and_then(|did| self.tcx.hir.get_if_local(did)) .and_then(|did| self.tcx.hir.get_if_local(did))
.and_then(|node| { .map(|node| self.get_fn_like_arguments(node))
if let hir::map::NodeExpr( .unwrap_or((found_span.unwrap(), found));
&hir::Expr {
node: hir::ExprClosure(_, ref decl, id, span, _),
..
}) = node
{
let ty_snips = decl.inputs.iter()
.map(|ty| {
self.tcx.sess.codemap().span_to_snippet(ty.span).ok()
.and_then(|snip| {
// filter out dummy spans
if snip == "," || snip == "|" {
None
} else {
Some(snip)
}
})
})
.collect::<Vec<Option<String>>>();
let body = self.tcx.hir.body(id); self.report_arg_count_mismatch(span,
let pat_snips = body.arguments.iter() closure_span,
.map(|arg| expected,
self.tcx.sess.codemap().span_to_snippet(arg.pat.span).ok()) found,
.collect::<Option<Vec<String>>>(); found_trait_ty.is_closure())
Some((span, pat_snips, ty_snips))
} else {
None
}
})
.map(|(span, pat, ty)| (Some(span), Some((pat, ty))))
.unwrap_or((None, None));
let closure_args = closure_args.and_then(|(pat, ty)| Some((pat?, ty)));
self.report_arg_count_mismatch(
span,
closure_span.or(found_span),
expected_ty_count,
expected_tuple,
found_ty_count,
closure_args,
found_trait_ty.is_closure()
)
} }
} }
@ -845,94 +792,135 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
} }
} }
fn get_fn_like_arguments(&self, node: hir::map::Node) -> (Span, Vec<ArgKind>) {
if let hir::map::NodeExpr(&hir::Expr {
node: hir::ExprClosure(_, ref _decl, id, span, _),
..
}) = node {
(self.tcx.sess.codemap().def_span(span), self.tcx.hir.body(id).arguments.iter()
.map(|arg| {
if let hir::Pat {
node: hir::PatKind::Tuple(args, _),
span,
..
} = arg.pat.clone().into_inner() {
ArgKind::Tuple(
span,
args.iter().map(|pat| {
let snippet = self.tcx.sess.codemap()
.span_to_snippet(pat.span).unwrap();
(snippet, "_".to_owned())
}).collect::<Vec<_>>(),
)
} else {
let name = self.tcx.sess.codemap().span_to_snippet(arg.pat.span).unwrap();
ArgKind::Arg(name, "_".to_owned())
}
})
.collect::<Vec<ArgKind>>())
} else if let hir::map::NodeItem(&hir::Item {
span,
node: hir::ItemFn(ref decl, ..),
..
}) = node {
(self.tcx.sess.codemap().def_span(span), decl.inputs.iter()
.map(|arg| match arg.clone().into_inner().node {
hir::TyTup(ref tys) => ArgKind::Tuple(
arg.span,
tys.iter()
.map(|_| ("_".to_owned(), "_".to_owned()))
.collect::<Vec<_>>(),
),
_ => ArgKind::Arg("_".to_owned(), "_".to_owned())
}).collect::<Vec<ArgKind>>())
} else {
panic!("non-FnLike node found: {:?}", node);
}
}
fn report_arg_count_mismatch( fn report_arg_count_mismatch(
&self, &self,
span: Span, span: Span,
found_span: Option<Span>, found_span: Span,
expected: usize, expected_args: Vec<ArgKind>,
expected_tuple: Option<usize>, found_args: Vec<ArgKind>,
found: usize, is_closure: bool,
closure_args: Option<(Vec<String>, Vec<Option<String>>)>,
is_closure: bool
) -> DiagnosticBuilder<'tcx> { ) -> DiagnosticBuilder<'tcx> {
use std::borrow::Cow;
let kind = if is_closure { "closure" } else { "function" }; let kind = if is_closure { "closure" } else { "function" };
let args_str = |n, distinct| format!( let args_str = |arguments: &Vec<ArgKind>, other: &Vec<ArgKind>| {
"{} {}argument{}", let arg_length = arguments.len();
n, let distinct = match &other[..] {
if distinct && n >= 2 { "distinct " } else { "" }, &[ArgKind::Tuple(..)] => true,
if n == 1 { "" } else { "s" }, _ => false,
); };
match (arg_length, arguments.get(0)) {
let expected_str = if let Some(n) = expected_tuple { (1, Some(&ArgKind::Tuple(_, ref fields))) => {
assert!(expected == 1); format!("a single {}-tuple as argument", fields.len())
if closure_args.as_ref().map(|&(ref pats, _)| pats.len()) == Some(n) { }
Cow::from("a single tuple as argument") _ => format!("{} {}argument{}",
} else { arg_length,
// be verbose when numbers differ if distinct && arg_length > 1 { "distinct " } else { "" },
Cow::from(format!("a single {}-tuple as argument", n)) if arg_length == 1 { "" } else { "s" }),
} }
} else {
Cow::from(args_str(expected, false))
}; };
let found_str = if expected_tuple.is_some() { let expected_str = args_str(&expected_args, &found_args);
args_str(found, true) let found_str = args_str(&found_args, &expected_args);
} else {
args_str(found, false)
};
let mut err = struct_span_err!(
let mut err = struct_span_err!(self.tcx.sess, span, E0593, self.tcx.sess,
span,
E0593,
"{} is expected to take {}, but it takes {}", "{} is expected to take {}, but it takes {}",
kind, kind,
expected_str, expected_str,
found_str, found_str,
); );
err.span_label( err.span_label(span, format!( "expected {} that takes {}", kind, expected_str));
span, err.span_label(found_span, format!("takes {}", found_str));
format!(
"expected {} that takes {}",
kind,
expected_str,
)
);
if let Some(span) = found_span { if let &[ArgKind::Tuple(_, ref fields)] = &found_args[..] {
if let (Some(expected_tuple), Some((pats, tys))) = (expected_tuple, closure_args) { if fields.len() == expected_args.len() {
if expected_tuple != found || pats.len() != found { let sugg = fields.iter()
err.span_label(span, format!("takes {}", found_str)); .map(|(name, _)| name.to_owned())
} else { .collect::<Vec<String>>().join(", ");
let sugg = format!( err.span_suggestion(found_span,
"|({}){}|", "change the closure to take multiple arguments instead of \
pats.join(", "), a single tuple",
format!("|{}|", sugg));
// add type annotations if available }
if tys.iter().any(|ty| ty.is_some()) { }
Cow::from(format!( if let &[ArgKind::Tuple(_, ref fields)] = &expected_args[..] {
": ({})", if fields.len() == found_args.len() && is_closure {
tys.into_iter().map(|ty| if let Some(ty) = ty { let sugg = format!(
ty "|({}){}|",
} else { found_args.iter()
"_".to_string() .map(|arg| match arg {
}).collect::<Vec<String>>().join(", ") ArgKind::Arg(name, _) => name.to_owned(),
)) _ => "_".to_owned(),
} else { })
Cow::from("") .collect::<Vec<String>>()
}, .join(", "),
); // add type annotations if available
if found_args.iter().any(|arg| match arg {
err.span_suggestion( ArgKind::Arg(_, ty) => ty != "_",
span, _ => false,
"consider changing the closure to accept a tuple", }) {
sugg format!(": ({})",
); fields.iter()
} .map(|(_, ty)| ty.to_owned())
} else { .collect::<Vec<String>>()
err.span_label(span, format!("takes {}", found_str)); .join(", "))
} else {
"".to_owned()
},
);
err.span_suggestion(found_span,
"change the closure to accept a tuple instead of individual \
arguments",
sugg);
} }
} }
@ -1325,3 +1313,14 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
suggested_limit)); suggested_limit));
} }
} }
enum ArgKind {
Arg(String, String),
Tuple(Span, Vec<(String, String)>),
}
impl ArgKind {
fn empty() -> ArgKind {
ArgKind::Arg("_".to_owned(), "_".to_owned())
}
}

View file

@ -18,6 +18,8 @@ fn main() {
//~^ ERROR closure is expected to take //~^ ERROR closure is expected to take
[1, 2, 3].sort_by(|(tuple, tuple2)| panic!()); [1, 2, 3].sort_by(|(tuple, tuple2)| panic!());
//~^ ERROR closure is expected to take //~^ ERROR closure is expected to take
[1, 2, 3].sort_by(|(tuple, tuple2): (usize, _)| panic!());
//~^ ERROR closure is expected to take
f(|| panic!()); f(|| panic!());
//~^ ERROR closure is expected to take //~^ ERROR closure is expected to take
@ -32,6 +34,9 @@ fn main() {
let bar = |i, x, y| i; let bar = |i, x, y| i;
let _it = vec![1, 2, 3].into_iter().enumerate().map(bar); let _it = vec![1, 2, 3].into_iter().enumerate().map(bar);
//~^ ERROR closure is expected to take //~^ ERROR closure is expected to take
let _it = vec![1, 2, 3].into_iter().enumerate().map(qux);
//~^ ERROR function is expected to take
} }
fn foo() {} fn foo() {}
fn qux(x: usize, y: usize) {}

View file

@ -14,64 +14,97 @@ error[E0593]: closure is expected to take 2 arguments, but it takes 1 argument
| | | |
| expected closure that takes 2 arguments | expected closure that takes 2 arguments
error[E0593]: closure is expected to take 2 arguments, but it takes 1 argument error[E0593]: closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument
--> $DIR/closure-arg-count.rs:19:15 --> $DIR/closure-arg-count.rs:19:15
| |
19 | [1, 2, 3].sort_by(|(tuple, tuple2)| panic!()); 19 | [1, 2, 3].sort_by(|(tuple, tuple2)| panic!());
| ^^^^^^^ ----------------- takes 1 argument | ^^^^^^^ ----------------- takes a single 2-tuple as argument
| | | |
| expected closure that takes 2 arguments | expected closure that takes 2 distinct arguments
help: change the closure to take multiple arguments instead of a single tuple
|
19 | [1, 2, 3].sort_by(|tuple, tuple2| panic!());
| ^^^^^^^^^^^^^^^
error[E0593]: closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument
--> $DIR/closure-arg-count.rs:21:15
|
21 | [1, 2, 3].sort_by(|(tuple, tuple2): (usize, _)| panic!());
| ^^^^^^^ ----------------------------- takes a single 2-tuple as argument
| |
| expected closure that takes 2 distinct arguments
help: change the closure to take multiple arguments instead of a single tuple
|
21 | [1, 2, 3].sort_by(|tuple, tuple2| panic!());
| ^^^^^^^^^^^^^^^
error[E0593]: closure is expected to take 1 argument, but it takes 0 arguments error[E0593]: closure is expected to take 1 argument, but it takes 0 arguments
--> $DIR/closure-arg-count.rs:21:5 --> $DIR/closure-arg-count.rs:23:5
| |
21 | f(|| panic!()); 23 | f(|| panic!());
| ^ -- takes 0 arguments | ^ -- takes 0 arguments
| | | |
| expected closure that takes 1 argument | expected closure that takes 1 argument
| |
= note: required by `f` = note: required by `f`
error[E0593]: closure is expected to take a single tuple as argument, but it takes 2 distinct arguments error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments
--> $DIR/closure-arg-count.rs:24:53
|
24 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i, x| i);
| ^^^ ------ help: consider changing the closure to accept a tuple: `|(i, x)|`
| |
| expected closure that takes a single tuple as argument
error[E0593]: closure is expected to take a single tuple as argument, but it takes 2 distinct arguments
--> $DIR/closure-arg-count.rs:26:53 --> $DIR/closure-arg-count.rs:26:53
| |
26 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i: usize, x| i); 26 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i, x| i);
| ^^^ ------------- help: consider changing the closure to accept a tuple: `|(i, x): (usize, _)|` | ^^^ ------ takes 2 distinct arguments
| | | |
| expected closure that takes a single tuple as argument | expected closure that takes a single 2-tuple as argument
help: change the closure to accept a tuple instead of individual arguments
|
26 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|(i, x)| i);
| ^^^^^^^^
error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 3 distinct arguments error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments
--> $DIR/closure-arg-count.rs:28:53 --> $DIR/closure-arg-count.rs:28:53
| |
28 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i, x, y| i); 28 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i: usize, x| i);
| ^^^ ------------- takes 2 distinct arguments
| |
| expected closure that takes a single 2-tuple as argument
help: change the closure to accept a tuple instead of individual arguments
|
28 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|(i, x)| i);
| ^^^^^^^^
error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 3 distinct arguments
--> $DIR/closure-arg-count.rs:30:53
|
30 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i, x, y| i);
| ^^^ --------- takes 3 distinct arguments | ^^^ --------- takes 3 distinct arguments
| | | |
| expected closure that takes a single 2-tuple as argument | expected closure that takes a single 2-tuple as argument
error[E0593]: function is expected to take a single 2-tuple as argument, but it takes 0 arguments error[E0593]: function is expected to take a single 2-tuple as argument, but it takes 0 arguments
--> $DIR/closure-arg-count.rs:30:53 --> $DIR/closure-arg-count.rs:32:53
| |
30 | let _it = vec![1, 2, 3].into_iter().enumerate().map(foo); 32 | let _it = vec![1, 2, 3].into_iter().enumerate().map(foo);
| ^^^ expected function that takes a single 2-tuple as argument | ^^^ expected function that takes a single 2-tuple as argument
... ...
37 | fn foo() {} 41 | fn foo() {}
| -------- takes 0 arguments | -------- takes 0 arguments
error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 3 distinct arguments error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 3 distinct arguments
--> $DIR/closure-arg-count.rs:33:53 --> $DIR/closure-arg-count.rs:35:53
| |
32 | let bar = |i, x, y| i; 34 | let bar = |i, x, y| i;
| --------- takes 3 distinct arguments | --------- takes 3 distinct arguments
33 | let _it = vec![1, 2, 3].into_iter().enumerate().map(bar); 35 | let _it = vec![1, 2, 3].into_iter().enumerate().map(bar);
| ^^^ expected closure that takes a single 2-tuple as argument | ^^^ expected closure that takes a single 2-tuple as argument
error: aborting due to 9 previous errors error[E0593]: function is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments
--> $DIR/closure-arg-count.rs:37:53
|
37 | let _it = vec![1, 2, 3].into_iter().enumerate().map(qux);
| ^^^ expected function that takes a single 2-tuple as argument
...
42 | fn qux(x: usize, y: usize) {}
| -------------------------- takes 2 distinct arguments
error: aborting due to 11 previous errors