Skip to content

Commit 2ca387e

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 e8c698b commit 2ca387e

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
@@ -1519,6 +1519,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15191519
ty.into(),
15201520
TypeAnnotationNeeded::E0282,
15211521
true,
1522+
self.param_env,
1523+
None,
15221524
)
15231525
.emit()
15241526
});

compiler/rustc_hir_typeck/src/method/probe.rs

+2
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
437437
ty.into(),
438438
TypeAnnotationNeeded::E0282,
439439
!raw_ptr_call,
440+
self.param_env,
441+
None,
440442
);
441443
if raw_ptr_call {
442444
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
@@ -782,6 +782,8 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
782782
p.into(),
783783
TypeAnnotationNeeded::E0282,
784784
false,
785+
self.fcx.param_env,
786+
None,
785787
)
786788
.emit()
787789
}

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ symbols! {
497497
bitxor,
498498
bitxor_assign,
499499
black_box,
500+
blanket_into_impl,
500501
block,
501502
bool,
502503
bool_then,

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

+227-20
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::errors::{
2626
AmbiguousImpl, AmbiguousReturn, AnnotationRequired, InferenceBadError,
2727
SourceKindMultiSuggestion, SourceKindSubdiag,
2828
};
29-
use crate::infer::InferCtxt;
29+
use crate::infer::{InferCtxt, InferCtxtExt};
3030

3131
pub enum TypeAnnotationNeeded {
3232
/// ```compile_fail,E0282
@@ -75,6 +75,14 @@ pub enum UnderspecifiedArgKind {
7575
Const { is_parameter: bool },
7676
}
7777

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

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

658860
#[derive(Debug)]
@@ -668,6 +870,7 @@ enum InferSourceKind<'tcx> {
668870
pattern_name: Option<Ident>,
669871
ty: Ty<'tcx>,
670872
def_id: Option<DefId>,
873+
init_expr_hir_id: Option<HirId>,
671874
},
672875
ClosureArg {
673876
insert_span: Span,
@@ -853,8 +1056,11 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
8531056
let cost = self.source_cost(&new_source) + self.attempt;
8541057
debug!(?cost);
8551058
self.attempt += 1;
856-
if let Some(InferSource { kind: InferSourceKind::GenericArg { def_id: did, .. }, .. }) =
857-
self.infer_source
1059+
if let Some(InferSource { kind: InferSourceKind::GenericArg { def_id: did, .. }, .. })
1060+
| Some(InferSource {
1061+
kind: InferSourceKind::FullyQualifiedMethodCall { def_id: did, .. },
1062+
..
1063+
}) = self.infer_source
8581064
&& let InferSourceKind::LetBinding { ref ty, ref mut def_id, .. } = new_source.kind
8591065
&& ty.is_ty_or_numeric_infer()
8601066
{
@@ -1163,6 +1369,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
11631369
pattern_name: local.pat.simple_ident(),
11641370
ty,
11651371
def_id: None,
1372+
init_expr_hir_id: local.init.map(|e| e.hir_id),
11661373
},
11671374
})
11681375
}

0 commit comments

Comments
 (0)