Skip to content

Commit

Permalink
Auto merge of #63870 - estebank:async-fn-call, r=oli-obk
Browse files Browse the repository at this point in the history
Suggest call fn ctor passed as arg to fn with type param bounds

_Reviewer note: the relevant changes are in the second commit, the first is simple and mechanical, but verbose._

When forgetting to call a fn in an argument position to an fn that has a generic bound:

```rust
async fn foo() {}

fn bar(f: impl Future<Output=()>) {}

fn main() {
    bar(foo); // <- should be `bar(foo());`
}
```

suggest calling it:

```
error[E0277]: the trait bound `fn() -> impl std::future::Future {foo}: std::future::Future` is not satisfied
  --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:9:5
   |
LL | fn bar(f: impl Future<Output=()>) {}
   | --------------------------------- required by `bar`
...
LL |     bar(foo);
   |     ^^^ the trait `std::future::Future` is not implemented for `fn() -> impl std::future::Future {foo}`
   |
   = help: it looks like you forgot to use parentheses to call the function: `foo()`
```

Fix #63100. Follow up to #63833 and #63337.
  • Loading branch information
bors committed Sep 1, 2019
2 parents e29faf0 + e553051 commit dfd43f0
Show file tree
Hide file tree
Showing 193 changed files with 1,317 additions and 2,102 deletions.
104 changes: 83 additions & 21 deletions src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
use super::{
ConstEvalFailure,
EvaluationResult,
FulfillmentError,
FulfillmentErrorCode,
MismatchedProjectionTypes,
ObjectSafetyViolation,
Obligation,
ObligationCause,
ObligationCauseCode,
OnUnimplementedDirective,
OnUnimplementedNote,
OutputTypeParameterMismatch,
TraitNotObjectSafe,
ConstEvalFailure,
Overflow,
PredicateObligation,
SelectionContext,
SelectionError,
ObjectSafetyViolation,
Overflow,
TraitNotObjectSafe,
};

use crate::hir;
Expand All @@ -35,7 +36,7 @@ use crate::util::nodemap::{FxHashMap, FxHashSet};
use errors::{Applicability, DiagnosticBuilder};
use std::fmt;
use syntax::ast;
use syntax::symbol::sym;
use syntax::symbol::{sym, kw};
use syntax_pos::{DUMMY_SP, Span, ExpnKind};

impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
Expand Down Expand Up @@ -657,19 +658,22 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
span,
E0277,
"{}",
message.unwrap_or_else(||
format!("the trait bound `{}` is not satisfied{}",
trait_ref.to_predicate(), post_message)
));
message.unwrap_or_else(|| format!(
"the trait bound `{}` is not satisfied{}",
trait_ref.to_predicate(),
post_message,
)));

let explanation =
if obligation.cause.code == ObligationCauseCode::MainFunctionType {
"consider using `()`, or a `Result`".to_owned()
} else {
format!("{}the trait `{}` is not implemented for `{}`",
pre_message,
trait_ref,
trait_ref.self_ty())
format!(
"{}the trait `{}` is not implemented for `{}`",
pre_message,
trait_ref,
trait_ref.self_ty(),
)
};

if let Some(ref s) = label {
Expand All @@ -686,6 +690,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}

self.suggest_borrow_on_unsized_slice(&obligation.cause.code, &mut err);
self.suggest_fn_call(&obligation, &mut err, &trait_ref);
self.suggest_remove_reference(&obligation, &mut err, &trait_ref);
self.suggest_semicolon_removal(&obligation, &mut err, span, &trait_ref);

Expand Down Expand Up @@ -953,6 +958,57 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}

fn suggest_fn_call(
&self,
obligation: &PredicateObligation<'tcx>,
err: &mut DiagnosticBuilder<'tcx>,
trait_ref: &ty::Binder<ty::TraitRef<'tcx>>,
) {
let self_ty = trait_ref.self_ty();
match self_ty.sty {
ty::FnDef(def_id, _) => {
// We tried to apply the bound to an `fn`. Check whether calling it would evaluate
// to a type that *would* satisfy the trait binding. If it would, suggest calling
// it: `bar(foo)` -> `bar(foo)`. This case is *very* likely to be hit if `foo` is
// `async`.
let output_ty = self_ty.fn_sig(self.tcx).output();
let new_trait_ref = ty::TraitRef {
def_id: trait_ref.def_id(),
substs: self.tcx.mk_substs_trait(output_ty.skip_binder(), &[]),
};
let obligation = Obligation::new(
obligation.cause.clone(),
obligation.param_env,
new_trait_ref.to_predicate(),
);
match self.evaluate_obligation(&obligation) {
Ok(EvaluationResult::EvaluatedToOk) |
Ok(EvaluationResult::EvaluatedToOkModuloRegions) |
Ok(EvaluationResult::EvaluatedToAmbig) => {
if let Some(hir::Node::Item(hir::Item {
ident,
node: hir::ItemKind::Fn(.., body_id),
..
})) = self.tcx.hir().get_if_local(def_id) {
let body = self.tcx.hir().body(*body_id);
err.help(&format!(
"use parentheses to call the function: `{}({})`",
ident,
body.params.iter()
.map(|arg| match &arg.pat.node {
hir::PatKind::Binding(_, _, ident, None)
if ident.name != kw::SelfLower => ident.to_string(),
_ => "_".to_string(),
}).collect::<Vec<_>>().join(", ")));
}
}
_ => {}
}
}
_ => {}
}
}

/// Whenever references are used by mistake, like `for (i, e) in &vec.iter().enumerate()`,
/// suggest removing these references until we reach a type that implements the trait.
fn suggest_remove_reference(
Expand Down Expand Up @@ -1535,25 +1591,31 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
err.note("only the last element of a tuple may have a dynamically sized type");
}
ObligationCauseCode::ProjectionWf(data) => {
err.note(&format!("required so that the projection `{}` is well-formed",
data));
err.note(&format!(
"required so that the projection `{}` is well-formed",
data,
));
}
ObligationCauseCode::ReferenceOutlivesReferent(ref_ty) => {
err.note(&format!("required so that reference `{}` does not outlive its referent",
ref_ty));
err.note(&format!(
"required so that reference `{}` does not outlive its referent",
ref_ty,
));
}
ObligationCauseCode::ObjectTypeBound(object_ty, region) => {
err.note(&format!("required so that the lifetime bound of `{}` for `{}` \
is satisfied",
region, object_ty));
err.note(&format!(
"required so that the lifetime bound of `{}` for `{}` is satisfied",
region,
object_ty,
));
}
ObligationCauseCode::ItemObligation(item_def_id) => {
let item_name = tcx.def_path_str(item_def_id);
let msg = format!("required by `{}`", item_name);

if let Some(sp) = tcx.hir().span_if_local(item_def_id) {
let sp = tcx.sess.source_map().def_span(sp);
err.span_note(sp, &msg);
err.span_label(sp, &msg);
} else {
err.note(&msg);
}
Expand Down
Loading

0 comments on commit dfd43f0

Please sign in to comment.