Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit adc3ea6

Browse files
committedAug 20, 2024
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 4d5b3b1 commit adc3ea6

32 files changed

+652
-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
@@ -495,6 +495,7 @@ symbols! {
495495
bitxor,
496496
bitxor_assign,
497497
black_box,
498+
blanket_into_impl,
498499
block,
499500
bool,
500501
borrowck_graphviz_format,

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

+213-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 { .. }))
@@ -418,6 +426,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
418426
arg: GenericArg<'tcx>,
419427
error_code: TypeAnnotationNeeded,
420428
should_label_span: bool,
429+
param_env: ty::ParamEnv<'tcx>,
430+
originating_projection: Option<ty::ProjectionPredicate<'tcx>>,
421431
) -> Diag<'a> {
422432
let arg = self.resolve_vars_if_possible(arg);
423433
let arg_data = self.extract_inference_diagnostics_data(arg, None);
@@ -451,17 +461,56 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
451461
let mut infer_subdiags = Vec::new();
452462
let mut multi_suggestions = Vec::new();
453463
match kind {
454-
InferSourceKind::LetBinding { insert_span, pattern_name, ty, def_id } => {
455-
infer_subdiags.push(SourceKindSubdiag::LetLike {
456-
span: insert_span,
457-
name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new),
458-
x_kind: arg_data.where_x_is_kind(ty),
459-
prefix_kind: arg_data.kind.clone(),
460-
prefix: arg_data.kind.try_get_prefix().unwrap_or_default(),
461-
arg_name: arg_data.name,
462-
kind: if pattern_name.is_some() { "with_pattern" } else { "other" },
463-
type_name: ty_to_string(self, ty, def_id),
464-
});
464+
InferSourceKind::LetBinding {
465+
insert_span,
466+
pattern_name,
467+
ty,
468+
def_id,
469+
init_expr_hir_id,
470+
} => {
471+
let mut paths = vec![];
472+
if let Some(def_id) = def_id
473+
&& let Some(hir_id) = init_expr_hir_id
474+
&& let expr = self.infcx.tcx.hir().expect_expr(hir_id)
475+
&& let hir::ExprKind::MethodCall(_, rcvr, _, _) = expr.kind
476+
&& let Some(ty) = typeck_results.node_type_opt(rcvr.hir_id)
477+
{
478+
paths = self.get_fully_qualified_path_suggestions_from_impls(
479+
ty,
480+
def_id,
481+
InferenceSuggestionFormat::BindingType,
482+
param_env,
483+
originating_projection,
484+
);
485+
}
486+
487+
if paths.is_empty() {
488+
infer_subdiags.push(SourceKindSubdiag::LetLike {
489+
span: insert_span,
490+
name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new),
491+
x_kind: arg_data.where_x_is_kind(ty),
492+
prefix_kind: arg_data.kind.clone(),
493+
prefix: arg_data.kind.try_get_prefix().unwrap_or_default(),
494+
arg_name: arg_data.name,
495+
kind: if pattern_name.is_some() { "with_pattern" } else { "other" },
496+
type_name: ty_to_string(self, ty, def_id),
497+
});
498+
} else {
499+
for type_name in paths {
500+
infer_subdiags.push(SourceKindSubdiag::LetLike {
501+
span: insert_span,
502+
name: pattern_name
503+
.map(|name| name.to_string())
504+
.unwrap_or_else(String::new),
505+
x_kind: arg_data.where_x_is_kind(ty),
506+
prefix_kind: arg_data.kind.clone(),
507+
prefix: arg_data.kind.try_get_prefix().unwrap_or_default(),
508+
arg_name: arg_data.name.clone(),
509+
kind: if pattern_name.is_some() { "with_pattern" } else { "other" },
510+
type_name,
511+
});
512+
}
513+
}
465514
}
466515
InferSourceKind::ClosureArg { insert_span, ty } => {
467516
infer_subdiags.push(SourceKindSubdiag::LetLike {
@@ -557,12 +606,35 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
557606
_ => "",
558607
};
559608

560-
multi_suggestions.push(SourceKindMultiSuggestion::new_fully_qualified(
561-
receiver.span,
562-
def_path,
563-
adjustment,
564-
successor,
565-
));
609+
// Look for all the possible implementations to suggest, otherwise we'll show
610+
// just suggest the syntax for the fully qualified path with placeholders.
611+
let paths = self.get_fully_qualified_path_suggestions_from_impls(
612+
args.type_at(0),
613+
def_id,
614+
InferenceSuggestionFormat::FullyQualifiedMethodCall,
615+
param_env,
616+
originating_projection,
617+
);
618+
if paths.len() > 20 || paths.is_empty() {
619+
// This will show the fallback impl, so the expression will have type
620+
// parameter placeholders, but it's better than nothing.
621+
multi_suggestions.push(SourceKindMultiSuggestion::new_fully_qualified(
622+
receiver.span,
623+
def_path,
624+
adjustment,
625+
successor,
626+
));
627+
} else {
628+
// These are the paths to specific impls.
629+
for path in paths {
630+
multi_suggestions.push(SourceKindMultiSuggestion::new_fully_qualified(
631+
receiver.span,
632+
path,
633+
adjustment,
634+
successor,
635+
));
636+
}
637+
}
566638
}
567639
}
568640
InferSourceKind::ClosureReturn { ty, data, should_wrap_expr } => {
@@ -613,6 +685,122 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
613685
}),
614686
}
615687
}
688+
689+
fn get_fully_qualified_path_suggestions_from_impls(
690+
&self,
691+
self_ty: Ty<'tcx>,
692+
def_id: DefId,
693+
target_type: InferenceSuggestionFormat,
694+
param_env: ty::ParamEnv<'tcx>,
695+
originating_projection: Option<ty::ProjectionPredicate<'tcx>>,
696+
) -> Vec<String> {
697+
let tcx = self.infcx.tcx;
698+
let mut paths = vec![];
699+
let name = tcx.item_name(def_id);
700+
let trait_def_id = tcx.parent(def_id);
701+
tcx.for_each_relevant_impl(trait_def_id, self_ty, |impl_def_id| {
702+
let impl_args = self.fresh_args_for_item(DUMMY_SP, impl_def_id);
703+
let impl_trait_ref =
704+
tcx.impl_trait_ref(impl_def_id).unwrap().instantiate(tcx, impl_args);
705+
let impl_self_ty = impl_trait_ref.self_ty();
706+
if self.infcx.can_eq(param_env, impl_self_ty, self_ty) {
707+
// The expr's self type could conform to this impl's self type.
708+
} else {
709+
// Nope, don't bother.
710+
return;
711+
}
712+
713+
let filter = if let Some(ty::ProjectionPredicate {
714+
projection_term: ty::AliasTerm { def_id, .. },
715+
term,
716+
}) = originating_projection
717+
&& let ty::TermKind::Ty(assoc_ty) = term.unpack()
718+
&& tcx.item_name(def_id) == sym::Output
719+
&& hir::lang_items::BINARY_OPERATORS
720+
.iter()
721+
.map(|&op| tcx.lang_items().get(op))
722+
.any(|op| op == Some(tcx.parent(def_id)))
723+
{
724+
// If the predicate that failed to be inferred is an associated type called
725+
// "Output" (from one of the math traits), we will only mention the `Into` and
726+
// `From` impls that correspond to the self type as well, so as to avoid showing
727+
// multiple conversion options.
728+
Some(assoc_ty)
729+
} else {
730+
None
731+
};
732+
let assocs = tcx.associated_items(impl_def_id);
733+
734+
if tcx.is_diagnostic_item(sym::blanket_into_impl, impl_def_id)
735+
&& let Some(did) = tcx.get_diagnostic_item(sym::From)
736+
{
737+
let mut found = false;
738+
tcx.for_each_impl(did, |impl_def_id| {
739+
// We had an `<A as Into<B>::into` and we've hit the blanket
740+
// impl for `From<A>`. So we try and look for the right `From`
741+
// impls that *would* apply. We *could* do this in a generalized
742+
// version by evaluating the `where` clauses, but that would be
743+
// way too involved to implement. Instead we special case the
744+
// arguably most common case of `expr.into()`.
745+
let Some(header) = tcx.impl_trait_header(impl_def_id) else {
746+
return;
747+
};
748+
let target = header.trait_ref.skip_binder().args.type_at(0);
749+
if filter.is_some() && filter != Some(target) {
750+
return;
751+
};
752+
let target = header.trait_ref.skip_binder().args.type_at(0);
753+
let ty = header.trait_ref.skip_binder().args.type_at(1);
754+
if ty == self_ty {
755+
match target_type {
756+
InferenceSuggestionFormat::BindingType => {
757+
paths.push(if let ty::Infer(_) = target.kind() {
758+
"/* Type */".to_string()
759+
} else {
760+
format!("{target}")
761+
});
762+
}
763+
InferenceSuggestionFormat::FullyQualifiedMethodCall => {
764+
paths.push(format!("<{self_ty} as Into<{target}>>::into"));
765+
}
766+
}
767+
found = true;
768+
}
769+
});
770+
if found {
771+
return;
772+
}
773+
}
774+
775+
// We're at the `impl` level, but we want to get the same method we
776+
// called *on this `impl`*, in order to get the right DefId and args.
777+
let Some(assoc) = assocs.filter_by_name_unhygienic(name).next() else {
778+
// The method isn't in this `impl`? Not useful to us then.
779+
return;
780+
};
781+
let Some(trait_assoc_item) = assoc.trait_item_def_id else {
782+
return;
783+
};
784+
let args = impl_trait_ref
785+
.args
786+
.extend_to(tcx, trait_assoc_item, |def, _| self.var_for_def(DUMMY_SP, def));
787+
match target_type {
788+
InferenceSuggestionFormat::BindingType => {
789+
let fn_sig = tcx.fn_sig(def_id).instantiate(tcx, args);
790+
let ret = fn_sig.skip_binder().output();
791+
paths.push(if let ty::Infer(_) = ret.kind() {
792+
"/* Type */".to_string()
793+
} else {
794+
format!("{ret}")
795+
});
796+
}
797+
InferenceSuggestionFormat::FullyQualifiedMethodCall => {
798+
paths.push(self.tcx.value_path_str_with_args(def_id, args));
799+
}
800+
}
801+
});
802+
paths
803+
}
616804
}
617805

618806
#[derive(Debug)]
@@ -628,6 +816,7 @@ enum InferSourceKind<'tcx> {
628816
pattern_name: Option<Ident>,
629817
ty: Ty<'tcx>,
630818
def_id: Option<DefId>,
819+
init_expr_hir_id: Option<HirId>,
631820
},
632821
ClosureArg {
633822
insert_span: Span,
@@ -813,8 +1002,11 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
8131002
let cost = self.source_cost(&new_source) + self.attempt;
8141003
debug!(?cost);
8151004
self.attempt += 1;
816-
if let Some(InferSource { kind: InferSourceKind::GenericArg { def_id: did, .. }, .. }) =
817-
self.infer_source
1005+
if let Some(InferSource { kind: InferSourceKind::GenericArg { def_id: did, .. }, .. })
1006+
| Some(InferSource {
1007+
kind: InferSourceKind::FullyQualifiedMethodCall { def_id: did, .. },
1008+
..
1009+
}) = self.infer_source
8181010
&& let InferSourceKind::LetBinding { ref ty, ref mut def_id, .. } = new_source.kind
8191011
&& ty.is_ty_or_numeric_infer()
8201012
{
@@ -1123,6 +1315,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
11231315
pattern_name: local.pat.simple_ident(),
11241316
ty,
11251317
def_id: None,
1318+
init_expr_hir_id: local.init.map(|e| e.hir_id),
11261319
},
11271320
})
11281321
}

‎compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs

+14
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
208208
trait_ref.self_ty().skip_binder().into(),
209209
TypeAnnotationNeeded::E0282,
210210
false,
211+
obligation.param_env,
212+
None,
211213
);
212214
return err.stash(span, StashKey::MaybeForgetReturn).unwrap();
213215
}
@@ -237,6 +239,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
237239
arg,
238240
TypeAnnotationNeeded::E0283,
239241
true,
242+
obligation.param_env,
243+
None,
240244
)
241245
} else {
242246
struct_span_code_err!(
@@ -477,6 +481,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
477481
arg,
478482
TypeAnnotationNeeded::E0282,
479483
false,
484+
obligation.param_env,
485+
None,
480486
)
481487
}
482488

@@ -496,6 +502,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
496502
a.into(),
497503
TypeAnnotationNeeded::E0282,
498504
true,
505+
obligation.param_env,
506+
None,
499507
)
500508
}
501509
ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) => {
@@ -526,6 +534,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
526534
arg,
527535
TypeAnnotationNeeded::E0284,
528536
true,
537+
obligation.param_env,
538+
Some(data),
529539
)
530540
.with_note(format!("cannot satisfy `{predicate}`"))
531541
} else {
@@ -556,6 +566,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
556566
arg,
557567
TypeAnnotationNeeded::E0284,
558568
true,
569+
obligation.param_env,
570+
None,
559571
);
560572
err
561573
} else {
@@ -578,6 +590,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
578590
ct.into(),
579591
TypeAnnotationNeeded::E0284,
580592
true,
593+
obligation.param_env,
594+
None,
581595
),
582596
ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })
583597
if term.is_infer() =>
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.