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 error for missing tuple pattern in args #45069

Merged
merged 5 commits into from
Oct 13, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
215 changes: 172 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,97 @@ 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 expected_str = if let Some(n) = expected_tuple {
assert!(expected == 1);
if closure_args.as_ref().map(|&(ref pats, _)| pats.len()) == Some(n) {
Cow::from("a single tuple as argument")
} else {
// be verbose when numbers differ
Cow::from(format!("a single {}-tuple as argument", n))
}
} else {
Cow::from(args_str(expected, false))
};

let found_str = if expected_tuple.is_some() {
args_str(found, true)
} else {
args_str(found, false)
};


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,
expected_str,
found_str,
);

err.span_label(
span,
format!(
"expected {} that takes {}",
kind,
expected_str,
)
);

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 {}", found_str));
} 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 the closure to accept a tuple",
sugg
);
}
} else {
err.span_label(span, format!("takes {}", found_str));
}
}

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 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:21:53
|
21 | let _it = vec![1, 2, 3].into_iter().enumerate().map(|i: usize, x| i);
| ^^^ ------------- help: consider changing the closure to accept a tuple: `|(i, x): (usize, _)|`
| |
| expected closure that takes a single tuple as argument

error[E0593]: closure is expected to take a single 2-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 a single 2-tuple as argument

error: aborting due to 8 previous errors