1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
12 use rustc::infer::InferOk;
13 use rustc::traits::ObligationCause;
16 use syntax::util::parser::PREC_POSTFIX;
19 use rustc::hir::def::Def;
20 use rustc::hir::map::{NodeItem, NodeExpr};
21 use rustc::hir::{Item, ItemConst, print};
22 use rustc::ty::{self, Ty, AssociatedItem};
23 use rustc::ty::adjustment::AllowTwoPhase;
24 use errors::{DiagnosticBuilder, CodeMapper};
26 use super::method::probe;
28 impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
29 // Requires that the two types unify, and prints an error message if
31 pub fn demand_suptype(&self, sp: Span, expected: Ty<'tcx>, actual: Ty<'tcx>) {
32 self.demand_suptype_diag(sp, expected, actual).map(|mut e| e.emit());
35 pub fn demand_suptype_diag(&self,
38 actual: Ty<'tcx>) -> Option<DiagnosticBuilder<'tcx>> {
39 let cause = &self.misc(sp);
40 match self.at(cause, self.param_env).sup(expected, actual) {
41 Ok(InferOk { obligations, value: () }) => {
42 self.register_predicates(obligations);
46 Some(self.report_mismatched_types(&cause, expected, actual, e))
51 pub fn demand_eqtype(&self, sp: Span, expected: Ty<'tcx>, actual: Ty<'tcx>) {
52 if let Some(mut err) = self.demand_eqtype_diag(sp, expected, actual) {
57 pub fn demand_eqtype_diag(&self,
60 actual: Ty<'tcx>) -> Option<DiagnosticBuilder<'tcx>> {
61 self.demand_eqtype_with_origin(&self.misc(sp), expected, actual)
64 pub fn demand_eqtype_with_origin(&self,
65 cause: &ObligationCause<'tcx>,
67 actual: Ty<'tcx>) -> Option<DiagnosticBuilder<'tcx>> {
68 match self.at(cause, self.param_env).eq(expected, actual) {
69 Ok(InferOk { obligations, value: () }) => {
70 self.register_predicates(obligations);
74 Some(self.report_mismatched_types(cause, expected, actual, e))
79 pub fn demand_coerce(&self,
83 allow_two_phase: AllowTwoPhase)
85 let (ty, err) = self.demand_coerce_diag(expr, checked_ty, expected, allow_two_phase);
86 if let Some(mut err) = err {
92 // Checks that the type of `expr` can be coerced to `expected`.
94 // NB: This code relies on `self.diverges` to be accurate. In
95 // particular, assignments to `!` will be permitted if the
96 // diverges flag is currently "always".
97 pub fn demand_coerce_diag(&self,
101 allow_two_phase: AllowTwoPhase)
102 -> (Ty<'tcx>, Option<DiagnosticBuilder<'tcx>>) {
103 let expected = self.resolve_type_vars_with_obligations(expected);
105 let e = match self.try_coerce(expr, checked_ty, expected, allow_two_phase) {
106 Ok(ty) => return (ty, None),
110 let cause = self.misc(expr.span);
111 let expr_ty = self.resolve_type_vars_with_obligations(checked_ty);
112 let mut err = self.report_mismatched_types(&cause, expected, expr_ty, e);
114 // If the expected type is an enum with any variants whose sole
115 // field is of the found type, suggest such variants. See Issue
117 if let ty::TyAdt(expected_adt, substs) = expected.sty {
118 let mut compatible_variants = vec![];
119 for variant in &expected_adt.variants {
120 if variant.fields.len() == 1 {
121 let sole_field = &variant.fields[0];
122 let sole_field_ty = sole_field.ty(self.tcx, substs);
123 if self.can_coerce(expr_ty, sole_field_ty) {
124 let mut variant_path = self.tcx.item_path_str(variant.did);
125 variant_path = variant_path.trim_left_matches("std::prelude::v1::")
127 compatible_variants.push(variant_path);
131 if !compatible_variants.is_empty() {
132 let expr_text = print::to_string(print::NO_ANN, |s| s.print_expr(expr));
133 let suggestions = compatible_variants.iter()
134 .map(|v| format!("{}({})", v, expr_text)).collect::<Vec<_>>();
135 err.span_suggestions(expr.span,
136 "try using a variant of the expected type",
141 self.suggest_ref_or_into(&mut err, expr, expected, expr_ty);
143 (expected, Some(err))
146 pub fn get_conversion_methods(&self, span: Span, expected: Ty<'tcx>, checked_ty: Ty<'tcx>)
147 -> Vec<AssociatedItem> {
148 let mut methods = self.probe_for_return_type(span,
149 probe::Mode::MethodCall,
154 self.has_no_input_arg(m) &&
155 self.tcx.get_attrs(m.def_id).iter()
156 // This special internal attribute is used to whitelist
157 // "identity-like" conversion methods to be suggested here.
159 // FIXME (#46459 and #46460): ideally
160 // `std::convert::Into::into` and `std::borrow:ToOwned` would
161 // also be `#[rustc_conversion_suggestion]`, if not for
162 // method-probing false-positives and -negatives (respectively).
164 // FIXME? Other potential candidate methods: `as_ref` and
166 .find(|a| a.check_name("rustc_conversion_suggestion")).is_some()
172 // This function checks if the method isn't static and takes other arguments than `self`.
173 fn has_no_input_arg(&self, method: &AssociatedItem) -> bool {
175 Def::Method(def_id) => {
176 self.tcx.fn_sig(def_id).inputs().skip_binder().len() == 1
182 /// Identify some cases where `as_ref()` would be appropriate and suggest it.
184 /// Given the following code:
187 /// fn takes_ref(_: &Foo) {}
188 /// let ref opt = Some(Foo);
190 /// opt.map(|arg| takes_ref(arg));
192 /// Suggest using `opt.as_ref().map(|arg| takes_ref(arg));` instead.
194 /// It only checks for `Option` and `Result` and won't work with
196 /// opt.map(|arg| { takes_ref(arg) });
198 fn can_use_as_ref(&self, expr: &hir::Expr) -> Option<(Span, &'static str, String)> {
199 if let hir::ExprPath(hir::QPath::Resolved(_, ref path)) = expr.node {
200 if let hir::def::Def::Local(id) = path.def {
201 let parent = self.tcx.hir.get_parent_node(id);
202 if let Some(NodeExpr(hir::Expr {
204 node: hir::ExprClosure(_, decl, ..),
206 })) = self.tcx.hir.find(parent) {
207 let parent = self.tcx.hir.get_parent_node(*id);
208 if let (Some(NodeExpr(hir::Expr {
209 node: hir::ExprMethodCall(path, span, expr),
211 })), 1) = (self.tcx.hir.find(parent), decl.inputs.len()) {
212 let self_ty = self.tables.borrow().node_id_to_type(expr[0].hir_id);
213 let self_ty = format!("{:?}", self_ty);
214 let name = path.ident.as_str();
215 let is_as_ref_able = (
216 self_ty.starts_with("&std::option::Option") ||
217 self_ty.starts_with("&std::result::Result") ||
218 self_ty.starts_with("std::option::Option") ||
219 self_ty.starts_with("std::result::Result")
220 ) && (name == "map" || name == "and_then");
222 return Some((span.shrink_to_lo(),
223 "consider using `as_ref` instead",
224 "as_ref().".into()));
233 /// This function is used to determine potential "simple" improvements or users' errors and
234 /// provide them useful help. For example:
237 /// fn some_fn(s: &str) {}
239 /// let x = "hey!".to_owned();
240 /// some_fn(x); // error
243 /// No need to find every potential function which could make a coercion to transform a
244 /// `String` into a `&str` since a `&` would do the trick!
246 /// In addition of this check, it also checks between references mutability state. If the
247 /// expected is mutable but the provided isn't, maybe we could just say "Hey, try with
249 pub fn check_ref(&self,
251 checked_ty: Ty<'tcx>,
253 -> Option<(Span, &'static str, String)> {
254 let cm = self.sess().codemap();
255 // Use the callsite's span if this is a macro call. #41858
256 let sp = cm.call_span_if_macro(expr.span);
257 if !cm.span_to_filename(sp).is_real() {
261 match (&expected.sty, &checked_ty.sty) {
262 (&ty::TyRef(_, exp, _), &ty::TyRef(_, check, _)) => match (&exp.sty, &check.sty) {
263 (&ty::TyStr, &ty::TyArray(arr, _)) |
264 (&ty::TyStr, &ty::TySlice(arr)) if arr == self.tcx.types.u8 => {
265 if let hir::ExprLit(_) = expr.node {
266 if let Ok(src) = cm.span_to_snippet(sp) {
267 if src.starts_with("b\"") {
269 "consider removing the leading `b`",
270 src[1..].to_string()));
275 (&ty::TyArray(arr, _), &ty::TyStr) |
276 (&ty::TySlice(arr), &ty::TyStr) if arr == self.tcx.types.u8 => {
277 if let hir::ExprLit(_) = expr.node {
278 if let Ok(src) = cm.span_to_snippet(sp) {
279 if src.starts_with("\"") {
281 "consider adding a leading `b`",
282 format!("b{}", src)));
289 (&ty::TyRef(_, _, mutability), _) => {
290 // Check if it can work when put into a ref. For example:
293 // fn bar(x: &mut i32) {}
296 // bar(&x); // error, expected &mut
298 let ref_ty = match mutability {
299 hir::Mutability::MutMutable => self.tcx.mk_mut_ref(
300 self.tcx.mk_region(ty::ReStatic),
302 hir::Mutability::MutImmutable => self.tcx.mk_imm_ref(
303 self.tcx.mk_region(ty::ReStatic),
306 if self.can_coerce(ref_ty, expected) {
307 if let Ok(src) = cm.span_to_snippet(sp) {
308 let sugg_expr = match expr.node { // parenthesize if needed (Issue #46756)
309 hir::ExprCast(_, _) | hir::ExprBinary(_, _, _) => format!("({})", src),
312 if let Some(sugg) = self.can_use_as_ref(expr) {
315 return Some(match mutability {
316 hir::Mutability::MutMutable => {
317 (sp, "consider mutably borrowing here", format!("&mut {}",
320 hir::Mutability::MutImmutable => {
321 (sp, "consider borrowing here", format!("&{}", sugg_expr))
327 (_, &ty::TyRef(_, checked, _)) => {
328 // We have `&T`, check if what was expected was `T`. If so,
329 // we may want to suggest adding a `*`, or removing
332 // (But, also check check the `expn_info()` to see if this is
333 // a macro; if so, it's hard to extract the text and make a good
334 // suggestion, so don't bother.)
335 if self.infcx.can_sub(self.param_env, checked, &expected).is_ok() &&
336 sp.ctxt().outer().expn_info().is_none() {
339 hir::ExprAddrOf(_, ref expr) => {
340 if !cm.span_to_filename(expr.span).is_real() {
343 if let Ok(code) = cm.span_to_snippet(expr.span) {
344 return Some((sp, "consider removing the borrow", code));
348 // Maybe add `*`? Only if `T: Copy`.
350 if !self.infcx.type_moves_by_default(self.param_env,
353 let sp = cm.call_span_if_macro(sp);
354 if let Ok(code) = cm.span_to_snippet(sp) {
356 "consider dereferencing the borrow",
357 format!("*{}", code)));
369 pub fn check_for_cast(&self,
370 err: &mut DiagnosticBuilder<'tcx>,
372 checked_ty: Ty<'tcx>,
373 expected_ty: Ty<'tcx>)
375 let parent_id = self.tcx.hir.get_parent_node(expr.id);
376 match self.tcx.hir.find(parent_id) {
378 // Shouldn't suggest `.into()` on `const`s.
379 if let NodeItem(Item { node: ItemConst(_, _), .. }) = parent {
380 // FIXME(estebank): modify once we decide to suggest `as` casts
387 let will_truncate = "will truncate the source value";
388 let depending_on_isize = "will truncate or zero-extend depending on the bit width of \
390 let depending_on_usize = "will truncate or zero-extend depending on the bit width of \
392 let will_sign_extend = "will sign-extend the source value";
393 let will_zero_extend = "will zero-extend the source value";
395 // If casting this expression to a given numeric type would be appropriate in case of a type
398 // We want to minimize the amount of casting operations that are suggested, as it can be a
399 // lossy operation with potentially bad side effects, so we only suggest when encountering
400 // an expression that indicates that the original type couldn't be directly changed.
402 // For now, don't suggest casting with `as`.
403 let can_cast = false;
405 let needs_paren = expr.precedence().order() < (PREC_POSTFIX as i8);
407 if let Ok(src) = self.tcx.sess.codemap().span_to_snippet(expr.span) {
408 let msg = format!("you can cast an `{}` to `{}`", checked_ty, expected_ty);
409 let cast_suggestion = format!("{}{}{} as {}",
410 if needs_paren { "(" } else { "" },
412 if needs_paren { ")" } else { "" },
414 let into_suggestion = format!("{}{}{}.into()",
415 if needs_paren { "(" } else { "" },
417 if needs_paren { ")" } else { "" });
419 match (&expected_ty.sty, &checked_ty.sty) {
420 (&ty::TyInt(ref exp), &ty::TyInt(ref found)) => {
421 match (found.bit_width(), exp.bit_width()) {
422 (Some(found), Some(exp)) if found > exp => {
424 err.span_suggestion(expr.span,
425 &format!("{}, which {}", msg, will_truncate),
429 (None, _) | (_, None) => {
431 err.span_suggestion(expr.span,
432 &format!("{}, which {}",
439 err.span_suggestion(expr.span,
440 &format!("{}, which {}", msg, will_sign_extend),
446 (&ty::TyUint(ref exp), &ty::TyUint(ref found)) => {
447 match (found.bit_width(), exp.bit_width()) {
448 (Some(found), Some(exp)) if found > exp => {
450 err.span_suggestion(expr.span,
451 &format!("{}, which {}", msg, will_truncate),
455 (None, _) | (_, None) => {
457 err.span_suggestion(expr.span,
458 &format!("{}, which {}",
465 err.span_suggestion(expr.span,
466 &format!("{}, which {}", msg, will_zero_extend),
472 (&ty::TyInt(ref exp), &ty::TyUint(ref found)) => {
474 match (found.bit_width(), exp.bit_width()) {
475 (Some(found), Some(exp)) if found > exp - 1 => {
476 err.span_suggestion(expr.span,
477 &format!("{}, which {}", msg, will_truncate),
481 err.span_suggestion(expr.span,
482 &format!("{}, which {}", msg, will_truncate),
486 err.span_suggestion(expr.span,
487 &format!("{}, which {}",
493 err.span_suggestion(expr.span,
494 &format!("{}, which {}",
500 err.span_suggestion(expr.span,
501 &format!("{}, which {}", msg, will_zero_extend),
508 (&ty::TyUint(ref exp), &ty::TyInt(ref found)) => {
510 match (found.bit_width(), exp.bit_width()) {
511 (Some(found), Some(exp)) if found - 1 > exp => {
512 err.span_suggestion(expr.span,
513 &format!("{}, which {}", msg, will_truncate),
517 err.span_suggestion(expr.span,
518 &format!("{}, which {}", msg, will_sign_extend),
522 err.span_suggestion(expr.span,
523 &format!("{}, which {}",
529 err.span_suggestion(expr.span,
530 &format!("{}, which {}",
536 err.span_suggestion(expr.span,
537 &format!("{}, which {}", msg, will_sign_extend),
544 (&ty::TyFloat(ref exp), &ty::TyFloat(ref found)) => {
545 if found.bit_width() < exp.bit_width() {
546 err.span_suggestion(expr.span,
547 &format!("{} in a lossless way",
551 err.span_suggestion(expr.span,
552 &format!("{}, producing the closest possible value",
558 (&ty::TyUint(_), &ty::TyFloat(_)) | (&ty::TyInt(_), &ty::TyFloat(_)) => {
560 err.span_suggestion(expr.span,
561 &format!("{}, rounding the float towards zero",
564 err.warn("casting here will cause undefined behavior if the rounded value \
565 cannot be represented by the target integer type, including \
566 `Inf` and `NaN` (this is a bug and will be fixed)");
570 (&ty::TyFloat(ref exp), &ty::TyUint(ref found)) => {
571 // if `found` is `None` (meaning found is `usize`), don't suggest `.into()`
572 if exp.bit_width() > found.bit_width().unwrap_or(256) {
573 err.span_suggestion(expr.span,
574 &format!("{}, producing the floating point \
575 representation of the integer",
579 err.span_suggestion(expr.span,
580 &format!("{}, producing the floating point \
581 representation of the integer, rounded if \
588 (&ty::TyFloat(ref exp), &ty::TyInt(ref found)) => {
589 // if `found` is `None` (meaning found is `isize`), don't suggest `.into()`
590 if exp.bit_width() > found.bit_width().unwrap_or(256) {
591 err.span_suggestion(expr.span,
592 &format!("{}, producing the floating point \
593 representation of the integer",
597 err.span_suggestion(expr.span,
598 &format!("{}, producing the floating point \
599 representation of the integer, rounded if \