Merge pull request #557 from mcarton/cmp_owned

Check types in the CMP_OWNED lint
This commit is contained in:
llogiq 2016-01-18 19:15:43 +01:00
commit 4293c77198
4 changed files with 25 additions and 12 deletions

View File

@ -318,7 +318,7 @@ fn check_unwrap_or_default(
return false; return false;
}; };
if implements_trait(cx, arg_ty, default_trait_id) { if implements_trait(cx, arg_ty, default_trait_id, None) {
span_lint(cx, OR_FUN_CALL, span, span_lint(cx, OR_FUN_CALL, span,
&format!("use of `{}` followed by a call to `{}`", name, path)) &format!("use of `{}` followed by a call to `{}`", name, path))
.span_suggestion(span, "try this", .span_suggestion(span, "try this",

View File

@ -11,7 +11,7 @@
use rustc::middle::const_eval::EvalHint::ExprTypeChecked; use rustc::middle::const_eval::EvalHint::ExprTypeChecked;
use utils::{get_item_name, match_path, snippet, get_parent_expr, span_lint}; use utils::{get_item_name, match_path, snippet, get_parent_expr, span_lint};
use utils::{span_help_and_lint, walk_ptrs_ty, is_integer_literal}; use utils::{span_help_and_lint, walk_ptrs_ty, is_integer_literal, implements_trait};
/// **What it does:** This lint checks for function arguments and let bindings denoted as `ref`. It is `Warn` by default. /// **What it does:** This lint checks for function arguments and let bindings denoted as `ref`. It is `Warn` by default.
/// ///
@ -210,18 +210,18 @@ impl LateLintPass for CmpOwned {
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) { fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
if let ExprBinary(ref cmp, ref left, ref right) = expr.node { if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
if is_comparison_binop(cmp.node) { if is_comparison_binop(cmp.node) {
check_to_owned(cx, left, right.span, true, cmp.span); check_to_owned(cx, left, right, true, cmp.span);
check_to_owned(cx, right, left.span, false, cmp.span) check_to_owned(cx, right, left, false, cmp.span)
} }
} }
} }
} }
fn check_to_owned(cx: &LateContext, expr: &Expr, other_span: Span, left: bool, op: Span) { fn check_to_owned(cx: &LateContext, expr: &Expr, other: &Expr, left: bool, op: Span) {
let snip = match expr.node { let (arg_ty, snip) = match expr.node {
ExprMethodCall(Spanned{node: ref name, ..}, _, ref args) if args.len() == 1 => { ExprMethodCall(Spanned{node: ref name, ..}, _, ref args) if args.len() == 1 => {
if name.as_str() == "to_string" || name.as_str() == "to_owned" && is_str_arg(cx, args) { if name.as_str() == "to_string" || name.as_str() == "to_owned" && is_str_arg(cx, args) {
snippet(cx, args[0].span, "..") (cx.tcx.expr_ty(&args[0]), snippet(cx, args[0].span, ".."))
} else { } else {
return; return;
} }
@ -229,7 +229,7 @@ fn check_to_owned(cx: &LateContext, expr: &Expr, other_span: Span, left: bool, o
ExprCall(ref path, ref v) if v.len() == 1 => { ExprCall(ref path, ref v) if v.len() == 1 => {
if let ExprPath(None, ref path) = path.node { if let ExprPath(None, ref path) = path.node {
if match_path(path, &["String", "from_str"]) || match_path(path, &["String", "from"]) { if match_path(path, &["String", "from_str"]) || match_path(path, &["String", "from"]) {
snippet(cx, v[0].span, "..") (cx.tcx.expr_ty(&v[0]), snippet(cx, v[0].span, ".."))
} else { } else {
return; return;
} }
@ -239,6 +239,17 @@ fn check_to_owned(cx: &LateContext, expr: &Expr, other_span: Span, left: bool, o
} }
_ => return, _ => return,
}; };
let other_ty = cx.tcx.expr_ty(other);
let partial_eq_trait_id = match cx.tcx.lang_items.eq_trait() {
Some(id) => id,
None => return,
};
if !implements_trait(cx, arg_ty, partial_eq_trait_id, Some(vec![other_ty])) {
return;
}
if left { if left {
span_lint(cx, span_lint(cx,
CMP_OWNED, CMP_OWNED,
@ -247,14 +258,14 @@ fn check_to_owned(cx: &LateContext, expr: &Expr, other_span: Span, left: bool, o
compare without allocation", compare without allocation",
snip, snip,
snippet(cx, op, "=="), snippet(cx, op, "=="),
snippet(cx, other_span, ".."))); snippet(cx, other.span, "..")));
} else { } else {
span_lint(cx, span_lint(cx,
CMP_OWNED, CMP_OWNED,
expr.span, expr.span,
&format!("this creates an owned instance just for comparison. Consider using `{} {} {}` to \ &format!("this creates an owned instance just for comparison. Consider using `{} {} {}` to \
compare without allocation", compare without allocation",
snippet(cx, other_span, ".."), snippet(cx, other.span, ".."),
snippet(cx, op, "=="), snippet(cx, op, "=="),
snip)); snip));
} }

View File

@ -242,14 +242,14 @@ pub fn get_trait_def_id(cx: &LateContext, path: &[&str]) -> Option<DefId> {
/// Check whether a type implements a trait. /// Check whether a type implements a trait.
/// See also `get_trait_def_id`. /// See also `get_trait_def_id`.
pub fn implements_trait<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: ty::Ty<'tcx>, trait_id: DefId) -> bool { pub fn implements_trait<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: ty::Ty<'tcx>, trait_id: DefId, ty_params: Option<Vec<ty::Ty<'tcx>>>) -> bool {
cx.tcx.populate_implementations_for_trait_if_necessary(trait_id); cx.tcx.populate_implementations_for_trait_if_necessary(trait_id);
let infcx = infer::new_infer_ctxt(cx.tcx, &cx.tcx.tables, None); let infcx = infer::new_infer_ctxt(cx.tcx, &cx.tcx.tables, None);
let obligation = traits::predicate_for_trait_def(cx.tcx, let obligation = traits::predicate_for_trait_def(cx.tcx,
traits::ObligationCause::dummy(), traits::ObligationCause::dummy(),
trait_id, 0, ty, trait_id, 0, ty,
vec![]); ty_params.unwrap_or_default());
traits::SelectionContext::new(&infcx).evaluate_obligation_conservatively(&obligation) traits::SelectionContext::new(&infcx).evaluate_obligation_conservatively(&obligation)
} }

View File

@ -21,4 +21,6 @@ fn with_to_string(x : &str) {
// as of 2015-08-14 // as of 2015-08-14
x != String::from("foo"); //~ERROR this creates an owned instance x != String::from("foo"); //~ERROR this creates an owned instance
42.to_string() == "42";
} }