Skip to content

Commit 50f81d2

Browse files
committed
When annotations needed, look at impls for more accurate suggestions
When encountering an expression that needs type annotations, if we have the trait `DefId` we look for all the `impl`s that could be satisfied by the expression we have (without looking at additional obligations) and suggest the fully qualified to specific impls. For left over type parameters, we replace them with `_`. ``` error[E0283]: type annotations needed --> $DIR/E0283.rs:35:24 | LL | let bar = foo_impl.into() * 1u32; | ^^^^ | note: multiple `impl`s satisfying `Impl: Into<_>` found --> $DIR/E0283.rs:17:1 | LL | impl Into<u32> for Impl { | ^^^^^^^^^^^^^^^^^^^^^^^ = note: and another `impl` found in the `core` crate: - impl<T, U> Into<U> for T where U: From<T>; help: try using a fully qualified path to specify the expected types | LL | let bar = <_ as Into<_>>::into(foo_impl) * 1u32; | +++++++++++++++++++++ ~ help: try using a fully qualified path to specify the expected types | LL | let bar = <Impl as Into<u32>>::into(foo_impl) * 1u32; | ++++++++++++++++++++++++++ ~ ``` This approach does not account for blanket-impls, so we can end up with suggestions like `<_ as Into<_>>::into(foo)`. It'd be nice to have a more complete mechanism that does account for all obligations when resolving methods. Do not suggest `path::to<impl Trait for Type>::method` In the pretty-printer, we have a weird way to display fully-qualified for non-local impls, where we show a regular path, but the section corresponding to the `<Type as Trait>` we use non-syntax for it like `path::to<impl Trait for Type>`. It should be `<Type for path::to::Trait>`, but this is only better when we are printing code to be suggested, not to find where the `impl` actually is, so we add a new flag to the printer for this. Special case `Into` suggestion to look for `From` `impl`s When we encounter a blanket `<Ty as Into<Other>` `impl`, look at the `From` `impl`s so that we can suggest the appropriate `Other`: ``` error[E0284]: type annotations needed --> $DIR/issue-70082.rs:7:33 | LL | let y: f64 = 0.01f64 * 1i16.into(); | - ^^^^ | | | type must be known at this point | = note: cannot satisfy `<f64 as Mul<_>>::Output == f64` help: try using a fully qualified path to specify the expected types | LL | let y: f64 = 0.01f64 * <i16 as Into<i32>>::into(1i16); | +++++++++++++++++++++++++ ~ help: try using a fully qualified path to specify the expected types | LL | let y: f64 = 0.01f64 * <i16 as Into<i64>>::into(1i16); | +++++++++++++++++++++++++ ~ help: try using a fully qualified path to specify the expected types | LL | let y: f64 = 0.01f64 * <i16 as Into<i128>>::into(1i16); | ++++++++++++++++++++++++++ ~ help: try using a fully qualified path to specify the expected types | LL | let y: f64 = 0.01f64 * <i16 as Into<isize>>::into(1i16); | +++++++++++++++++++++++++++ ~ help: try using a fully qualified path to specify the expected types | LL | let y: f64 = 0.01f64 * <i16 as Into<f32>>::into(1i16); | +++++++++++++++++++++++++ ~ help: try using a fully qualified path to specify the expected types | LL | let y: f64 = 0.01f64 * <i16 as Into<f64>>::into(1i16); | +++++++++++++++++++++++++ ~ help: try using a fully qualified path to specify the expected types | LL | let y: f64 = 0.01f64 * <i16 as Into<AtomicI16>>::into(1i16); | +++++++++++++++++++++++++++++++ ~ ``` Suggest an appropriate type for a binding of method chain Do the same we do with fully-qualified type suggestions to the suggestion to specify a binding type: ``` error[E0282]: type annotations needed --> $DIR/slice-pattern-refutable.rs:14:9 | LL | let [a, b, c] = Zeroes.into() else { | ^^^^^^^^^ | help: consider giving this pattern a type | LL | let [a, b, c]: _ = Zeroes.into() else { | +++ help: consider giving this pattern a type | LL | let [a, b, c]: [usize; 3] = Zeroes.into() else { | ++++++++++++ ``` review comments - Pass `ParamEnv` through - Remove now-unnecessary `Formatter` mode - Rework the way we pick up the bounds Add naïve mechanism to filter `Into` suggestions involving math ops ``` error[E0284]: type annotations needed --> $DIR/issue-70082.rs:7:33 | LL | let y: f64 = 0.01f64 * 1i16.into(); | - ^^^^ | | | type must be known at this point | = note: cannot satisfy `<f64 as Mul<_>>::Output == f64` help: try using a fully qualified path to specify the expected types | LL | let y: f64 = 0.01f64 * <i16 as Into<f64>>::into(1i16); | +++++++++++++++++++++++++ ~ ``` Note that we only suggest `Into<f64>`, and not `Into<i32>`, `Into<i64>`, `Into<i128>`, `Into<isize>`, `Into<f32>` or `Into<AtomicI16>`. Replace `_` with `/* Type */` in let binding type suggestion Rework the `predicate` "trafficking" to be more targetted Rename `predicate` to `originating_projection`. Pass in only the `ProjectionPredicate` instead of the `Predicate` to avoid needing to destructure as much.
1 parent 6cf068d commit 50f81d2

32 files changed

+666
-112
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15481548
ty.into(),
15491549
TypeAnnotationNeeded::E0282,
15501550
true,
1551+
self.param_env,
1552+
None,
15511553
)
15521554
.emit()
15531555
});

compiler/rustc_hir_typeck/src/method/probe.rs

+2
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
435435
ty.into(),
436436
TypeAnnotationNeeded::E0282,
437437
!raw_ptr_call,
438+
self.param_env,
439+
None,
438440
);
439441
if raw_ptr_call {
440442
err.span_label(span, "cannot call a method on a raw pointer with an unknown pointee type");

compiler/rustc_hir_typeck/src/writeback.rs

+2
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,8 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
784784
p.into(),
785785
TypeAnnotationNeeded::E0282,
786786
false,
787+
self.fcx.param_env,
788+
None,
787789
)
788790
.emit()
789791
}

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ symbols! {
496496
bitxor,
497497
bitxor_assign,
498498
black_box,
499+
blanket_into_impl,
499500
block,
500501
bool,
501502
borrowck_graphviz_format,

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

+227-20
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::errors::{
2525
AmbiguousImpl, AmbiguousReturn, AnnotationRequired, InferenceBadError,
2626
SourceKindMultiSuggestion, SourceKindSubdiag,
2727
};
28-
use crate::infer::InferCtxt;
28+
use crate::infer::{InferCtxt, InferCtxtExt};
2929

3030
pub enum TypeAnnotationNeeded {
3131
/// ```compile_fail,E0282
@@ -74,6 +74,14 @@ pub enum UnderspecifiedArgKind {
7474
Const { is_parameter: bool },
7575
}
7676

77+
enum InferenceSuggestionFormat {
78+
/// The inference suggestion will the provided as the explicit type of a binding.
79+
BindingType,
80+
/// The inference suggestion will the provided in the same expression where the error occurred,
81+
/// expanding method calls into fully-qualified paths specifying the self-type and trait.
82+
FullyQualifiedMethodCall,
83+
}
84+
7785
impl InferenceDiagnosticsData {
7886
fn can_add_more_info(&self) -> bool {
7987
!(self.name == "_" && matches!(self.kind, UnderspecifiedArgKind::Type { .. }))
@@ -419,6 +427,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
419427
arg: GenericArg<'tcx>,
420428
error_code: TypeAnnotationNeeded,
421429
should_label_span: bool,
430+
param_env: ty::ParamEnv<'tcx>,
431+
originating_projection: Option<ty::ProjectionPredicate<'tcx>>,
422432
) -> Diag<'a> {
423433
let arg = self.resolve_vars_if_possible(arg);
424434
let arg_data = self.extract_inference_diagnostics_data(arg, None);
@@ -452,17 +462,56 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
452462
let mut infer_subdiags = Vec::new();
453463
let mut multi_suggestions = Vec::new();
454464
match kind {
455-
InferSourceKind::LetBinding { insert_span, pattern_name, ty, def_id } => {
456-
infer_subdiags.push(SourceKindSubdiag::LetLike {
457-
span: insert_span,
458-
name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new),
459-
x_kind: arg_data.where_x_is_kind(ty),
460-
prefix_kind: arg_data.kind.clone(),
461-
prefix: arg_data.kind.try_get_prefix().unwrap_or_default(),
462-
arg_name: arg_data.name,
463-
kind: if pattern_name.is_some() { "with_pattern" } else { "other" },
464-
type_name: ty_to_string(self, ty, def_id),
465-
});
465+
InferSourceKind::LetBinding {
466+
insert_span,
467+
pattern_name,
468+
ty,
469+
def_id,
470+
init_expr_hir_id,
471+
} => {
472+
let mut paths = vec![];
473+
if let Some(def_id) = def_id
474+
&& let Some(hir_id) = init_expr_hir_id
475+
&& let expr = self.infcx.tcx.hir().expect_expr(hir_id)
476+
&& let hir::ExprKind::MethodCall(_, rcvr, _, _) = expr.kind
477+
&& let Some(ty) = typeck_results.node_type_opt(rcvr.hir_id)
478+
{
479+
paths = self.get_fully_qualified_path_suggestions_from_impls(
480+
ty,
481+
def_id,
482+
InferenceSuggestionFormat::BindingType,
483+
param_env,
484+
originating_projection,
485+
);
486+
}
487+
488+
if paths.is_empty() {
489+
infer_subdiags.push(SourceKindSubdiag::LetLike {
490+
span: insert_span,
491+
name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new),
492+
x_kind: arg_data.where_x_is_kind(ty),
493+
prefix_kind: arg_data.kind.clone(),
494+
prefix: arg_data.kind.try_get_prefix().unwrap_or_default(),
495+
arg_name: arg_data.name,
496+
kind: if pattern_name.is_some() { "with_pattern" } else { "other" },
497+
type_name: ty_to_string(self, ty, def_id),
498+
});
499+
} else {
500+
for type_name in paths {
501+
infer_subdiags.push(SourceKindSubdiag::LetLike {
502+
span: insert_span,
503+
name: pattern_name
504+
.map(|name| name.to_string())
505+
.unwrap_or_else(String::new),
506+
x_kind: arg_data.where_x_is_kind(ty),
507+
prefix_kind: arg_data.kind.clone(),
508+
prefix: arg_data.kind.try_get_prefix().unwrap_or_default(),
509+
arg_name: arg_data.name.clone(),
510+
kind: if pattern_name.is_some() { "with_pattern" } else { "other" },
511+
type_name,
512+
});
513+
}
514+
}
466515
}
467516
InferSourceKind::ClosureArg { insert_span, ty } => {
468517
infer_subdiags.push(SourceKindSubdiag::LetLike {
@@ -558,12 +607,35 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
558607
_ => "",
559608
};
560609

561-
multi_suggestions.push(SourceKindMultiSuggestion::new_fully_qualified(
562-
receiver.span,
563-
def_path,
564-
adjustment,
565-
successor,
566-
));
610+
// Look for all the possible implementations to suggest, otherwise we'll show
611+
// just suggest the syntax for the fully qualified path with placeholders.
612+
let paths = self.get_fully_qualified_path_suggestions_from_impls(
613+
args.type_at(0),
614+
def_id,
615+
InferenceSuggestionFormat::FullyQualifiedMethodCall,
616+
param_env,
617+
originating_projection,
618+
);
619+
if paths.len() > 20 || paths.is_empty() {
620+
// This will show the fallback impl, so the expression will have type
621+
// parameter placeholders, but it's better than nothing.
622+
multi_suggestions.push(SourceKindMultiSuggestion::new_fully_qualified(
623+
receiver.span,
624+
def_path,
625+
adjustment,
626+
successor,
627+
));
628+
} else {
629+
// These are the paths to specific impls.
630+
for path in paths {
631+
multi_suggestions.push(SourceKindMultiSuggestion::new_fully_qualified(
632+
receiver.span,
633+
path,
634+
adjustment,
635+
successor,
636+
));
637+
}
638+
}
567639
}
568640
}
569641
InferSourceKind::ClosureReturn { ty, data, should_wrap_expr } => {
@@ -655,6 +727,136 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
655727
}
656728
false
657729
}
730+
731+
/// Given a `self_ty` and a trait item `def_id`, find all relevant `impl`s and provide suitable
732+
/// code for a suggestion.
733+
///
734+
/// If `suggestion_style` corresponds to a method call expression, then we suggest the
735+
/// fully-qualified path for the associated item.
736+
///
737+
/// If `suggestion_style` corresponds to a let binding, then we suggest a type suitable for it
738+
/// corresponding to the return type of the associated item.
739+
///
740+
/// If `originating_projection` corresponds to a math operation, we restrict the suggestions to
741+
/// only `impl`s for the same type that was expected (instead of showing every integer type,
742+
/// mention only the one that is most likely to be relevant).
743+
///
744+
/// `trait From` is treated specially, in order to look for suitable `Into` `impl`s as well.
745+
fn get_fully_qualified_path_suggestions_from_impls(
746+
&self,
747+
self_ty: Ty<'tcx>,
748+
def_id: DefId,
749+
suggestion_style: InferenceSuggestionFormat,
750+
param_env: ty::ParamEnv<'tcx>,
751+
originating_projection: Option<ty::ProjectionPredicate<'tcx>>,
752+
) -> Vec<String> {
753+
let tcx = self.infcx.tcx;
754+
let mut paths = vec![];
755+
let name = tcx.item_name(def_id);
756+
let trait_def_id = tcx.parent(def_id);
757+
tcx.for_each_relevant_impl(trait_def_id, self_ty, |impl_def_id| {
758+
let impl_args = self.fresh_args_for_item(DUMMY_SP, impl_def_id);
759+
let impl_trait_ref =
760+
tcx.impl_trait_ref(impl_def_id).unwrap().instantiate(tcx, impl_args);
761+
let impl_self_ty = impl_trait_ref.self_ty();
762+
if self.infcx.can_eq(param_env, impl_self_ty, self_ty) {
763+
// The expr's self type could conform to this impl's self type.
764+
} else {
765+
// Nope, don't bother.
766+
return;
767+
}
768+
769+
let filter = if let Some(ty::ProjectionPredicate {
770+
projection_term: ty::AliasTerm { def_id, .. },
771+
term,
772+
}) = originating_projection
773+
&& let ty::TermKind::Ty(assoc_ty) = term.unpack()
774+
&& tcx.item_name(def_id) == sym::Output
775+
&& hir::lang_items::BINARY_OPERATORS
776+
.iter()
777+
.map(|&op| tcx.lang_items().get(op))
778+
.any(|op| op == Some(tcx.parent(def_id)))
779+
{
780+
// If the predicate that failed to be inferred is an associated type called
781+
// "Output" (from one of the math traits), we will only mention the `Into` and
782+
// `From` impls that correspond to the self type as well, so as to avoid showing
783+
// multiple conversion options.
784+
Some(assoc_ty)
785+
} else {
786+
None
787+
};
788+
let assocs = tcx.associated_items(impl_def_id);
789+
790+
if tcx.is_diagnostic_item(sym::blanket_into_impl, impl_def_id)
791+
&& let Some(did) = tcx.get_diagnostic_item(sym::From)
792+
{
793+
let mut found = false;
794+
tcx.for_each_impl(did, |impl_def_id| {
795+
// We had an `<A as Into<B>::into` and we've hit the blanket
796+
// impl for `From<A>`. So we try and look for the right `From`
797+
// impls that *would* apply. We *could* do this in a generalized
798+
// version by evaluating the `where` clauses, but that would be
799+
// way too involved to implement. Instead we special case the
800+
// arguably most common case of `expr.into()`.
801+
let Some(header) = tcx.impl_trait_header(impl_def_id) else {
802+
return;
803+
};
804+
let target = header.trait_ref.skip_binder().args.type_at(0);
805+
if filter.is_some() && filter != Some(target) {
806+
return;
807+
};
808+
let target = header.trait_ref.skip_binder().args.type_at(0);
809+
let ty = header.trait_ref.skip_binder().args.type_at(1);
810+
if ty == self_ty {
811+
match suggestion_style {
812+
InferenceSuggestionFormat::BindingType => {
813+
paths.push(if let ty::Infer(_) = target.kind() {
814+
"/* Type */".to_string()
815+
} else {
816+
format!("{target}")
817+
});
818+
}
819+
InferenceSuggestionFormat::FullyQualifiedMethodCall => {
820+
paths.push(format!("<{self_ty} as Into<{target}>>::into"));
821+
}
822+
}
823+
found = true;
824+
}
825+
});
826+
if found {
827+
return;
828+
}
829+
}
830+
831+
// We're at the `impl` level, but we want to get the same method we
832+
// called *on this `impl`*, in order to get the right DefId and args.
833+
let Some(assoc) = assocs.filter_by_name_unhygienic(name).next() else {
834+
// The method isn't in this `impl`? Not useful to us then.
835+
return;
836+
};
837+
let Some(trait_assoc_item) = assoc.trait_item_def_id else {
838+
return;
839+
};
840+
let args = impl_trait_ref
841+
.args
842+
.extend_to(tcx, trait_assoc_item, |def, _| self.var_for_def(DUMMY_SP, def));
843+
match suggestion_style {
844+
InferenceSuggestionFormat::BindingType => {
845+
let fn_sig = tcx.fn_sig(def_id).instantiate(tcx, args);
846+
let ret = fn_sig.skip_binder().output();
847+
paths.push(if let ty::Infer(_) = ret.kind() {
848+
"/* Type */".to_string()
849+
} else {
850+
format!("{ret}")
851+
});
852+
}
853+
InferenceSuggestionFormat::FullyQualifiedMethodCall => {
854+
paths.push(self.tcx.value_path_str_with_args(def_id, args));
855+
}
856+
}
857+
});
858+
paths
859+
}
658860
}
659861

660862
#[derive(Debug)]
@@ -670,6 +872,7 @@ enum InferSourceKind<'tcx> {
670872
pattern_name: Option<Ident>,
671873
ty: Ty<'tcx>,
672874
def_id: Option<DefId>,
875+
init_expr_hir_id: Option<HirId>,
673876
},
674877
ClosureArg {
675878
insert_span: Span,
@@ -855,8 +1058,11 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
8551058
let cost = self.source_cost(&new_source) + self.attempt;
8561059
debug!(?cost);
8571060
self.attempt += 1;
858-
if let Some(InferSource { kind: InferSourceKind::GenericArg { def_id: did, .. }, .. }) =
859-
self.infer_source
1061+
if let Some(InferSource { kind: InferSourceKind::GenericArg { def_id: did, .. }, .. })
1062+
| Some(InferSource {
1063+
kind: InferSourceKind::FullyQualifiedMethodCall { def_id: did, .. },
1064+
..
1065+
}) = self.infer_source
8601066
&& let InferSourceKind::LetBinding { ref ty, ref mut def_id, .. } = new_source.kind
8611067
&& ty.is_ty_or_numeric_infer()
8621068
{
@@ -1165,6 +1371,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
11651371
pattern_name: local.pat.simple_ident(),
11661372
ty,
11671373
def_id: None,
1374+
init_expr_hir_id: local.init.map(|e| e.hir_id),
11681375
},
11691376
})
11701377
}

0 commit comments

Comments
 (0)