Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
206 changes: 163 additions & 43 deletions src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,41 +711,105 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
}
}

OutputTypeParameterMismatch(ref expected_trait_ref, ref actual_trait_ref, _) => {
OutputTypeParameterMismatch(ref found_trait_ref, ref expected_trait_ref, _) => {
let found_trait_ref = self.resolve_type_vars_if_possible(&*found_trait_ref);
let expected_trait_ref = self.resolve_type_vars_if_possible(&*expected_trait_ref);
let actual_trait_ref = self.resolve_type_vars_if_possible(&*actual_trait_ref);
if actual_trait_ref.self_ty().references_error() {
if expected_trait_ref.self_ty().references_error() {
return;
}
let expected_trait_ty = expected_trait_ref.self_ty();
let found_span = expected_trait_ty.ty_to_def_id().and_then(|did| {
let found_trait_ty = found_trait_ref.self_ty();

let found_did = found_trait_ty.ty_to_def_id();
let found_span = found_did.and_then(|did| {
self.tcx.hir.span_if_local(did)
});

let self_ty_count =
match expected_trait_ref.skip_binder().substs.type_at(1).sty {
let found_ty_count =
match found_trait_ref.skip_binder().substs.type_at(1).sty {
ty::TyTuple(ref tys, _) => tys.len(),
_ => 1,
};
let arg_ty_count =
match actual_trait_ref.skip_binder().substs.type_at(1).sty {
ty::TyTuple(ref tys, _) => tys.len(),
_ => 1,
let (expected_tys, expected_ty_count) =
match expected_trait_ref.skip_binder().substs.type_at(1).sty {
ty::TyTuple(ref tys, _) =>
(tys.iter().map(|t| &t.sty).collect(), tys.len()),
ref sty => (vec![sty], 1),
};
if self_ty_count == arg_ty_count {
if found_ty_count == expected_ty_count {
self.report_closure_arg_mismatch(span,
found_span,
expected_trait_ref,
actual_trait_ref)
found_trait_ref,
expected_trait_ref)
} else {
// Expected `|| { }`, found `|x, y| { }`
// Expected `fn(x) -> ()`, found `|| { }`
let expected_tuple = if expected_ty_count == 1 {
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."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this say "FIXME"?

Copy link
Contributor Author

@sinkuu sinkuu Oct 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR improves message for "expected |(x, y)|, found |x, y|" case but not "expected |x, y|, found |(x, y)|" case. In the latter case, found_trait_ref should be FnMut<((_, _),)>, but it becomes FnMut<(_,)> and I don't know how to fix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikomatsakis, I believe @sinkuu is referring to the following case:

 18 |     f(|(x, y)| x + y);
    |     ^ -------- takes a single tuple as an argument
    |     | |
    |     | help: consider changing to: `|x, y|`
    |     |
    |     expected closure that takes 2 arguments

We should create an issue after this PR lands to track that case.

// 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(|node| {
if let hir::map::NodeExpr(
&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);
let pat_snips = body.arguments.iter()
.map(|arg|
self.tcx.sess.codemap().span_to_snippet(arg.pat.span).ok())
.collect::<Option<Vec<String>>>();

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,
found_span,
arg_ty_count,
self_ty_count,
expected_trait_ty.is_closure()
closure_span.or(found_span),
expected_ty_count,
expected_tuple,
found_ty_count,
closure_args,
found_trait_ty.is_closure()
)
}
}
Expand All @@ -767,32 +831,88 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
err.emit();
}

fn report_arg_count_mismatch(&self,
span: Span,
found_span: Option<Span>,
expected: usize,
found: usize,
is_closure: bool)
-> DiagnosticBuilder<'tcx>
{
fn report_arg_count_mismatch(
&self,
span: Span,
found_span: Option<Span>,
expected: usize,
expected_tuple: Option<usize>,
found: usize,
closure_args: Option<(Vec<String>, Vec<Option<String>>)>,
is_closure: bool
) -> DiagnosticBuilder<'tcx> {
use std::borrow::Cow;

let kind = if is_closure { "closure" } else { "function" };

let args_str = |n, distinct| format!(
"{} {}argument{}",
n,
if distinct && n >= 2 { "distinct " } else { "" },
if n == 1 { "" } else { "s" },
);

let mut err = struct_span_err!(self.tcx.sess, span, E0593,
"{} takes {} argument{} but {} argument{} {} required",
if is_closure { "closure" } else { "function" },
found,
if found == 1 { "" } else { "s" },
expected,
if expected == 1 { "" } else { "s" },
if expected == 1 { "is" } else { "are" });

err.span_label(span, format!("expected {} that takes {} argument{}",
if is_closure { "closure" } else { "function" },
expected,
if expected == 1 { "" } else { "s" }));
"{} is expected to take {}, but it takes {}",
kind,
if expected_tuple.is_some() {
Cow::from("a single tuple as argument")
} else {
Cow::from(args_str(expected, false))
},
if expected_tuple.is_some() {
args_str(found, true)
} else {
args_str(found, false)
},
);

err.span_label(
span,
format!(
"expected {} that takes {}{}",
kind,
args_str(expected, false),
if let Some(n) = expected_tuple {
assert!(expected == 1);
Cow::from(format!(", a {}-tuple", n))
} else {
Cow::from("")
}
)
);

if let Some(span) = found_span {
err.span_label(span, format!("takes {} argument{}",
found,
if found == 1 { "" } else { "s" }));
if let (Some(expected_tuple), Some((pats, tys))) = (expected_tuple, closure_args) {
if expected_tuple != found || pats.len() != found {
err.span_label(span, format!("takes {}", args_str(found, true)));
} else {
let sugg = format!(
"|({}){}|",
pats.join(", "),

// add type annotations if available
if tys.iter().any(|ty| ty.is_some()) {
Cow::from(format!(
": ({})",
tys.into_iter().map(|ty| if let Some(ty) = ty {
ty
} else {
"_".to_string()
}).collect::<Vec<String>>().join(", ")
))
} else {
Cow::from("")
},
);

err.span_suggestion(span, "consider changing to", sugg);
}
} else {
err.span_label(span, format!("takes {}", args_str(found, false)));
}
}

err
}

Expand Down
4 changes: 4 additions & 0 deletions src/test/ui/mismatched_types/closure-arg-count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ fn main() {
[1, 2, 3].sort_by(|tuple| panic!());
[1, 2, 3].sort_by(|(tuple, tuple2)| panic!());
f(|| panic!());

let _it = vec![1, 2, 3].into_iter().enumerate().map(|i, x| i);
let _it = vec![1, 2, 3].into_iter().enumerate().map(|i: usize, x| i);
let _it = vec![1, 2, 3].into_iter().enumerate().map(|i, x, y| i);
}
42 changes: 33 additions & 9 deletions src/test/ui/mismatched_types/closure-arg-count.stderr
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
error[E0593]: closure takes 0 arguments but 2 arguments are required
error[E0593]: closure is expected to take 2 arguments, but it takes 0 arguments
--> $DIR/closure-arg-count.rs:15:15
|
15 | [1, 2, 3].sort_by(|| panic!());
| ^^^^^^^ ----------- takes 0 arguments
| ^^^^^^^ -- takes 0 arguments
| |
| expected closure that takes 2 arguments

error[E0593]: closure takes 1 argument but 2 arguments are required
error[E0593]: closure is expected to take 2 arguments, but it takes 1 argument
--> $DIR/closure-arg-count.rs:16:15
|
16 | [1, 2, 3].sort_by(|tuple| panic!());
| ^^^^^^^ ---------------- takes 1 argument
| ^^^^^^^ ------- takes 1 argument
| |
| expected closure that takes 2 arguments

Expand All @@ -23,23 +23,47 @@ error[E0308]: mismatched types
= note: expected type `&{integer}`
found type `(_, _)`

error[E0593]: closure takes 1 argument but 2 arguments are required
error[E0593]: closure is expected to take 2 arguments, but it takes 1 argument
--> $DIR/closure-arg-count.rs:17:15
|
17 | [1, 2, 3].sort_by(|(tuple, tuple2)| panic!());
| ^^^^^^^ -------------------------- takes 1 argument
| ^^^^^^^ ----------------- takes 1 argument
| |
| expected closure that takes 2 arguments

error[E0593]: closure takes 0 arguments but 1 argument is required
error[E0593]: closure is expected to take 1 argument, but it takes 0 arguments
--> $DIR/closure-arg-count.rs:18:5
|
18 | f(|| panic!());
| ^ ----------- takes 0 arguments
| ^ -- takes 0 arguments
| |
| expected closure that takes 1 argument
|
= note: required by `f`

error: aborting due to 5 previous errors
error[E0593]: closure is expected to take a single tuple as argument, but it takes 2 distinct arguments
--> $DIR/closure-arg-count.rs:20:53
|
20 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i, x| i);
| ^^^ ------ help: consider changing to: `|(i, x)|`
Copy link
Contributor

@estebank estebank Oct 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you modify the suggestion text to be freestanding without the snippet? Something along the lines of consider changing the closure to accept a tuple. rustc is not the only way these messages can be presented, so they should make sense even when the suggested code is not presented next to it.

| |
| expected closure that takes 1 argument, a 2-tuple
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really good! I hope you don't mind me nitpicking some more though. =)

Reading this "expected closure that takes 1 argument, a 2-tuple" -- which I think I may even have initially suggested -- I see now that it is actually ambiguous. In particular, the intention was something like "that takes 1 argument, which is a 2-tuple", but it could also be parsed as a list, like "that takes 1 argument and a 2-tuple". In fact, this second interpretation is what I got this time, and I had to re-read it a few times to understand what was meant.

Therefore, I suggest:

"expected closure that takes a single tuple as argument"

which echoes the wording from above


error[E0593]: closure is expected to take a single tuple as argument, but it takes 2 distinct arguments
--> $DIR/closure-arg-count.rs:21:53
|
21 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i: usize, x| i);
| ^^^ ------------- help: consider changing to: `|(i, x): (usize, _)|`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks great

| |
| expected closure that takes 1 argument, a 2-tuple

error[E0593]: closure is expected to take a single tuple as argument, but it takes 3 distinct arguments
--> $DIR/closure-arg-count.rs:22:53
|
22 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i, x, y| i);
| ^^^ --------- takes 3 distinct arguments
| |
| expected closure that takes 1 argument, a 2-tuple

error: aborting due to 8 previous errors