882 lines
36 KiB
Rust
882 lines
36 KiB
Rust
use crate::check::FnCtxt;
|
|
use rustc::infer::InferOk;
|
|
use rustc::traits::{self, ObligationCause};
|
|
|
|
use rustc::ty::adjustment::AllowTwoPhase;
|
|
use rustc::ty::{self, AssocItem, Ty};
|
|
use rustc_errors::{Applicability, DiagnosticBuilder};
|
|
use rustc_hir as hir;
|
|
use rustc_hir::{is_range_literal, print, Node};
|
|
use rustc_span::symbol::sym;
|
|
use rustc_span::Span;
|
|
use syntax::util::parser::PREC_POSTFIX;
|
|
|
|
use super::method::probe;
|
|
|
|
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|
pub fn emit_coerce_suggestions(
|
|
&self,
|
|
err: &mut DiagnosticBuilder<'_>,
|
|
expr: &hir::Expr<'_>,
|
|
expr_ty: Ty<'tcx>,
|
|
expected: Ty<'tcx>,
|
|
) {
|
|
self.annotate_expected_due_to_let_ty(err, expr);
|
|
self.suggest_compatible_variants(err, expr, expected, expr_ty);
|
|
self.suggest_ref_or_into(err, expr, expected, expr_ty);
|
|
self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty);
|
|
self.suggest_missing_await(err, expr, expected, expr_ty);
|
|
}
|
|
|
|
// Requires that the two types unify, and prints an error message if
|
|
// they don't.
|
|
pub fn demand_suptype(&self, sp: Span, expected: Ty<'tcx>, actual: Ty<'tcx>) {
|
|
self.demand_suptype_diag(sp, expected, actual).map(|mut e| e.emit());
|
|
}
|
|
|
|
pub fn demand_suptype_diag(
|
|
&self,
|
|
sp: Span,
|
|
expected: Ty<'tcx>,
|
|
actual: Ty<'tcx>,
|
|
) -> Option<DiagnosticBuilder<'tcx>> {
|
|
let cause = &self.misc(sp);
|
|
match self.at(cause, self.param_env).sup(expected, actual) {
|
|
Ok(InferOk { obligations, value: () }) => {
|
|
self.register_predicates(obligations);
|
|
None
|
|
}
|
|
Err(e) => Some(self.report_mismatched_types(&cause, expected, actual, e)),
|
|
}
|
|
}
|
|
|
|
pub fn demand_eqtype(&self, sp: Span, expected: Ty<'tcx>, actual: Ty<'tcx>) {
|
|
if let Some(mut err) = self.demand_eqtype_diag(sp, expected, actual) {
|
|
err.emit();
|
|
}
|
|
}
|
|
|
|
pub fn demand_eqtype_diag(
|
|
&self,
|
|
sp: Span,
|
|
expected: Ty<'tcx>,
|
|
actual: Ty<'tcx>,
|
|
) -> Option<DiagnosticBuilder<'tcx>> {
|
|
self.demand_eqtype_with_origin(&self.misc(sp), expected, actual)
|
|
}
|
|
|
|
pub fn demand_eqtype_with_origin(
|
|
&self,
|
|
cause: &ObligationCause<'tcx>,
|
|
expected: Ty<'tcx>,
|
|
actual: Ty<'tcx>,
|
|
) -> Option<DiagnosticBuilder<'tcx>> {
|
|
match self.at(cause, self.param_env).eq(expected, actual) {
|
|
Ok(InferOk { obligations, value: () }) => {
|
|
self.register_predicates(obligations);
|
|
None
|
|
}
|
|
Err(e) => Some(self.report_mismatched_types(cause, expected, actual, e)),
|
|
}
|
|
}
|
|
|
|
pub fn demand_coerce(
|
|
&self,
|
|
expr: &hir::Expr<'_>,
|
|
checked_ty: Ty<'tcx>,
|
|
expected: Ty<'tcx>,
|
|
allow_two_phase: AllowTwoPhase,
|
|
) -> Ty<'tcx> {
|
|
let (ty, err) = self.demand_coerce_diag(expr, checked_ty, expected, allow_two_phase);
|
|
if let Some(mut err) = err {
|
|
err.emit();
|
|
}
|
|
ty
|
|
}
|
|
|
|
// Checks that the type of `expr` can be coerced to `expected`.
|
|
//
|
|
// N.B., this code relies on `self.diverges` to be accurate. In
|
|
// particular, assignments to `!` will be permitted if the
|
|
// diverges flag is currently "always".
|
|
pub fn demand_coerce_diag(
|
|
&self,
|
|
expr: &hir::Expr<'_>,
|
|
checked_ty: Ty<'tcx>,
|
|
expected: Ty<'tcx>,
|
|
allow_two_phase: AllowTwoPhase,
|
|
) -> (Ty<'tcx>, Option<DiagnosticBuilder<'tcx>>) {
|
|
let expected = self.resolve_vars_with_obligations(expected);
|
|
|
|
let e = match self.try_coerce(expr, checked_ty, expected, allow_two_phase) {
|
|
Ok(ty) => return (ty, None),
|
|
Err(e) => e,
|
|
};
|
|
|
|
let expr = expr.peel_drop_temps();
|
|
let cause = self.misc(expr.span);
|
|
let expr_ty = self.resolve_vars_with_obligations(checked_ty);
|
|
let mut err = self.report_mismatched_types(&cause, expected, expr_ty, e);
|
|
|
|
if self.is_assign_to_bool(expr, expected) {
|
|
// Error reported in `check_assign` so avoid emitting error again.
|
|
err.delay_as_bug();
|
|
return (expected, None);
|
|
}
|
|
|
|
self.emit_coerce_suggestions(&mut err, expr, expr_ty, expected);
|
|
|
|
(expected, Some(err))
|
|
}
|
|
|
|
fn annotate_expected_due_to_let_ty(
|
|
&self,
|
|
err: &mut DiagnosticBuilder<'_>,
|
|
expr: &hir::Expr<'_>,
|
|
) {
|
|
let parent = self.tcx.hir().get_parent_node(expr.hir_id);
|
|
if let Some(hir::Node::Local(hir::Local { ty: Some(ty), init: Some(init), .. })) =
|
|
self.tcx.hir().find(parent)
|
|
{
|
|
if init.hir_id == expr.hir_id {
|
|
// Point at `let` assignment type.
|
|
err.span_label(ty.span, "expected due to this");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns whether the expected type is `bool` and the expression is `x = y`.
|
|
pub fn is_assign_to_bool(&self, expr: &hir::Expr<'_>, expected: Ty<'tcx>) -> bool {
|
|
if let hir::ExprKind::Assign(..) = expr.kind {
|
|
return expected == self.tcx.types.bool;
|
|
}
|
|
false
|
|
}
|
|
|
|
/// If the expected type is an enum (Issue #55250) with any variants whose
|
|
/// sole field is of the found type, suggest such variants. (Issue #42764)
|
|
fn suggest_compatible_variants(
|
|
&self,
|
|
err: &mut DiagnosticBuilder<'_>,
|
|
expr: &hir::Expr<'_>,
|
|
expected: Ty<'tcx>,
|
|
expr_ty: Ty<'tcx>,
|
|
) {
|
|
if let ty::Adt(expected_adt, substs) = expected.kind {
|
|
if !expected_adt.is_enum() {
|
|
return;
|
|
}
|
|
|
|
let mut compatible_variants = expected_adt
|
|
.variants
|
|
.iter()
|
|
.filter(|variant| variant.fields.len() == 1)
|
|
.filter_map(|variant| {
|
|
let sole_field = &variant.fields[0];
|
|
let sole_field_ty = sole_field.ty(self.tcx, substs);
|
|
if self.can_coerce(expr_ty, sole_field_ty) {
|
|
let variant_path = self.tcx.def_path_str(variant.def_id);
|
|
// FIXME #56861: DRYer prelude filtering
|
|
Some(variant_path.trim_start_matches("std::prelude::v1::").to_string())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.peekable();
|
|
|
|
if compatible_variants.peek().is_some() {
|
|
let expr_text =
|
|
self.tcx.sess.source_map().span_to_snippet(expr.span).unwrap_or_else(|_| {
|
|
print::to_string(print::NO_ANN, |s| s.print_expr(expr))
|
|
});
|
|
let suggestions = compatible_variants.map(|v| format!("{}({})", v, expr_text));
|
|
let msg = "try using a variant of the expected enum";
|
|
err.span_suggestions(expr.span, msg, suggestions, Applicability::MaybeIncorrect);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_conversion_methods(
|
|
&self,
|
|
span: Span,
|
|
expected: Ty<'tcx>,
|
|
checked_ty: Ty<'tcx>,
|
|
) -> Vec<AssocItem> {
|
|
let mut methods = self.probe_for_return_type(
|
|
span,
|
|
probe::Mode::MethodCall,
|
|
expected,
|
|
checked_ty,
|
|
hir::DUMMY_HIR_ID,
|
|
);
|
|
methods.retain(|m| {
|
|
self.has_no_input_arg(m)
|
|
&& self
|
|
.tcx
|
|
.get_attrs(m.def_id)
|
|
.iter()
|
|
// This special internal attribute is used to whitelist
|
|
// "identity-like" conversion methods to be suggested here.
|
|
//
|
|
// FIXME (#46459 and #46460): ideally
|
|
// `std::convert::Into::into` and `std::borrow:ToOwned` would
|
|
// also be `#[rustc_conversion_suggestion]`, if not for
|
|
// method-probing false-positives and -negatives (respectively).
|
|
//
|
|
// FIXME? Other potential candidate methods: `as_ref` and
|
|
// `as_mut`?
|
|
.find(|a| a.check_name(sym::rustc_conversion_suggestion))
|
|
.is_some()
|
|
});
|
|
|
|
methods
|
|
}
|
|
|
|
// This function checks if the method isn't static and takes other arguments than `self`.
|
|
fn has_no_input_arg(&self, method: &AssocItem) -> bool {
|
|
match method.kind {
|
|
ty::AssocKind::Method => {
|
|
self.tcx.fn_sig(method.def_id).inputs().skip_binder().len() == 1
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Identify some cases where `as_ref()` would be appropriate and suggest it.
|
|
///
|
|
/// Given the following code:
|
|
/// ```
|
|
/// struct Foo;
|
|
/// fn takes_ref(_: &Foo) {}
|
|
/// let ref opt = Some(Foo);
|
|
///
|
|
/// opt.map(|param| takes_ref(param));
|
|
/// ```
|
|
/// Suggest using `opt.as_ref().map(|param| takes_ref(param));` instead.
|
|
///
|
|
/// It only checks for `Option` and `Result` and won't work with
|
|
/// ```
|
|
/// opt.map(|param| { takes_ref(param) });
|
|
/// ```
|
|
fn can_use_as_ref(&self, expr: &hir::Expr<'_>) -> Option<(Span, &'static str, String)> {
|
|
let path = match expr.kind {
|
|
hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => path,
|
|
_ => return None,
|
|
};
|
|
|
|
let local_id = match path.res {
|
|
hir::def::Res::Local(id) => id,
|
|
_ => return None,
|
|
};
|
|
|
|
let local_parent = self.tcx.hir().get_parent_node(local_id);
|
|
let param_hir_id = match self.tcx.hir().find(local_parent) {
|
|
Some(Node::Param(hir::Param { hir_id, .. })) => hir_id,
|
|
_ => return None,
|
|
};
|
|
|
|
let param_parent = self.tcx.hir().get_parent_node(*param_hir_id);
|
|
let (expr_hir_id, closure_fn_decl) = match self.tcx.hir().find(param_parent) {
|
|
Some(Node::Expr(hir::Expr {
|
|
hir_id,
|
|
kind: hir::ExprKind::Closure(_, decl, ..),
|
|
..
|
|
})) => (hir_id, decl),
|
|
_ => return None,
|
|
};
|
|
|
|
let expr_parent = self.tcx.hir().get_parent_node(*expr_hir_id);
|
|
let hir = self.tcx.hir().find(expr_parent);
|
|
let closure_params_len = closure_fn_decl.inputs.len();
|
|
let (method_path, method_span, method_expr) = match (hir, closure_params_len) {
|
|
(
|
|
Some(Node::Expr(hir::Expr {
|
|
kind: hir::ExprKind::MethodCall(path, span, expr),
|
|
..
|
|
})),
|
|
1,
|
|
) => (path, span, expr),
|
|
_ => return None,
|
|
};
|
|
|
|
let self_ty = self.tables.borrow().node_type(method_expr[0].hir_id);
|
|
let self_ty = format!("{:?}", self_ty);
|
|
let name = method_path.ident.as_str();
|
|
let is_as_ref_able = (self_ty.starts_with("&std::option::Option")
|
|
|| self_ty.starts_with("&std::result::Result")
|
|
|| self_ty.starts_with("std::option::Option")
|
|
|| self_ty.starts_with("std::result::Result"))
|
|
&& (name == "map" || name == "and_then");
|
|
match (is_as_ref_able, self.sess().source_map().span_to_snippet(*method_span)) {
|
|
(true, Ok(src)) => {
|
|
let suggestion = format!("as_ref().{}", src);
|
|
Some((*method_span, "consider using `as_ref` instead", suggestion))
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
crate fn is_hir_id_from_struct_pattern_shorthand_field(
|
|
&self,
|
|
hir_id: hir::HirId,
|
|
sp: Span,
|
|
) -> bool {
|
|
let cm = self.sess().source_map();
|
|
let parent_id = self.tcx.hir().get_parent_node(hir_id);
|
|
if let Some(parent) = self.tcx.hir().find(parent_id) {
|
|
// Account for fields
|
|
if let Node::Expr(hir::Expr { kind: hir::ExprKind::Struct(_, fields, ..), .. }) = parent
|
|
{
|
|
if let Ok(src) = cm.span_to_snippet(sp) {
|
|
for field in *fields {
|
|
if field.ident.as_str() == src && field.is_shorthand {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// This function is used to determine potential "simple" improvements or users' errors and
|
|
/// provide them useful help. For example:
|
|
///
|
|
/// ```
|
|
/// fn some_fn(s: &str) {}
|
|
///
|
|
/// let x = "hey!".to_owned();
|
|
/// some_fn(x); // error
|
|
/// ```
|
|
///
|
|
/// No need to find every potential function which could make a coercion to transform a
|
|
/// `String` into a `&str` since a `&` would do the trick!
|
|
///
|
|
/// In addition of this check, it also checks between references mutability state. If the
|
|
/// expected is mutable but the provided isn't, maybe we could just say "Hey, try with
|
|
/// `&mut`!".
|
|
pub fn check_ref(
|
|
&self,
|
|
expr: &hir::Expr<'_>,
|
|
checked_ty: Ty<'tcx>,
|
|
expected: Ty<'tcx>,
|
|
) -> Option<(Span, &'static str, String)> {
|
|
let cm = self.sess().source_map();
|
|
let sp = expr.span;
|
|
if !cm.span_to_filename(sp).is_real() {
|
|
// Ignore if span is from within a macro #41858, #58298. We previously used the macro
|
|
// call span, but that breaks down when the type error comes from multiple calls down.
|
|
return None;
|
|
}
|
|
|
|
let is_struct_pat_shorthand_field =
|
|
self.is_hir_id_from_struct_pattern_shorthand_field(expr.hir_id, sp);
|
|
|
|
// If the span is from a macro, then it's hard to extract the text
|
|
// and make a good suggestion, so don't bother.
|
|
let is_macro = sp.from_expansion() && sp.desugaring_kind().is_none();
|
|
|
|
// `ExprKind::DropTemps` is semantically irrelevant for these suggestions.
|
|
let expr = expr.peel_drop_temps();
|
|
|
|
match (&expr.kind, &expected.kind, &checked_ty.kind) {
|
|
(_, &ty::Ref(_, exp, _), &ty::Ref(_, check, _)) => match (&exp.kind, &check.kind) {
|
|
(&ty::Str, &ty::Array(arr, _)) | (&ty::Str, &ty::Slice(arr))
|
|
if arr == self.tcx.types.u8 =>
|
|
{
|
|
if let hir::ExprKind::Lit(_) = expr.kind {
|
|
if let Ok(src) = cm.span_to_snippet(sp) {
|
|
if src.starts_with("b\"") {
|
|
return Some((
|
|
sp,
|
|
"consider removing the leading `b`",
|
|
src[1..].to_string(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
(&ty::Array(arr, _), &ty::Str) | (&ty::Slice(arr), &ty::Str)
|
|
if arr == self.tcx.types.u8 =>
|
|
{
|
|
if let hir::ExprKind::Lit(_) = expr.kind {
|
|
if let Ok(src) = cm.span_to_snippet(sp) {
|
|
if src.starts_with("\"") {
|
|
return Some((
|
|
sp,
|
|
"consider adding a leading `b`",
|
|
format!("b{}", src),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
},
|
|
(_, &ty::Ref(_, _, mutability), _) => {
|
|
// Check if it can work when put into a ref. For example:
|
|
//
|
|
// ```
|
|
// fn bar(x: &mut i32) {}
|
|
//
|
|
// let x = 0u32;
|
|
// bar(&x); // error, expected &mut
|
|
// ```
|
|
let ref_ty = match mutability {
|
|
hir::Mutability::Mut => {
|
|
self.tcx.mk_mut_ref(self.tcx.mk_region(ty::ReStatic), checked_ty)
|
|
}
|
|
hir::Mutability::Not => {
|
|
self.tcx.mk_imm_ref(self.tcx.mk_region(ty::ReStatic), checked_ty)
|
|
}
|
|
};
|
|
if self.can_coerce(ref_ty, expected) {
|
|
let mut sugg_sp = sp;
|
|
if let hir::ExprKind::MethodCall(segment, _sp, args) = &expr.kind {
|
|
let clone_trait = self.tcx.lang_items().clone_trait().unwrap();
|
|
if let ([arg], Some(true), sym::clone) = (
|
|
&args[..],
|
|
self.tables.borrow().type_dependent_def_id(expr.hir_id).map(|did| {
|
|
let ai = self.tcx.associated_item(did);
|
|
ai.container == ty::TraitContainer(clone_trait)
|
|
}),
|
|
segment.ident.name,
|
|
) {
|
|
// If this expression had a clone call when suggesting borrowing
|
|
// we want to suggest removing it because it'd now be unnecessary.
|
|
sugg_sp = arg.span;
|
|
}
|
|
}
|
|
if let Ok(src) = cm.span_to_snippet(sugg_sp) {
|
|
let needs_parens = match expr.kind {
|
|
// parenthesize if needed (Issue #46756)
|
|
hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true,
|
|
// parenthesize borrows of range literals (Issue #54505)
|
|
_ if is_range_literal(self.tcx.sess.source_map(), expr) => true,
|
|
_ => false,
|
|
};
|
|
let sugg_expr = if needs_parens { format!("({})", src) } else { src };
|
|
|
|
if let Some(sugg) = self.can_use_as_ref(expr) {
|
|
return Some(sugg);
|
|
}
|
|
let field_name = if is_struct_pat_shorthand_field {
|
|
format!("{}: ", sugg_expr)
|
|
} else {
|
|
String::new()
|
|
};
|
|
if let Some(hir::Node::Expr(hir::Expr {
|
|
kind: hir::ExprKind::Assign(left_expr, ..),
|
|
..
|
|
})) = self.tcx.hir().find(self.tcx.hir().get_parent_node(expr.hir_id))
|
|
{
|
|
if mutability == hir::Mutability::Mut {
|
|
// Found the following case:
|
|
// fn foo(opt: &mut Option<String>){ opt = None }
|
|
// --- ^^^^
|
|
// | |
|
|
// consider dereferencing here: `*opt` |
|
|
// expected mutable reference, found enum `Option`
|
|
if let Ok(src) = cm.span_to_snippet(left_expr.span) {
|
|
return Some((
|
|
left_expr.span,
|
|
"consider dereferencing here to assign to the mutable \
|
|
borrowed piece of memory",
|
|
format!("*{}", src),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
return Some(match mutability {
|
|
hir::Mutability::Mut => (
|
|
sp,
|
|
"consider mutably borrowing here",
|
|
format!("{}&mut {}", field_name, sugg_expr),
|
|
),
|
|
hir::Mutability::Not => (
|
|
sp,
|
|
"consider borrowing here",
|
|
format!("{}&{}", field_name, sugg_expr),
|
|
),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
(
|
|
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref expr),
|
|
_,
|
|
&ty::Ref(_, checked, _),
|
|
) if {
|
|
self.infcx.can_sub(self.param_env, checked, &expected).is_ok() && !is_macro
|
|
} =>
|
|
{
|
|
// We have `&T`, check if what was expected was `T`. If so,
|
|
// we may want to suggest removing a `&`.
|
|
if !cm.span_to_filename(expr.span).is_real() {
|
|
if let Ok(code) = cm.span_to_snippet(sp) {
|
|
if code.chars().next() == Some('&') {
|
|
return Some((
|
|
sp,
|
|
"consider removing the borrow",
|
|
code[1..].to_string(),
|
|
));
|
|
}
|
|
}
|
|
return None;
|
|
}
|
|
if let Ok(code) = cm.span_to_snippet(expr.span) {
|
|
return Some((sp, "consider removing the borrow", code));
|
|
}
|
|
}
|
|
_ if sp == expr.span && !is_macro => {
|
|
// Check for `Deref` implementations by constructing a predicate to
|
|
// prove: `<T as Deref>::Output == U`
|
|
let deref_trait = self.tcx.lang_items().deref_trait().unwrap();
|
|
let item_def_id = self
|
|
.tcx
|
|
.associated_items(deref_trait)
|
|
.find(|item| item.kind == ty::AssocKind::Type)
|
|
.unwrap()
|
|
.def_id;
|
|
let predicate =
|
|
ty::Predicate::Projection(ty::Binder::bind(ty::ProjectionPredicate {
|
|
// `<T as Deref>::Output`
|
|
projection_ty: ty::ProjectionTy {
|
|
// `T`
|
|
substs: self.tcx.intern_substs(&[checked_ty.into()]),
|
|
// `Deref::Output`
|
|
item_def_id,
|
|
},
|
|
// `U`
|
|
ty: expected,
|
|
}));
|
|
let obligation = traits::Obligation::new(self.misc(sp), self.param_env, predicate);
|
|
let impls_deref = self.infcx.predicate_may_hold(&obligation);
|
|
|
|
// For a suggestion to make sense, the type would need to be `Copy`.
|
|
let is_copy = self.infcx.type_is_copy_modulo_regions(self.param_env, expected, sp);
|
|
|
|
if is_copy && impls_deref {
|
|
if let Ok(code) = cm.span_to_snippet(sp) {
|
|
let message = if checked_ty.is_region_ptr() {
|
|
"consider dereferencing the borrow"
|
|
} else {
|
|
"consider dereferencing the type"
|
|
};
|
|
let suggestion = if is_struct_pat_shorthand_field {
|
|
format!("{}: *{}", code, code)
|
|
} else {
|
|
format!("*{}", code)
|
|
};
|
|
return Some((sp, message, suggestion));
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn check_for_cast(
|
|
&self,
|
|
err: &mut DiagnosticBuilder<'_>,
|
|
expr: &hir::Expr<'_>,
|
|
checked_ty: Ty<'tcx>,
|
|
expected_ty: Ty<'tcx>,
|
|
) -> bool {
|
|
if self.tcx.hir().is_const_context(expr.hir_id) {
|
|
// Shouldn't suggest `.into()` on `const`s.
|
|
// FIXME(estebank): modify once we decide to suggest `as` casts
|
|
return false;
|
|
}
|
|
if !self.tcx.sess.source_map().span_to_filename(expr.span).is_real() {
|
|
// Ignore if span is from within a macro.
|
|
return false;
|
|
}
|
|
|
|
// If casting this expression to a given numeric type would be appropriate in case of a type
|
|
// mismatch.
|
|
//
|
|
// We want to minimize the amount of casting operations that are suggested, as it can be a
|
|
// lossy operation with potentially bad side effects, so we only suggest when encountering
|
|
// an expression that indicates that the original type couldn't be directly changed.
|
|
//
|
|
// For now, don't suggest casting with `as`.
|
|
let can_cast = false;
|
|
|
|
let mut prefix = String::new();
|
|
if let Some(hir::Node::Expr(hir::Expr {
|
|
kind: hir::ExprKind::Struct(_, fields, _), ..
|
|
})) = self.tcx.hir().find(self.tcx.hir().get_parent_node(expr.hir_id))
|
|
{
|
|
// `expr` is a literal field for a struct, only suggest if appropriate
|
|
for field in *fields {
|
|
if field.expr.hir_id == expr.hir_id && field.is_shorthand {
|
|
// This is a field literal
|
|
prefix = format!("{}: ", field.ident);
|
|
break;
|
|
}
|
|
}
|
|
if &prefix == "" {
|
|
// Likely a field was meant, but this field wasn't found. Do not suggest anything.
|
|
return false;
|
|
}
|
|
}
|
|
if let hir::ExprKind::Call(path, args) = &expr.kind {
|
|
if let (hir::ExprKind::Path(hir::QPath::TypeRelative(base_ty, path_segment)), 1) =
|
|
(&path.kind, args.len())
|
|
{
|
|
// `expr` is a conversion like `u32::from(val)`, do not suggest anything (#63697).
|
|
if let (hir::TyKind::Path(hir::QPath::Resolved(None, base_ty_path)), sym::from) =
|
|
(&base_ty.kind, path_segment.ident.name)
|
|
{
|
|
if let Some(ident) = &base_ty_path.segments.iter().map(|s| s.ident).next() {
|
|
match ident.name {
|
|
sym::i128
|
|
| sym::i64
|
|
| sym::i32
|
|
| sym::i16
|
|
| sym::i8
|
|
| sym::u128
|
|
| sym::u64
|
|
| sym::u32
|
|
| sym::u16
|
|
| sym::u8
|
|
| sym::isize
|
|
| sym::usize
|
|
if base_ty_path.segments.len() == 1 =>
|
|
{
|
|
return false;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let msg = format!("you can convert an `{}` to `{}`", checked_ty, expected_ty);
|
|
let cast_msg = format!("you can cast an `{} to `{}`", checked_ty, expected_ty);
|
|
let try_msg = format!("{} and panic if the converted value wouldn't fit", msg);
|
|
let lit_msg = format!(
|
|
"change the type of the numeric literal from `{}` to `{}`",
|
|
checked_ty, expected_ty,
|
|
);
|
|
|
|
let needs_paren = expr.precedence().order() < (PREC_POSTFIX as i8);
|
|
|
|
if let Ok(src) = self.tcx.sess.source_map().span_to_snippet(expr.span) {
|
|
let cast_suggestion = format!(
|
|
"{}{}{}{} as {}",
|
|
prefix,
|
|
if needs_paren { "(" } else { "" },
|
|
src,
|
|
if needs_paren { ")" } else { "" },
|
|
expected_ty,
|
|
);
|
|
let try_into_suggestion = format!(
|
|
"{}{}{}{}.try_into().unwrap()",
|
|
prefix,
|
|
if needs_paren { "(" } else { "" },
|
|
src,
|
|
if needs_paren { ")" } else { "" },
|
|
);
|
|
let into_suggestion = format!(
|
|
"{}{}{}{}.into()",
|
|
prefix,
|
|
if needs_paren { "(" } else { "" },
|
|
src,
|
|
if needs_paren { ")" } else { "" },
|
|
);
|
|
let suffix_suggestion = format!(
|
|
"{}{}{}{}",
|
|
if needs_paren { "(" } else { "" },
|
|
if let (ty::Int(_), ty::Float(_)) | (ty::Uint(_), ty::Float(_)) =
|
|
(&expected_ty.kind, &checked_ty.kind,)
|
|
{
|
|
// Remove fractional part from literal, for example `42.0f32` into `42`
|
|
let src = src.trim_end_matches(&checked_ty.to_string());
|
|
src.split(".").next().unwrap()
|
|
} else {
|
|
src.trim_end_matches(&checked_ty.to_string())
|
|
},
|
|
expected_ty,
|
|
if needs_paren { ")" } else { "" },
|
|
);
|
|
let literal_is_ty_suffixed = |expr: &hir::Expr<'_>| {
|
|
if let hir::ExprKind::Lit(lit) = &expr.kind {
|
|
lit.node.is_suffixed()
|
|
} else {
|
|
false
|
|
}
|
|
};
|
|
|
|
let suggest_to_change_suffix_or_into =
|
|
|err: &mut DiagnosticBuilder<'_>, is_fallible: bool| {
|
|
let into_sugg = into_suggestion.clone();
|
|
err.span_suggestion(
|
|
expr.span,
|
|
if literal_is_ty_suffixed(expr) {
|
|
&lit_msg
|
|
} else if is_fallible {
|
|
&try_msg
|
|
} else {
|
|
&msg
|
|
},
|
|
if literal_is_ty_suffixed(expr) {
|
|
suffix_suggestion.clone()
|
|
} else if is_fallible {
|
|
try_into_suggestion
|
|
} else {
|
|
into_sugg
|
|
},
|
|
Applicability::MachineApplicable,
|
|
);
|
|
};
|
|
|
|
match (&expected_ty.kind, &checked_ty.kind) {
|
|
(&ty::Int(ref exp), &ty::Int(ref found)) => {
|
|
let is_fallible = match (found.bit_width(), exp.bit_width()) {
|
|
(Some(found), Some(exp)) if found > exp => true,
|
|
(None, _) | (_, None) => true,
|
|
_ => false,
|
|
};
|
|
suggest_to_change_suffix_or_into(err, is_fallible);
|
|
true
|
|
}
|
|
(&ty::Uint(ref exp), &ty::Uint(ref found)) => {
|
|
let is_fallible = match (found.bit_width(), exp.bit_width()) {
|
|
(Some(found), Some(exp)) if found > exp => true,
|
|
(None, _) | (_, None) => true,
|
|
_ => false,
|
|
};
|
|
suggest_to_change_suffix_or_into(err, is_fallible);
|
|
true
|
|
}
|
|
(&ty::Int(_), &ty::Uint(_)) | (&ty::Uint(_), &ty::Int(_)) => {
|
|
suggest_to_change_suffix_or_into(err, true);
|
|
true
|
|
}
|
|
(&ty::Float(ref exp), &ty::Float(ref found)) => {
|
|
if found.bit_width() < exp.bit_width() {
|
|
suggest_to_change_suffix_or_into(err, false);
|
|
} else if literal_is_ty_suffixed(expr) {
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&lit_msg,
|
|
suffix_suggestion,
|
|
Applicability::MachineApplicable,
|
|
);
|
|
} else if can_cast {
|
|
// Missing try_into implementation for `f64` to `f32`
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&format!("{}, producing the closest possible value", cast_msg),
|
|
cast_suggestion,
|
|
Applicability::MaybeIncorrect, // lossy conversion
|
|
);
|
|
}
|
|
true
|
|
}
|
|
(&ty::Uint(_), &ty::Float(_)) | (&ty::Int(_), &ty::Float(_)) => {
|
|
if literal_is_ty_suffixed(expr) {
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&lit_msg,
|
|
suffix_suggestion,
|
|
Applicability::MachineApplicable,
|
|
);
|
|
} else if can_cast {
|
|
// Missing try_into implementation for `{float}` to `{integer}`
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&format!("{}, rounding the float towards zero", msg),
|
|
cast_suggestion,
|
|
Applicability::MaybeIncorrect, // lossy conversion
|
|
);
|
|
err.warn(
|
|
"if the rounded value cannot be represented by the target \
|
|
integer type, including `Inf` and `NaN`, casting will cause \
|
|
undefined behavior \
|
|
(https://github.com/rust-lang/rust/issues/10184)",
|
|
);
|
|
}
|
|
true
|
|
}
|
|
(&ty::Float(ref exp), &ty::Uint(ref found)) => {
|
|
// if `found` is `None` (meaning found is `usize`), don't suggest `.into()`
|
|
if exp.bit_width() > found.bit_width().unwrap_or(256) {
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&format!(
|
|
"{}, producing the floating point representation of the integer",
|
|
msg,
|
|
),
|
|
into_suggestion,
|
|
Applicability::MachineApplicable,
|
|
);
|
|
} else if literal_is_ty_suffixed(expr) {
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&lit_msg,
|
|
suffix_suggestion,
|
|
Applicability::MachineApplicable,
|
|
);
|
|
} else {
|
|
// Missing try_into implementation for `{integer}` to `{float}`
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&format!(
|
|
"{}, producing the floating point representation of the integer,
|
|
rounded if necessary",
|
|
cast_msg,
|
|
),
|
|
cast_suggestion,
|
|
Applicability::MaybeIncorrect, // lossy conversion
|
|
);
|
|
}
|
|
true
|
|
}
|
|
(&ty::Float(ref exp), &ty::Int(ref found)) => {
|
|
// if `found` is `None` (meaning found is `isize`), don't suggest `.into()`
|
|
if exp.bit_width() > found.bit_width().unwrap_or(256) {
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&format!(
|
|
"{}, producing the floating point representation of the integer",
|
|
&msg,
|
|
),
|
|
into_suggestion,
|
|
Applicability::MachineApplicable,
|
|
);
|
|
} else if literal_is_ty_suffixed(expr) {
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&lit_msg,
|
|
suffix_suggestion,
|
|
Applicability::MachineApplicable,
|
|
);
|
|
} else {
|
|
// Missing try_into implementation for `{integer}` to `{float}`
|
|
err.span_suggestion(
|
|
expr.span,
|
|
&format!(
|
|
"{}, producing the floating point representation of the integer, \
|
|
rounded if necessary",
|
|
&msg,
|
|
),
|
|
cast_suggestion,
|
|
Applicability::MaybeIncorrect, // lossy conversion
|
|
);
|
|
}
|
|
true
|
|
}
|
|
_ => false,
|
|
}
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|