blob: 9947dea2340964b2c8847a1530c96c5b4717e44d [file] [log] [blame]
use crate::hir::map::Map;
use crate::infer::type_variable::TypeVariableOriginKind;
use crate::infer::InferCtxt;
use crate::ty::print::Print;
use crate::ty::{self, DefIdTree, Infer, Ty, TyVar};
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Namespace};
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{Body, Expr, ExprKind, FunctionRetTy, HirId, Local, Pat};
use rustc_span::source_map::DesugaringKind;
use rustc_span::symbol::kw;
use rustc_span::Span;
use std::borrow::Cow;
struct FindLocalByTypeVisitor<'a, 'tcx> {
infcx: &'a InferCtxt<'a, 'tcx>,
target_ty: Ty<'tcx>,
hir_map: &'a Map<'tcx>,
found_local_pattern: Option<&'tcx Pat<'tcx>>,
found_arg_pattern: Option<&'tcx Pat<'tcx>>,
found_ty: Option<Ty<'tcx>>,
found_closure: Option<&'tcx ExprKind<'tcx>>,
found_method_call: Option<&'tcx Expr<'tcx>>,
}
impl<'a, 'tcx> FindLocalByTypeVisitor<'a, 'tcx> {
fn new(infcx: &'a InferCtxt<'a, 'tcx>, target_ty: Ty<'tcx>, hir_map: &'a Map<'tcx>) -> Self {
Self {
infcx,
target_ty,
hir_map,
found_local_pattern: None,
found_arg_pattern: None,
found_ty: None,
found_closure: None,
found_method_call: None,
}
}
fn node_matches_type(&mut self, hir_id: HirId) -> Option<Ty<'tcx>> {
let ty_opt =
self.infcx.in_progress_tables.and_then(|tables| tables.borrow().node_type_opt(hir_id));
match ty_opt {
Some(ty) => {
let ty = self.infcx.resolve_vars_if_possible(&ty);
if ty.walk().any(|inner_ty| {
inner_ty == self.target_ty
|| match (&inner_ty.kind, &self.target_ty.kind) {
(&Infer(TyVar(a_vid)), &Infer(TyVar(b_vid))) => {
self.infcx.type_variables.borrow_mut().sub_unified(a_vid, b_vid)
}
_ => false,
}
}) {
Some(ty)
} else {
None
}
}
None => None,
}
}
}
impl<'a, 'tcx> Visitor<'tcx> for FindLocalByTypeVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, Self::Map> {
NestedVisitorMap::OnlyBodies(&self.hir_map)
}
fn visit_local(&mut self, local: &'tcx Local<'tcx>) {
if let (None, Some(ty)) = (self.found_local_pattern, self.node_matches_type(local.hir_id)) {
self.found_local_pattern = Some(&*local.pat);
self.found_ty = Some(ty);
}
intravisit::walk_local(self, local);
}
fn visit_body(&mut self, body: &'tcx Body<'tcx>) {
for param in body.params {
if let (None, Some(ty)) = (self.found_arg_pattern, self.node_matches_type(param.hir_id))
{
self.found_arg_pattern = Some(&*param.pat);
self.found_ty = Some(ty);
}
}
intravisit::walk_body(self, body);
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if self.node_matches_type(expr.hir_id).is_some() {
match expr.kind {
ExprKind::Closure(..) => self.found_closure = Some(&expr.kind),
ExprKind::MethodCall(..) => self.found_method_call = Some(&expr),
_ => {}
}
}
intravisit::walk_expr(self, expr);
}
}
/// Suggest giving an appropriate return type to a closure expression.
fn closure_return_type_suggestion(
span: Span,
err: &mut DiagnosticBuilder<'_>,
output: &FunctionRetTy<'_>,
body: &Body<'_>,
descr: &str,
name: &str,
ret: &str,
parent_name: Option<String>,
parent_descr: Option<&str>,
) {
let (arrow, post) = match output {
FunctionRetTy::DefaultReturn(_) => ("-> ", " "),
_ => ("", ""),
};
let suggestion = match body.value.kind {
ExprKind::Block(..) => vec![(output.span(), format!("{}{}{}", arrow, ret, post))],
_ => vec![
(output.span(), format!("{}{}{}{{ ", arrow, ret, post)),
(body.value.span.shrink_to_hi(), " }".to_string()),
],
};
err.multipart_suggestion(
"give this closure an explicit return type without `_` placeholders",
suggestion,
Applicability::HasPlaceholders,
);
err.span_label(span, InferCtxt::missing_type_msg(&name, &descr, parent_name, parent_descr));
}
/// Given a closure signature, return a `String` containing a list of all its argument types.
fn closure_args(fn_sig: &ty::PolyFnSig<'_>) -> String {
fn_sig
.inputs()
.skip_binder()
.iter()
.next()
.map(|args| args.tuple_fields().map(|arg| arg.to_string()).collect::<Vec<_>>().join(", "))
.unwrap_or_default()
}
pub enum TypeAnnotationNeeded {
E0282,
E0283,
E0284,
}
impl Into<rustc_errors::DiagnosticId> for TypeAnnotationNeeded {
fn into(self) -> rustc_errors::DiagnosticId {
match self {
Self::E0282 => rustc_errors::error_code!(E0282),
Self::E0283 => rustc_errors::error_code!(E0283),
Self::E0284 => rustc_errors::error_code!(E0284),
}
}
}
impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
pub fn extract_type_name(
&self,
ty: Ty<'tcx>,
highlight: Option<ty::print::RegionHighlightMode>,
) -> (String, Option<Span>, Cow<'static, str>, Option<String>, Option<&'static str>) {
if let ty::Infer(ty::TyVar(ty_vid)) = ty.kind {
let ty_vars = self.type_variables.borrow();
let var_origin = ty_vars.var_origin(ty_vid);
if let TypeVariableOriginKind::TypeParameterDefinition(name, def_id) = var_origin.kind {
let parent_def_id = def_id.and_then(|def_id| self.tcx.parent(def_id));
let (parent_name, parent_desc) = if let Some(parent_def_id) = parent_def_id {
let parent_name = self
.tcx
.def_key(parent_def_id)
.disambiguated_data
.data
.get_opt_name()
.map(|parent_symbol| parent_symbol.to_string());
let type_parent_desc = self
.tcx
.def_kind(parent_def_id)
.map(|parent_def_kind| parent_def_kind.descr(parent_def_id));
(parent_name, type_parent_desc)
} else {
(None, None)
};
if name != kw::SelfUpper {
return (
name.to_string(),
Some(var_origin.span),
"type parameter".into(),
parent_name,
parent_desc,
);
}
}
}
let mut s = String::new();
let mut printer = ty::print::FmtPrinter::new(self.tcx, &mut s, Namespace::TypeNS);
if let Some(highlight) = highlight {
printer.region_highlight_mode = highlight;
}
let _ = ty.print(printer);
(s, None, ty.prefix_string(), None, None)
}
pub fn need_type_info_err(
&self,
body_id: Option<hir::BodyId>,
span: Span,
ty: Ty<'tcx>,
error_code: TypeAnnotationNeeded,
) -> DiagnosticBuilder<'tcx> {
let ty = self.resolve_vars_if_possible(&ty);
let (name, name_sp, descr, parent_name, parent_descr) = self.extract_type_name(&ty, None);
let mut local_visitor = FindLocalByTypeVisitor::new(&self, ty, &self.tcx.hir());
let ty_to_string = |ty: Ty<'tcx>| -> String {
let mut s = String::new();
let mut printer = ty::print::FmtPrinter::new(self.tcx, &mut s, Namespace::TypeNS);
let ty_vars = self.type_variables.borrow();
let getter = move |ty_vid| {
let var_origin = ty_vars.var_origin(ty_vid);
if let TypeVariableOriginKind::TypeParameterDefinition(name, _) = var_origin.kind {
return Some(name.to_string());
}
None
};
printer.name_resolver = Some(Box::new(&getter));
let _ = ty.print(printer);
s
};
if let Some(body_id) = body_id {
let expr = self.tcx.hir().expect_expr(body_id.hir_id);
local_visitor.visit_expr(expr);
}
let err_span = if let Some(pattern) = local_visitor.found_arg_pattern {
pattern.span
} else if let Some(span) = name_sp {
// `span` here lets us point at `sum` instead of the entire right hand side expr:
// error[E0282]: type annotations needed
// --> file2.rs:3:15
// |
// 3 | let _ = x.sum() as f64;
// | ^^^ cannot infer type for `S`
span
} else if let Some(ExprKind::MethodCall(_, call_span, _)) =
local_visitor.found_method_call.map(|e| &e.kind)
{
// Point at the call instead of the whole expression:
// error[E0284]: type annotations needed
// --> file.rs:2:5
// |
// 2 | vec![Ok(2)].into_iter().collect()?;
// | ^^^^^^^ cannot infer type
// |
// = note: cannot resolve `<_ as std::ops::Try>::Ok == _`
if span.contains(*call_span) { *call_span } else { span }
} else {
span
};
let is_named_and_not_impl_trait = |ty: Ty<'_>| {
&ty.to_string() != "_" &&
// FIXME: Remove this check after `impl_trait_in_bindings` is stabilized. #63527
(!ty.is_impl_trait() || self.tcx.features().impl_trait_in_bindings)
};
let ty_msg = match local_visitor.found_ty {
Some(ty::TyS { kind: ty::Closure(def_id, substs), .. }) => {
let fn_sig = substs.as_closure().sig(*def_id, self.tcx);
let args = closure_args(&fn_sig);
let ret = fn_sig.output().skip_binder().to_string();
format!(" for the closure `fn({}) -> {}`", args, ret)
}
Some(ty) if is_named_and_not_impl_trait(ty) => {
let ty = ty_to_string(ty);
format!(" for `{}`", ty)
}
_ => String::new(),
};
// When `name` corresponds to a type argument, show the path of the full type we're
// trying to infer. In the following example, `ty_msg` contains
// " in `std::result::Result<i32, E>`":
// ```
// error[E0282]: type annotations needed for `std::result::Result<i32, E>`
// --> file.rs:L:CC
// |
// L | let b = Ok(4);
// | - ^^ cannot infer type for `E` in `std::result::Result<i32, E>`
// | |
// | consider giving `b` the explicit type `std::result::Result<i32, E>`, where
// | the type parameter `E` is specified
// ```
let error_code = error_code.into();
let mut err = self.tcx.sess.struct_span_err_with_code(
err_span,
&format!("type annotations needed{}", ty_msg),
error_code,
);
let suffix = match local_visitor.found_ty {
Some(ty::TyS { kind: ty::Closure(def_id, substs), .. }) => {
let fn_sig = substs.as_closure().sig(*def_id, self.tcx);
let ret = fn_sig.output().skip_binder().to_string();
if let Some(ExprKind::Closure(_, decl, body_id, ..)) = local_visitor.found_closure {
if let Some(body) = self.tcx.hir().krate().bodies.get(body_id) {
closure_return_type_suggestion(
span,
&mut err,
&decl.output,
&body,
&descr,
&name,
&ret,
parent_name,
parent_descr,
);
// We don't want to give the other suggestions when the problem is the
// closure return type.
return err;
}
}
// This shouldn't be reachable, but just in case we leave a reasonable fallback.
let args = closure_args(&fn_sig);
// 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)
}
Some(ty) if is_named_and_not_impl_trait(ty) && name == "_" => {
let ty = ty_to_string(ty);
format!("the explicit type `{}`, with the type parameters specified", ty)
}
Some(ty) if is_named_and_not_impl_trait(ty) && ty.to_string() != name => {
let ty = ty_to_string(ty);
format!(
"the explicit type `{}`, where the type parameter `{}` is specified",
ty, name,
)
}
_ => "a type".to_string(),
};
if let Some(pattern) = local_visitor.found_arg_pattern {
// We don't want to show the default label for closures.
//
// So, before clearing, the output would look something like this:
// ```
// let x = |_| { };
// - ^^^^ cannot infer type for `[_; 0]`
// |
// consider giving this closure parameter a type
// ```
//
// After clearing, it looks something like this:
// ```
// let x = |_| { };
// ^ consider giving this closure parameter the type `[_; 0]`
// with the type parameter `_` specified
// ```
err.span_label(
pattern.span,
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),
}
} 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 specifiying type params or point out the return type of the call:
//
// error[E0282]: type annotations needed
// --> $DIR/type-annotations-needed-expr.rs:2:39
// |
// LL | let _ = x.into_iter().sum() as f64;
// | ^^^
// | |
// | cannot infer type for `S`
// | help: consider specifying the type argument in
// | the method call: `sum::<S>`
// |
// = note: type must be known at this point
//
// or
//
// error[E0282]: type annotations needed
// --> $DIR/issue-65611.rs:59:20
// |
// LL | let x = buffer.last().unwrap().0.clone();
// | -------^^^^--
// | | |
// | | cannot infer type for `T`
// | 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);
}
}
// Instead of the following:
// error[E0282]: type annotations needed
// --> file2.rs:3:15
// |
// 3 | let _ = x.sum() as f64;
// | --^^^--------- cannot infer type for `S`
// |
// = note: type must be known at this point
// We want:
// error[E0282]: type annotations needed
// --> file2.rs:3:15
// |
// 3 | let _ = x.sum() as f64;
// | ^^^ cannot infer type for `S`
// |
// = note: type must be known at this point
let span = name_sp.unwrap_or(err_span);
if !err
.span
.span_labels()
.iter()
.any(|span_label| span_label.label.is_some() && span_label.span == span)
&& local_visitor.found_arg_pattern.is_none()
{
// Avoid multiple labels pointing at `span`.
err.span_label(
span,
InferCtxt::missing_type_msg(&name, &descr, parent_name, parent_descr),
);
}
err
}
/// If the `FnSig` for the method call can be found and type arguments are identified as
/// needed, suggest annotating the call, otherwise point out the resulting type of the call.
fn annotate_method_call(
&self,
segment: &hir::PathSegment<'_>,
e: &Expr<'_>,
err: &mut DiagnosticBuilder<'_>,
) {
if let (Ok(snippet), Some(tables), None) = (
self.tcx.sess.source_map().span_to_snippet(segment.ident.span),
self.in_progress_tables,
&segment.args,
) {
let borrow = tables.borrow();
if let Some((DefKind::Method, did)) = borrow.type_dependent_def(e.hir_id) {
let generics = self.tcx.generics_of(did);
if !generics.params.is_empty() {
err.span_suggestion(
segment.ident.span,
&format!(
"consider specifying the type argument{} in the method call",
if generics.params.len() > 1 { "s" } else { "" },
),
format!(
"{}::<{}>",
snippet,
generics
.params
.iter()
.map(|p| p.name.to_string())
.collect::<Vec<String>>()
.join(", ")
),
Applicability::HasPlaceholders,
);
} else {
let sig = self.tcx.fn_sig(did);
let bound_output = sig.output();
let output = bound_output.skip_binder();
err.span_label(e.span, &format!("this method call resolves to `{:?}`", output));
let kind = &output.kind;
if let ty::Projection(proj) | ty::UnnormalizedProjection(proj) = kind {
if let Some(span) = self.tcx.hir().span_if_local(proj.item_def_id) {
err.span_label(span, &format!("`{:?}` defined here", output));
}
}
}
}
}
}
pub fn need_type_info_err_in_generator(
&self,
kind: hir::GeneratorKind,
span: Span,
ty: Ty<'tcx>,
) -> DiagnosticBuilder<'tcx> {
let ty = self.resolve_vars_if_possible(&ty);
let (name, _, descr, parent_name, parent_descr) = self.extract_type_name(&ty, None);
let mut err = struct_span_err!(
self.tcx.sess,
span,
E0698,
"type inside {} must be known in this context",
kind,
);
err.span_label(span, InferCtxt::missing_type_msg(&name, &descr, parent_name, parent_descr));
err
}
fn missing_type_msg(
type_name: &str,
descr: &str,
parent_name: Option<String>,
parent_descr: Option<&str>,
) -> Cow<'static, str> {
if type_name == "_" {
"cannot infer type".into()
} 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()
};
format!("cannot infer type for {} `{}`{}", descr, type_name, parent_desc).into()
}
}
}