Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide appropriate types in turbofish suggestions #76043

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_error_codes/src/error_codes/E0282.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ The compiler could not infer a type and asked for a type annotation.
Erroneous code example:

```compile_fail,E0282
let x = "hello".chars().rev().collect();
let x = None;
```

This error indicates that type inference did not result in one unique possible
Expand Down
174 changes: 120 additions & 54 deletions compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ fn closure_args(fn_sig: &ty::PolyFnSig<'_>) -> String {

pub enum TypeAnnotationNeeded {
/// ```compile_fail,E0282
/// let x = "hello".chars().rev().collect();
/// let _ = None;
/// ```
E0282,
/// An implementation cannot be chosen unambiguously because of lack of information.
Expand Down Expand Up @@ -342,6 +342,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
span: Span,
arg: GenericArg<'tcx>,
error_code: TypeAnnotationNeeded,
turbofish_suggestions: &[String],
) -> DiagnosticBuilder<'tcx> {
let arg = self.resolve_vars_if_possible(&arg);
let arg_data = self.extract_inference_diagnostics_data(arg, None);
Expand All @@ -365,12 +366,21 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
None
};
printer.name_resolver = Some(Box::new(&getter));
let _ = if let ty::FnDef(..) = ty.kind() {
let _ = match ty.kind() {
// We don't want the regular output for `fn`s because it includes its path in
// invalid pseudo-syntax, we want the `fn`-pointer output instead.
ty.fn_sig(self.tcx).print(printer)
} else {
ty.print(printer)
ty::FnDef(..) => ty.fn_sig(self.tcx).print(printer),
ty::Ref(_, ref_ty, mutability) => {
match ref_ty.kind() {
ty::Array(ty, _) => {
// Do not suggest `&[_; 0]`, instead suggest `&[_]`.
let _ = ty.print(printer);
return format!("&{}[{}]", mutability.prefix_str(), s);
}
_ => ty.print(printer),
}
}
_ => ty.print(printer),
};
s
};
Expand Down Expand Up @@ -448,7 +458,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
error_code,
);

let suffix = match local_visitor.found_node_ty {
let (suffix, sugg_ty) = match local_visitor.found_node_ty {
Some(ty) if ty.is_closure() => {
let substs =
if let ty::Closure(_, substs) = *ty.kind() { substs } else { unreachable!() };
Expand Down Expand Up @@ -483,20 +493,33 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
// This suggestion is incomplete, as the user will get further type inference
// errors due to the `_` placeholders and the introduction of `Box`, but it does
// nudge them in the right direction.
format!("a boxed closure type like `Box<dyn Fn({}) -> {}>`", args, ret)
(
format!("a boxed closure type like `Box<dyn Fn({}) -> {}>`", args, ret),
format!("Box<dyn Fn({}) -> {}>", args, ret),
)
}
Some(ty) if is_named_and_not_impl_trait(ty) && arg_data.name == "_" => {
let ty = ty_to_string(ty);
format!("the explicit type `{}`, with the type parameters specified", ty)
(
format!("the explicit type `{}`, with the type parameters specified", ty),
ty.to_string(),
)
}
Some(ty) if is_named_and_not_impl_trait(ty) && ty.to_string() != arg_data.name => {
let ty = ty_to_string(ty);
format!(
"the explicit type `{}`, where the type parameter `{}` is specified",
ty, arg_data.name,
(
format!(
"the explicit type `{}`, where the type parameter `{}` is specified",
ty, arg_data.name,
),
ty.to_string(),
)
}
_ => "a type".to_string(),
_ if turbofish_suggestions.len() == 1 => (
format!("the explicit type `{}`", turbofish_suggestions[0]),
turbofish_suggestions[0].clone(),
),
_ => ("a type".to_string(), "Type".to_string()),
};

if let Some(e) = local_visitor.found_exact_method_call {
Expand Down Expand Up @@ -527,7 +550,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
// | this method call resolves to `std::option::Option<&T>`
// |
// = note: type must be known at this point
self.annotate_method_call(segment, e, &mut err);
self.annotate_method_call(segment, e, &mut err, turbofish_suggestions);
}
} else if let Some(pattern) = local_visitor.found_arg_pattern {
// We don't want to show the default label for closures.
Expand All @@ -551,18 +574,49 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
format!("consider giving this closure parameter {}", suffix),
);
} else if let Some(pattern) = local_visitor.found_local_pattern {
let msg = if let Some(simple_ident) = pattern.simple_ident() {
match pattern.span.desugaring_kind() {
None => format!("consider giving `{}` {}", simple_ident, suffix),
Some(DesugaringKind::ForLoop(_)) => {
"the element type for this iterator is not specified".to_string()
}
_ => format!("this needs {}", suffix),
}
if let (hir::Node::Local(local), None) = (
self.tcx.hir().get(self.tcx.hir().get_parent_node(pattern.hir_id)),
pattern.span.desugaring_kind(),
) {
let (span, prefix) = match local.ty {
Some(ty) => (ty.span, ""),
None => (local.pat.span.shrink_to_hi(), ": "),
};
let msg = format!("consider giving this binding {}", suffix);
match &turbofish_suggestions[..] {
[] => err.span_suggestion_verbose(
span,
&msg,
format!("{}{}", prefix, sugg_ty),
Applicability::HasPlaceholders,
),
[ty] => err.span_suggestion_verbose(
span,
&msg,
format!("{}{}", prefix, ty),
Applicability::MachineApplicable,
),
_ => err.span_suggestions(
span,
&msg,
turbofish_suggestions.into_iter().map(|ty| format!("{}{}", prefix, ty)),
Applicability::MaybeIncorrect,
),
};
} else {
format!("consider giving this pattern {}", suffix)
};
err.span_label(pattern.span, msg);
let msg = if let Some(simple_ident) = pattern.simple_ident() {
match pattern.span.desugaring_kind() {
None => format!("consider giving `{}` {}", simple_ident, suffix),
Some(DesugaringKind::ForLoop(_)) => {
"the element type for this iterator is not specified".to_string()
}
_ => format!("this needs {}", suffix),
}
} else {
format!("consider giving this pattern {}", suffix)
};
err.span_label(pattern.span, msg);
}
} else if let Some(e) = local_visitor.found_method_call {
if let ExprKind::MethodCall(segment, ..) = &e.kind {
// Suggest specifying type params or point out the return type of the call:
Expand Down Expand Up @@ -591,7 +645,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
// | this method call resolves to `std::option::Option<&T>`
// |
// = note: type must be known at this point
self.annotate_method_call(segment, e, &mut err);
self.annotate_method_call(segment, e, &mut err, turbofish_suggestions);
}
}
// Instead of the following:
Expand Down Expand Up @@ -641,29 +695,49 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
segment: &hir::PathSegment<'_>,
e: &Expr<'_>,
err: &mut DiagnosticBuilder<'_>,
turbofish_suggestions: &[String],
) {
if let (Some(typeck_results), None) = (self.in_progress_typeck_results, &segment.args) {
let borrow = typeck_results.borrow();
if let Some((DefKind::AssocFn, did)) = borrow.type_dependent_def(e.hir_id) {
let generics = self.tcx.generics_of(did);
if !generics.params.is_empty() {
err.span_suggestion_verbose(
segment.ident.span.shrink_to_hi(),
&format!(
"consider specifying the type argument{} in the method call",
pluralize!(generics.params.len()),
),
format!(
"::<{}>",
generics
.params
.iter()
.map(|p| p.name.to_string())
.collect::<Vec<String>>()
.join(", ")
),
Applicability::HasPlaceholders,
let msg = format!(
"consider specifying the type argument{} in the method call",
pluralize!(generics.params.len()),
);
if turbofish_suggestions.is_empty() {
err.span_suggestion_verbose(
segment.ident.span.shrink_to_hi(),
&msg,
format!(
"::<{}>",
generics
.params
.iter()
.map(|p| p.name.to_string())
.collect::<Vec<String>>()
.join(", ")
),
Applicability::HasPlaceholders,
);
} else {
if turbofish_suggestions.len() == 1 {
err.span_suggestion_verbose(
segment.ident.span.shrink_to_hi(),
&msg,
format!("::<{}>", turbofish_suggestions[0]),
Applicability::MaybeIncorrect,
);
} else {
err.span_suggestions(
segment.ident.span.shrink_to_hi(),
&msg,
turbofish_suggestions.into_iter().map(|ty| format!("::<{}>", ty)),
Applicability::MaybeIncorrect,
);
}
}
} else {
let sig = self.tcx.fn_sig(did);
let bound_output = sig.output();
Expand Down Expand Up @@ -718,18 +792,9 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
) -> String {
if type_name == "_" {
format!("cannot infer {}", kind_str)
} else {
let parent_desc = if let Some(parent_name) = parent_name {
let parent_type_descr = if let Some(parent_descr) = parent_descr {
format!(" the {}", parent_descr)
} else {
"".into()
};

format!(" declared on{} `{}`", parent_type_descr, parent_name)
} else {
"".to_string()
};
} else if let (Some(parent_name), Some(parent_descr)) = (parent_name, parent_descr) {
let parent_type_descr = format!(" the {}", parent_descr);
let parent_desc = format!(" declared on{} `{}`", parent_type_descr, parent_name);

// FIXME: We really shouldn't be dealing with strings here
// but instead use a sensible enum for cases like this.
Expand All @@ -739,7 +804,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
"cannot infer {} {} {} `{}`{}",
kind_str, preposition, descr, type_name, parent_desc
)
.into()
} else {
format!("cannot infer type for {} `{}`", descr, type_name)
}
}
}
Loading