Skip to content

Commit 5c2c476

Browse files
authored
Rollup merge of rust-lang#102863 - compiler-errors:call-suggestion-on-unimplemented, r=nagisa
Standardize "use parentheses to call" suggestions between typeck and trait selection 1. Suggest calling constructors, since they're basically `FnDef`s but they have a different def kind and hir representation, so we were leaving them out. 2. Standardize the call suggestions between trait fulfillment errors and type mismatch. In the type mismatch suggestion, we suggest `/* Ty */` as the placeholder for an arg, and not the parameter's name, which is less helpful. 3. Use `predicate_must_hold_modulo_regions` instead of matching on `EvaluationResult` -- this might cause some suggestions to be filtered out, but we really shouldn't be suggesting a call if it "may" hold, only when it "must" hold. 4. Borrow some logic from `extract_callable_info` to generalize this suggestion to fn pointers, type parameters, and opaque types. Fixes rust-lang#102852
2 parents 84365ff + 35f1570 commit 5c2c476

19 files changed

+262
-106
lines changed

compiler/rustc_hir_analysis/src/check/callee.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::method::probe::{IsSuggestion, Mode, ProbeScope};
22
use super::method::MethodCallee;
3-
use super::{DefIdOrName, Expectation, FnCtxt, TupleArgumentsFlag};
3+
use super::{Expectation, FnCtxt, TupleArgumentsFlag};
44
use crate::type_error_struct;
55

66
use rustc_ast::util::parser::PREC_POSTFIX;
@@ -27,6 +27,7 @@ use rustc_span::Span;
2727
use rustc_target::spec::abi;
2828
use rustc_trait_selection::autoderef::Autoderef;
2929
use rustc_trait_selection::infer::InferCtxtExt as _;
30+
use rustc_trait_selection::traits::error_reporting::DefIdOrName;
3031
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
3132

3233
use std::iter;

compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs

+4-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use super::FnCtxt;
22
use crate::astconv::AstConv;
33
use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel};
44

5-
use hir::def_id::DefId;
65
use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX};
76
use rustc_errors::{Applicability, Diagnostic, MultiSpan};
87
use rustc_hir as hir;
@@ -19,6 +18,7 @@ use rustc_session::errors::ExprParenthesesNeeded;
1918
use rustc_span::symbol::sym;
2019
use rustc_span::Span;
2120
use rustc_trait_selection::infer::InferCtxtExt;
21+
use rustc_trait_selection::traits::error_reporting::DefIdOrName;
2222
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
2323

2424
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
@@ -90,7 +90,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
9090
if ty.is_suggestable(self.tcx, false) {
9191
format!("/* {ty} */")
9292
} else {
93-
"".to_string()
93+
"/* value */".to_string()
9494
}
9595
})
9696
.collect::<Vec<_>>()
@@ -102,10 +102,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
102102

103103
let msg = match def_id_or_name {
104104
DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) {
105-
DefKind::Ctor(CtorOf::Struct, _) => "instantiate this tuple struct".to_string(),
106-
DefKind::Ctor(CtorOf::Variant, _) => {
107-
"instantiate this tuple variant".to_string()
108-
}
105+
DefKind::Ctor(CtorOf::Struct, _) => "construct this tuple struct".to_string(),
106+
DefKind::Ctor(CtorOf::Variant, _) => "construct this tuple variant".to_string(),
109107
kind => format!("call this {}", kind.descr(def_id)),
110108
},
111109
DefIdOrName::Name(name) => format!("call this {name}"),
@@ -1209,8 +1207,3 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12091207
}
12101208
}
12111209
}
1212-
1213-
pub enum DefIdOrName {
1214-
DefId(DefId),
1215-
Name(&'static str),
1216-
}

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ pub mod on_unimplemented;
22
pub mod suggestions;
33

44
use super::{
5-
EvaluationResult, FulfillmentContext, FulfillmentError, FulfillmentErrorCode,
6-
MismatchedProjectionTypes, Obligation, ObligationCause, ObligationCauseCode,
7-
OnUnimplementedDirective, OnUnimplementedNote, OutputTypeParameterMismatch, Overflow,
8-
PredicateObligation, SelectionContext, SelectionError, TraitNotObjectSafe,
5+
FulfillmentContext, FulfillmentError, FulfillmentErrorCode, MismatchedProjectionTypes,
6+
Obligation, ObligationCause, ObligationCauseCode, OnUnimplementedDirective,
7+
OnUnimplementedNote, OutputTypeParameterMismatch, Overflow, PredicateObligation,
8+
SelectionContext, SelectionError, TraitNotObjectSafe,
99
};
1010

1111
use crate::infer::error_reporting::{TyCategory, TypeAnnotationNeeded as ErrorCode};
@@ -2796,3 +2796,8 @@ impl<'tcx> ty::TypeVisitor<'tcx> for HasNumericInferVisitor {
27962796
}
27972797
}
27982798
}
2799+
2800+
pub enum DefIdOrName {
2801+
DefId(DefId),
2802+
Name(&'static str),
2803+
}

compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

+153-64
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use super::{
2-
EvaluationResult, Obligation, ObligationCause, ObligationCauseCode, PredicateObligation,
2+
DefIdOrName, Obligation, ObligationCause, ObligationCauseCode, PredicateObligation,
33
SelectionContext,
44
};
55

66
use crate::autoderef::Autoderef;
77
use crate::infer::InferCtxt;
88
use crate::traits::normalize_to;
99

10+
use hir::def::CtorOf;
1011
use hir::HirId;
1112
use rustc_data_structures::fx::FxHashSet;
1213
use rustc_data_structures::stack::ensure_sufficient_stack;
@@ -22,14 +23,15 @@ use rustc_hir::lang_items::LangItem;
2223
use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Node};
2324
use rustc_infer::infer::error_reporting::TypeErrCtxt;
2425
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
26+
use rustc_infer::infer::LateBoundRegionConversionTime;
2527
use rustc_middle::hir::map;
2628
use rustc_middle::ty::{
2729
self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, DefIdTree,
2830
GeneratorDiagnosticData, GeneratorInteriorTypeCause, Infer, InferTy, IsSuggestable,
2931
ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitable,
3032
};
3133
use rustc_middle::ty::{TypeAndMut, TypeckResults};
32-
use rustc_span::symbol::{kw, sym, Ident, Symbol};
34+
use rustc_span::symbol::{sym, Ident, Symbol};
3335
use rustc_span::{BytePos, DesugaringKind, ExpnKind, Span, DUMMY_SP};
3436
use rustc_target::spec::abi;
3537
use std::fmt;
@@ -812,74 +814,136 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
812814
err: &mut Diagnostic,
813815
trait_pred: ty::PolyTraitPredicate<'tcx>,
814816
) -> bool {
815-
// Skipping binder here, remapping below
816-
let self_ty = trait_pred.self_ty().skip_binder();
817-
818-
let (def_id, output_ty, callable) = match *self_ty.kind() {
819-
ty::Closure(def_id, substs) => (def_id, substs.as_closure().sig().output(), "closure"),
820-
ty::FnDef(def_id, _) => (def_id, self_ty.fn_sig(self.tcx).output(), "function"),
821-
_ => return false,
822-
};
823-
let msg = format!("use parentheses to call the {}", callable);
817+
if let ty::PredicateKind::Trait(trait_pred) = obligation.predicate.kind().skip_binder()
818+
&& Some(trait_pred.def_id()) == self.tcx.lang_items().sized_trait()
819+
{
820+
// Don't suggest calling to turn an unsized type into a sized type
821+
return false;
822+
}
824823

825-
// "We should really create a single list of bound vars from the combined vars
826-
// from the predicate and function, but instead we just liberate the function bound vars"
827-
let output_ty = self.tcx.liberate_late_bound_regions(def_id, output_ty);
824+
// This is duplicated from `extract_callable_info` in typeck, which
825+
// relies on autoderef, so we can't use it here.
826+
let found = trait_pred.self_ty().skip_binder().peel_refs();
827+
let Some((def_id_or_name, output, inputs)) = (match *found.kind()
828+
{
829+
ty::FnPtr(fn_sig) => {
830+
Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.inputs()))
831+
}
832+
ty::FnDef(def_id, _) => {
833+
let fn_sig = found.fn_sig(self.tcx);
834+
Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs()))
835+
}
836+
ty::Closure(def_id, substs) => {
837+
let fn_sig = substs.as_closure().sig();
838+
Some((
839+
DefIdOrName::DefId(def_id),
840+
fn_sig.output(),
841+
fn_sig.inputs().map_bound(|inputs| &inputs[1..]),
842+
))
843+
}
844+
ty::Opaque(def_id, substs) => {
845+
self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| {
846+
if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder()
847+
&& Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output()
848+
// args tuple will always be substs[1]
849+
&& let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
850+
{
851+
Some((
852+
DefIdOrName::DefId(def_id),
853+
pred.kind().rebind(proj.term.ty().unwrap()),
854+
pred.kind().rebind(args.as_slice()),
855+
))
856+
} else {
857+
None
858+
}
859+
})
860+
}
861+
ty::Dynamic(data, _, ty::Dyn) => {
862+
data.iter().find_map(|pred| {
863+
if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder()
864+
&& Some(proj.item_def_id) == self.tcx.lang_items().fn_once_output()
865+
// for existential projection, substs are shifted over by 1
866+
&& let ty::Tuple(args) = proj.substs.type_at(0).kind()
867+
{
868+
Some((
869+
DefIdOrName::Name("trait object"),
870+
pred.rebind(proj.term.ty().unwrap()),
871+
pred.rebind(args.as_slice()),
872+
))
873+
} else {
874+
None
875+
}
876+
})
877+
}
878+
ty::Param(_) => {
879+
obligation.param_env.caller_bounds().iter().find_map(|pred| {
880+
if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder()
881+
&& Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output()
882+
&& proj.projection_ty.self_ty() == found
883+
// args tuple will always be substs[1]
884+
&& let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
885+
{
886+
Some((
887+
DefIdOrName::Name("type parameter"),
888+
pred.kind().rebind(proj.term.ty().unwrap()),
889+
pred.kind().rebind(args.as_slice()),
890+
))
891+
} else {
892+
None
893+
}
894+
})
895+
}
896+
_ => None,
897+
}) else { return false; };
898+
let output = self.replace_bound_vars_with_fresh_vars(
899+
obligation.cause.span,
900+
LateBoundRegionConversionTime::FnCall,
901+
output,
902+
);
903+
let inputs = inputs.skip_binder().iter().map(|ty| {
904+
self.replace_bound_vars_with_fresh_vars(
905+
obligation.cause.span,
906+
LateBoundRegionConversionTime::FnCall,
907+
inputs.rebind(*ty),
908+
)
909+
});
828910

829911
// Remapping bound vars here
830-
let trait_pred_and_self = trait_pred.map_bound(|trait_pred| (trait_pred, output_ty));
912+
let trait_pred_and_self = trait_pred.map_bound(|trait_pred| (trait_pred, output));
831913

832914
let new_obligation =
833915
self.mk_trait_obligation_with_new_self_ty(obligation.param_env, trait_pred_and_self);
834-
835-
match self.evaluate_obligation(&new_obligation) {
836-
Ok(
837-
EvaluationResult::EvaluatedToOk
838-
| EvaluationResult::EvaluatedToOkModuloRegions
839-
| EvaluationResult::EvaluatedToOkModuloOpaqueTypes
840-
| EvaluationResult::EvaluatedToAmbig,
841-
) => {}
842-
_ => return false,
916+
if !self.predicate_must_hold_modulo_regions(&new_obligation) {
917+
return false;
843918
}
844-
let hir = self.tcx.hir();
919+
845920
// Get the name of the callable and the arguments to be used in the suggestion.
846-
let (snippet, sugg) = match hir.get_if_local(def_id) {
847-
Some(hir::Node::Expr(hir::Expr {
848-
kind: hir::ExprKind::Closure(hir::Closure { fn_decl, fn_decl_span, .. }),
849-
..
850-
})) => {
851-
err.span_label(*fn_decl_span, "consider calling this closure");
852-
let Some(name) = self.get_closure_name(def_id, err, &msg) else {
853-
return false;
854-
};
855-
let args = fn_decl.inputs.iter().map(|_| "_").collect::<Vec<_>>().join(", ");
856-
let sugg = format!("({})", args);
857-
(format!("{}{}", name, sugg), sugg)
858-
}
859-
Some(hir::Node::Item(hir::Item {
860-
ident,
861-
kind: hir::ItemKind::Fn(.., body_id),
862-
..
863-
})) => {
864-
err.span_label(ident.span, "consider calling this function");
865-
let body = hir.body(*body_id);
866-
let args = body
867-
.params
868-
.iter()
869-
.map(|arg| match &arg.pat.kind {
870-
hir::PatKind::Binding(_, _, ident, None)
871-
// FIXME: provide a better suggestion when encountering `SelfLower`, it
872-
// should suggest a method call.
873-
if ident.name != kw::SelfLower => ident.to_string(),
874-
_ => "_".to_string(),
875-
})
876-
.collect::<Vec<_>>()
877-
.join(", ");
878-
let sugg = format!("({})", args);
879-
(format!("{}{}", ident, sugg), sugg)
880-
}
881-
_ => return false,
921+
let hir = self.tcx.hir();
922+
923+
let msg = match def_id_or_name {
924+
DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) {
925+
DefKind::Ctor(CtorOf::Struct, _) => {
926+
"use parentheses to construct this tuple struct".to_string()
927+
}
928+
DefKind::Ctor(CtorOf::Variant, _) => {
929+
"use parentheses to construct this tuple variant".to_string()
930+
}
931+
kind => format!("use parentheses to call this {}", kind.descr(def_id)),
932+
},
933+
DefIdOrName::Name(name) => format!("use parentheses to call this {name}"),
882934
};
935+
936+
let args = inputs
937+
.map(|ty| {
938+
if ty.is_suggestable(self.tcx, false) {
939+
format!("/* {ty} */")
940+
} else {
941+
"/* value */".to_string()
942+
}
943+
})
944+
.collect::<Vec<_>>()
945+
.join(", ");
946+
883947
if matches!(obligation.cause.code(), ObligationCauseCode::FunctionArgumentObligation { .. })
884948
&& obligation.cause.span.can_be_used_for_suggestions()
885949
{
@@ -890,11 +954,36 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
890954
err.span_suggestion_verbose(
891955
obligation.cause.span.shrink_to_hi(),
892956
&msg,
893-
sugg,
957+
format!("({args})"),
894958
Applicability::HasPlaceholders,
895959
);
896-
} else {
897-
err.help(&format!("{}: `{}`", msg, snippet));
960+
} else if let DefIdOrName::DefId(def_id) = def_id_or_name {
961+
let name = match hir.get_if_local(def_id) {
962+
Some(hir::Node::Expr(hir::Expr {
963+
kind: hir::ExprKind::Closure(hir::Closure { fn_decl_span, .. }),
964+
..
965+
})) => {
966+
err.span_label(*fn_decl_span, "consider calling this closure");
967+
let Some(name) = self.get_closure_name(def_id, err, &msg) else {
968+
return false;
969+
};
970+
name.to_string()
971+
}
972+
Some(hir::Node::Item(hir::Item { ident, kind: hir::ItemKind::Fn(..), .. })) => {
973+
err.span_label(ident.span, "consider calling this function");
974+
ident.to_string()
975+
}
976+
Some(hir::Node::Ctor(..)) => {
977+
let name = self.tcx.def_path_str(def_id);
978+
err.span_label(
979+
self.tcx.def_span(def_id),
980+
format!("consider calling the constructor for `{}`", name),
981+
);
982+
name
983+
}
984+
_ => return false,
985+
};
986+
err.help(&format!("{msg}: `{name}({args})`"));
898987
}
899988
true
900989
}

src/test/ui/binop/issue-77910-1.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ LL | assert_eq!(foo, y);
1919
| ^^^^^^^^^^^^^^^^^^ `for<'a> fn(&'a i32) -> &'a i32 {foo}` cannot be formatted using `{:?}` because it doesn't implement `Debug`
2020
|
2121
= help: the trait `Debug` is not implemented for fn item `for<'a> fn(&'a i32) -> &'a i32 {foo}`
22-
= help: use parentheses to call the function: `foo(s)`
22+
= help: use parentheses to call this function: `foo(/* &i32 */)`
2323
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
2424

2525
error: aborting due to 2 previous errors

src/test/ui/closures/closure-bounds-subtype.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ note: required by a bound in `take_const_owned`
1111
|
1212
LL | fn take_const_owned<F>(_: F) where F: FnOnce() + Sync + Send {
1313
| ^^^^ required by this bound in `take_const_owned`
14+
help: use parentheses to call this type parameter
15+
|
16+
LL | take_const_owned(f());
17+
| ++
1418
help: consider further restricting this bound
1519
|
1620
LL | fn give_owned<F>(f: F) where F: FnOnce() + Send + std::marker::Sync {

src/test/ui/issues/issue-35241.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ LL | fn test() -> Foo { Foo }
1111
|
1212
= note: expected struct `Foo`
1313
found fn item `fn(u32) -> Foo {Foo}`
14-
help: use parentheses to instantiate this tuple struct
14+
help: use parentheses to construct this tuple struct
1515
|
1616
LL | fn test() -> Foo { Foo(/* u32 */) }
1717
| +++++++++++

src/test/ui/issues/issue-70724-add_type_neq_err_label-unwrap.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ LL | assert_eq!(a, 0);
2929
| ^^^^^^^^^^^^^^^^ `fn() -> i32 {a}` cannot be formatted using `{:?}` because it doesn't implement `Debug`
3030
|
3131
= help: the trait `Debug` is not implemented for fn item `fn() -> i32 {a}`
32-
= help: use parentheses to call the function: `a()`
32+
= help: use parentheses to call this function: `a()`
3333
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
3434

3535
error: aborting due to 3 previous errors

0 commit comments

Comments
 (0)