Skip to content

Commit 401560c

Browse files
committed
Probe for types that might be appropriate in a turbofish suggestion
When encountering a situation where extra type annotations are needed, we sometimes suggest using turbofish. When we do so, previously we used either the `fn`'s type parameter names or `_` as placeholders. Now, we probe for all types that implement the traits the type parameter is bounded by and provide a structured suggestion for them.
1 parent 4edf33e commit 401560c

File tree

6 files changed

+233
-51
lines changed

6 files changed

+233
-51
lines changed

compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
342342
span: Span,
343343
arg: GenericArg<'tcx>,
344344
error_code: TypeAnnotationNeeded,
345+
turbofish_suggestions: Vec<String>,
345346
) -> DiagnosticBuilder<'tcx> {
346347
let arg = self.resolve_vars_if_possible(&arg);
347348
let arg_data = self.extract_inference_diagnostics_data(arg, None);
@@ -527,7 +528,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
527528
// | this method call resolves to `std::option::Option<&T>`
528529
// |
529530
// = note: type must be known at this point
530-
self.annotate_method_call(segment, e, &mut err);
531+
self.annotate_method_call(segment, e, &mut err, turbofish_suggestions);
531532
}
532533
} else if let Some(pattern) = local_visitor.found_arg_pattern {
533534
// We don't want to show the default label for closures.
@@ -591,7 +592,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
591592
// | this method call resolves to `std::option::Option<&T>`
592593
// |
593594
// = note: type must be known at this point
594-
self.annotate_method_call(segment, e, &mut err);
595+
self.annotate_method_call(segment, e, &mut err, turbofish_suggestions);
595596
}
596597
}
597598
// Instead of the following:
@@ -641,29 +642,49 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
641642
segment: &hir::PathSegment<'_>,
642643
e: &Expr<'_>,
643644
err: &mut DiagnosticBuilder<'_>,
645+
turbofish_suggestions: Vec<String>,
644646
) {
645647
if let (Some(typeck_results), None) = (self.in_progress_typeck_results, &segment.args) {
646648
let borrow = typeck_results.borrow();
647649
if let Some((DefKind::AssocFn, did)) = borrow.type_dependent_def(e.hir_id) {
648650
let generics = self.tcx.generics_of(did);
649651
if !generics.params.is_empty() {
650-
err.span_suggestion_verbose(
651-
segment.ident.span.shrink_to_hi(),
652-
&format!(
653-
"consider specifying the type argument{} in the method call",
654-
pluralize!(generics.params.len()),
655-
),
656-
format!(
657-
"::<{}>",
658-
generics
659-
.params
660-
.iter()
661-
.map(|p| p.name.to_string())
662-
.collect::<Vec<String>>()
663-
.join(", ")
664-
),
665-
Applicability::HasPlaceholders,
652+
let msg = format!(
653+
"consider specifying the type argument{} in the method call",
654+
pluralize!(generics.params.len()),
666655
);
656+
if turbofish_suggestions.is_empty() {
657+
err.span_suggestion_verbose(
658+
segment.ident.span.shrink_to_hi(),
659+
&msg,
660+
format!(
661+
"::<{}>",
662+
generics
663+
.params
664+
.iter()
665+
.map(|p| p.name.to_string())
666+
.collect::<Vec<String>>()
667+
.join(", ")
668+
),
669+
Applicability::HasPlaceholders,
670+
);
671+
} else {
672+
if turbofish_suggestions.len() == 1 {
673+
err.span_suggestion_verbose(
674+
segment.ident.span.shrink_to_hi(),
675+
&msg,
676+
format!("::<{}>", turbofish_suggestions[0]),
677+
Applicability::MaybeIncorrect,
678+
);
679+
} else {
680+
err.span_suggestions(
681+
segment.ident.span.shrink_to_hi(),
682+
&msg,
683+
turbofish_suggestions.into_iter().map(|ty| format!("::<{}>", ty)),
684+
Applicability::MaybeIncorrect,
685+
);
686+
}
687+
}
667688
} else {
668689
let sig = self.tcx.fn_sig(did);
669690
let bound_output = sig.output();

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

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use crate::traits::query::normalize::AtExt as _;
3434
use on_unimplemented::InferCtxtExt as _;
3535
use suggestions::InferCtxtExt as _;
3636

37-
pub use rustc_infer::traits::error_reporting::*;
37+
pub use rustc_infer::traits::{self, error_reporting::*};
3838

3939
pub trait InferCtxtExt<'tcx> {
4040
fn report_fulfillment_errors(
@@ -1505,12 +1505,23 @@ impl<'a, 'tcx> InferCtxtPrivExt<'tcx> for InferCtxt<'a, 'tcx> {
15051505
// check upstream for type errors and don't add the obligations to
15061506
// begin with in those cases.
15071507
if self.tcx.lang_items().sized_trait() == Some(trait_ref.def_id()) {
1508-
self.emit_inference_failure_err(body_id, span, subst, ErrorCode::E0282).emit();
1508+
self.emit_inference_failure_err(body_id, span, subst, ErrorCode::E0282, vec![])
1509+
.emit();
15091510
return;
15101511
}
1511-
let mut err =
1512-
self.emit_inference_failure_err(body_id, span, subst, ErrorCode::E0283);
1512+
// Try to find possible types that would satisfy the bounds in the type param to
1513+
// give an appropriate turbofish suggestion.
1514+
let turbofish_suggestions =
1515+
self.get_turbofish_suggestions(obligation, data, self_ty);
1516+
let mut err = self.emit_inference_failure_err(
1517+
body_id,
1518+
span,
1519+
subst,
1520+
ErrorCode::E0283,
1521+
turbofish_suggestions.clone(),
1522+
);
15131523
err.note(&format!("cannot satisfy `{}`", predicate));
1524+
15141525
if let ObligationCauseCode::ItemObligation(def_id) = obligation.cause.code {
15151526
self.suggest_fully_qualified_path(&mut err, def_id, span, trait_ref.def_id());
15161527
} else if let (
@@ -1544,23 +1555,44 @@ impl<'a, 'tcx> InferCtxtPrivExt<'tcx> for InferCtxt<'a, 'tcx> {
15441555
// |
15451556
// = note: cannot satisfy `_: Tt`
15461557

1547-
err.span_suggestion_verbose(
1548-
span.shrink_to_hi(),
1549-
&format!(
1550-
"consider specifying the type argument{} in the function call",
1551-
pluralize!(generics.params.len()),
1552-
),
1553-
format!(
1554-
"::<{}>",
1555-
generics
1556-
.params
1557-
.iter()
1558-
.map(|p| p.name.to_string())
1559-
.collect::<Vec<String>>()
1560-
.join(", ")
1561-
),
1562-
Applicability::HasPlaceholders,
1558+
let msg = format!(
1559+
"consider specifying the type argument{} in the function call",
1560+
pluralize!(generics.params.len()),
15631561
);
1562+
if turbofish_suggestions.is_empty() {
1563+
err.span_suggestion_verbose(
1564+
span.shrink_to_hi(),
1565+
&msg,
1566+
format!(
1567+
"::<{}>",
1568+
generics
1569+
.params
1570+
.iter()
1571+
.map(|p| p.name.to_string())
1572+
.collect::<Vec<String>>()
1573+
.join(", ")
1574+
),
1575+
Applicability::HasPlaceholders,
1576+
);
1577+
} else {
1578+
if turbofish_suggestions.len() == 1 {
1579+
err.span_suggestion_verbose(
1580+
span.shrink_to_hi(),
1581+
&msg,
1582+
format!("::<{}>", turbofish_suggestions[0]),
1583+
Applicability::MaybeIncorrect,
1584+
);
1585+
} else {
1586+
err.span_suggestions(
1587+
span.shrink_to_hi(),
1588+
&msg,
1589+
turbofish_suggestions
1590+
.into_iter()
1591+
.map(|ty| format!("::<{}>", ty)),
1592+
Applicability::MaybeIncorrect,
1593+
);
1594+
}
1595+
}
15641596
}
15651597
}
15661598
err
@@ -1573,7 +1605,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'tcx> for InferCtxt<'a, 'tcx> {
15731605
return;
15741606
}
15751607

1576-
self.emit_inference_failure_err(body_id, span, arg, ErrorCode::E0282)
1608+
self.emit_inference_failure_err(body_id, span, arg, ErrorCode::E0282, vec![])
15771609
}
15781610

15791611
ty::PredicateAtom::Subtype(data) => {
@@ -1584,7 +1616,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'tcx> for InferCtxt<'a, 'tcx> {
15841616
let SubtypePredicate { a_is_expected: _, a, b } = data;
15851617
// both must be type variables, or the other would've been instantiated
15861618
assert!(a.is_ty_var() && b.is_ty_var());
1587-
self.emit_inference_failure_err(body_id, span, a.into(), ErrorCode::E0282)
1619+
self.emit_inference_failure_err(body_id, span, a.into(), ErrorCode::E0282, vec![])
15881620
}
15891621
ty::PredicateAtom::Projection(data) => {
15901622
let trait_ref = bound_predicate.rebind(data).to_poly_trait_ref(self.tcx);
@@ -1600,6 +1632,7 @@ impl<'a, 'tcx> InferCtxtPrivExt<'tcx> for InferCtxt<'a, 'tcx> {
16001632
span,
16011633
self_ty.into(),
16021634
ErrorCode::E0284,
1635+
vec![],
16031636
);
16041637
err.note(&format!("cannot satisfy `{}`", predicate));
16051638
err

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

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ use super::{
44
};
55

66
use crate::autoderef::Autoderef;
7-
use crate::infer::InferCtxt;
8-
use crate::traits::normalize_projection_type;
7+
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
8+
use crate::infer::{InferCtxt, InferOk};
9+
use crate::traits::{self, normalize_projection_type};
910

11+
use rustc_data_structures::fx::FxHashSet;
1012
use rustc_data_structures::stack::ensure_sufficient_stack;
1113
use rustc_errors::{error_code, struct_span_err, Applicability, DiagnosticBuilder, Style};
1214
use rustc_hir as hir;
@@ -15,6 +17,7 @@ use rustc_hir::def_id::DefId;
1517
use rustc_hir::intravisit::Visitor;
1618
use rustc_hir::lang_items::LangItem;
1719
use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Node};
20+
use rustc_middle::ty::subst::{GenericArgKind, Subst};
1821
use rustc_middle::ty::{
1922
self, suggest_constraining_type_param, AdtKind, DefIdTree, Infer, InferTy, ToPredicate, Ty,
2023
TyCtxt, TypeFoldable, WithConstness,
@@ -23,6 +26,7 @@ use rustc_middle::ty::{TypeAndMut, TypeckResults};
2326
use rustc_span::symbol::{kw, sym, Ident, Symbol};
2427
use rustc_span::{MultiSpan, Span, DUMMY_SP};
2528
use rustc_target::spec::abi;
29+
use std::cmp::Ordering;
2630
use std::fmt;
2731

2832
use super::InferCtxtPrivExt;
@@ -38,6 +42,13 @@ pub enum GeneratorInteriorOrUpvar {
3842

3943
// This trait is public to expose the diagnostics methods to clippy.
4044
pub trait InferCtxtExt<'tcx> {
45+
fn get_turbofish_suggestions(
46+
&self,
47+
obligation: &PredicateObligation<'tcx>,
48+
data: ty::TraitPredicate<'tcx>,
49+
self_ty: Ty<'tcx>,
50+
) -> Vec<String>;
51+
4152
fn suggest_restricting_param_bound(
4253
&self,
4354
err: &mut DiagnosticBuilder<'_>,
@@ -317,6 +328,100 @@ fn suggest_restriction(
317328
}
318329

319330
impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
331+
/// Try to find possible types that would satisfy the bounds in the type param to give an
332+
/// appropriate turbofish suggestion.
333+
fn get_turbofish_suggestions(
334+
&self,
335+
obligation: &PredicateObligation<'tcx>,
336+
data: ty::TraitPredicate<'tcx>,
337+
self_ty: Ty<'tcx>,
338+
) -> Vec<String> {
339+
let mut turbofish_suggestions = FxHashSet::default();
340+
self.tcx.for_each_relevant_impl(data.trait_ref.def_id, self_ty, |impl_def_id| {
341+
let param_env = ty::ParamEnv::empty();
342+
let param_env = param_env.subst(self.tcx, data.trait_ref.substs);
343+
let ty = self.next_ty_var(TypeVariableOrigin {
344+
kind: TypeVariableOriginKind::NormalizeProjectionType,
345+
span: DUMMY_SP,
346+
});
347+
348+
let impl_substs = self.fresh_substs_for_item(obligation.cause.span, impl_def_id);
349+
let trait_ref = self.tcx.impl_trait_ref(impl_def_id).unwrap();
350+
let trait_ref = trait_ref.subst(self.tcx, impl_substs);
351+
352+
// Require the type the impl is implemented on to match
353+
// our type, and ignore the impl if there was a mismatch.
354+
let cause = traits::ObligationCause::dummy();
355+
let eq_result = self.at(&cause, param_env).eq(trait_ref.self_ty(), ty);
356+
if let Ok(InferOk { value: (), obligations }) = eq_result {
357+
// FIXME: ignoring `obligations` might cause false positives.
358+
drop(obligations);
359+
360+
let can_impl = match self.evaluate_obligation(&Obligation::new(
361+
cause,
362+
obligation.param_env,
363+
trait_ref.without_const().to_predicate(self.tcx),
364+
)) {
365+
Ok(eval_result) => eval_result.may_apply(),
366+
Err(traits::OverflowError) => true, // overflow doesn't mean yes *or* no
367+
};
368+
if can_impl
369+
&& data.trait_ref.substs.iter().zip(trait_ref.substs.iter()).all(|(l, r)| {
370+
// FIXME: ideally we would use `can_coerce` here instead, but `typeck`
371+
// comes *after* in the dependency graph.
372+
match (l.unpack(), r.unpack()) {
373+
(GenericArgKind::Type(left_ty), GenericArgKind::Type(right_ty)) => {
374+
match (&left_ty.peel_refs().kind(), &right_ty.peel_refs().kind()) {
375+
(Infer(_), _) | (_, Infer(_)) => true,
376+
(left_kind, right_kind) => left_kind == right_kind,
377+
}
378+
}
379+
(GenericArgKind::Lifetime(_), GenericArgKind::Lifetime(_))
380+
| (GenericArgKind::Const(_), GenericArgKind::Const(_)) => true,
381+
_ => false,
382+
}
383+
})
384+
&& !matches!(trait_ref.self_ty().kind(), ty::Infer(_))
385+
{
386+
turbofish_suggestions.insert(trait_ref.self_ty());
387+
}
388+
}
389+
});
390+
// Sort types by always suggesting `Vec<_>` and `String` first, as they are the
391+
// most likely desired types.
392+
let mut turbofish_suggestions = turbofish_suggestions.into_iter().collect::<Vec<_>>();
393+
turbofish_suggestions.sort_by(|left, right| {
394+
let vec_type = self.tcx.get_diagnostic_item(sym::vec_type);
395+
let string_type = self.tcx.get_diagnostic_item(sym::string_type);
396+
match (&left.kind(), &right.kind()) {
397+
(
398+
ty::Adt(ty::AdtDef { did: left, .. }, _),
399+
ty::Adt(ty::AdtDef { did: right, .. }, _),
400+
) if left == right => Ordering::Equal,
401+
(
402+
ty::Adt(ty::AdtDef { did: left, .. }, _),
403+
ty::Adt(ty::AdtDef { did: right, .. }, _),
404+
) if Some(*left) == vec_type && Some(*right) == string_type => Ordering::Less,
405+
(
406+
ty::Adt(ty::AdtDef { did: left, .. }, _),
407+
ty::Adt(ty::AdtDef { did: right, .. }, _),
408+
) if Some(*right) == vec_type && Some(*left) == string_type => Ordering::Greater,
409+
(ty::Adt(ty::AdtDef { did, .. }, _), _)
410+
if Some(*did) == vec_type || Some(*did) == string_type =>
411+
{
412+
Ordering::Less
413+
}
414+
(_, ty::Adt(ty::AdtDef { did, .. }, _))
415+
if Some(*did) == vec_type || Some(*did) == string_type =>
416+
{
417+
Ordering::Greater
418+
}
419+
_ => left.cmp(right),
420+
}
421+
});
422+
turbofish_suggestions.into_iter().map(|ty| ty.to_string()).collect()
423+
}
424+
320425
fn suggest_restricting_param_bound(
321426
&self,
322427
mut err: &mut DiagnosticBuilder<'_>,

compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1396,7 +1396,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13961396
ty
13971397
} else {
13981398
if !self.is_tainted_by_errors() {
1399-
self.emit_inference_failure_err((**self).body_id, sp, ty.into(), E0282)
1399+
self.emit_inference_failure_err((**self).body_id, sp, ty.into(), E0282, vec![])
14001400
.note("type must be known at this point")
14011401
.emit();
14021402
}

compiler/rustc_typeck/src/check/writeback.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
656656
self.span.to_span(self.tcx),
657657
t.into(),
658658
E0282,
659+
vec![],
659660
)
660661
.emit();
661662
}
@@ -669,6 +670,7 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
669670
self.span.to_span(self.tcx),
670671
c.into(),
671672
E0282,
673+
vec![],
672674
)
673675
.emit();
674676
}

0 commit comments

Comments
 (0)