mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-27 15:11:30 +00:00
Merge pull request #2117 from sinkuu/improve_take_by_value
Improve needless_pass_by_value
This commit is contained in:
commit
73a1dd8e7f
6 changed files with 293 additions and 147 deletions
|
@ -1,7 +1,7 @@
|
||||||
use rustc::hir::*;
|
use rustc::hir::*;
|
||||||
use rustc::hir::intravisit::FnKind;
|
use rustc::hir::intravisit::FnKind;
|
||||||
use rustc::lint::*;
|
use rustc::lint::*;
|
||||||
use rustc::ty::{self, TypeFoldable};
|
use rustc::ty::{self, RegionKind, TypeFoldable};
|
||||||
use rustc::traits;
|
use rustc::traits;
|
||||||
use rustc::middle::expr_use_visitor as euv;
|
use rustc::middle::expr_use_visitor as euv;
|
||||||
use rustc::middle::mem_categorization as mc;
|
use rustc::middle::mem_categorization as mc;
|
||||||
|
@ -9,8 +9,10 @@ use syntax::ast::NodeId;
|
||||||
use syntax_pos::Span;
|
use syntax_pos::Span;
|
||||||
use syntax::errors::DiagnosticBuilder;
|
use syntax::errors::DiagnosticBuilder;
|
||||||
use utils::{get_trait_def_id, implements_trait, in_macro, is_copy, is_self, match_type, multispan_sugg, paths,
|
use utils::{get_trait_def_id, implements_trait, in_macro, is_copy, is_self, match_type, multispan_sugg, paths,
|
||||||
snippet, span_lint_and_then};
|
snippet, snippet_opt, span_lint_and_then};
|
||||||
|
use utils::ptr::get_spans;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// **What it does:** Checks for functions taking arguments by value, but not
|
/// **What it does:** Checks for functions taking arguments by value, but not
|
||||||
/// consuming them in its
|
/// consuming them in its
|
||||||
|
@ -73,18 +75,29 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue {
|
||||||
_ => return,
|
_ => return,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allows these to be passed by value.
|
// Allow `Borrow` or functions to be taken by value
|
||||||
let fn_trait = need!(cx.tcx.lang_items().fn_trait());
|
|
||||||
let asref_trait = need!(get_trait_def_id(cx, &paths::ASREF_TRAIT));
|
|
||||||
let borrow_trait = need!(get_trait_def_id(cx, &paths::BORROW_TRAIT));
|
let borrow_trait = need!(get_trait_def_id(cx, &paths::BORROW_TRAIT));
|
||||||
|
let fn_traits = [
|
||||||
|
need!(cx.tcx.lang_items().fn_trait()),
|
||||||
|
need!(cx.tcx.lang_items().fn_once_trait()),
|
||||||
|
need!(cx.tcx.lang_items().fn_mut_trait()),
|
||||||
|
];
|
||||||
|
|
||||||
|
let sized_trait = need!(cx.tcx.lang_items().sized_trait());
|
||||||
|
|
||||||
let fn_def_id = cx.tcx.hir.local_def_id(node_id);
|
let fn_def_id = cx.tcx.hir.local_def_id(node_id);
|
||||||
|
|
||||||
let preds: Vec<ty::Predicate> = {
|
let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds.to_vec())
|
||||||
traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds.to_vec())
|
.filter(|p| !p.is_global())
|
||||||
.filter(|p| !p.is_global())
|
.filter_map(|pred| if let ty::Predicate::Trait(poly_trait_ref) = pred {
|
||||||
.collect()
|
if poly_trait_ref.def_id() == sized_trait || poly_trait_ref.skip_binder().has_escaping_regions() {
|
||||||
};
|
return None;
|
||||||
|
}
|
||||||
|
Some(poly_trait_ref)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Collect moved variables and spans which will need dereferencings from the
|
// Collect moved variables and spans which will need dereferencings from the
|
||||||
// function body.
|
// function body.
|
||||||
|
@ -102,45 +115,56 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue {
|
||||||
let fn_sig = cx.tcx.fn_sig(fn_def_id);
|
let fn_sig = cx.tcx.fn_sig(fn_def_id);
|
||||||
let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig);
|
let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig);
|
||||||
|
|
||||||
for ((input, &ty), arg) in decl.inputs.iter().zip(fn_sig.inputs()).zip(&body.arguments) {
|
for (idx, ((input, &ty), arg)) in decl.inputs
|
||||||
// Determines whether `ty` implements `Borrow<U>` (U != ty) specifically.
|
.iter()
|
||||||
// This is needed due to the `Borrow<T> for T` blanket impl.
|
.zip(fn_sig.inputs())
|
||||||
let implements_borrow_trait = preds
|
.zip(&body.arguments)
|
||||||
.iter()
|
.enumerate()
|
||||||
.filter_map(|pred| if let ty::Predicate::Trait(ref poly_trait_ref) = *pred {
|
{
|
||||||
Some(poly_trait_ref.skip_binder())
|
// * Exclude a type that is specifically bounded by `Borrow`.
|
||||||
} else {
|
// * Exclude a type whose reference also fulfills its bound.
|
||||||
None
|
// (e.g. `std::convert::AsRef`, `serde::Serialize`)
|
||||||
})
|
let (implements_borrow_trait, all_borrowable_trait) = {
|
||||||
.filter(|tpred| tpred.def_id() == borrow_trait && tpred.self_ty() == ty)
|
let preds = preds
|
||||||
.any(|tpred| {
|
.iter()
|
||||||
tpred
|
.filter(|t| t.skip_binder().self_ty() == ty)
|
||||||
.input_types()
|
.collect::<Vec<_>>();
|
||||||
.nth(1)
|
|
||||||
.expect("Borrow trait must have an parameter") != ty
|
(
|
||||||
});
|
preds.iter().any(|t| t.def_id() == borrow_trait),
|
||||||
|
!preds.is_empty() && preds.iter().all(|t| {
|
||||||
|
implements_trait(
|
||||||
|
cx,
|
||||||
|
cx.tcx.mk_imm_ref(&RegionKind::ReErased, ty),
|
||||||
|
t.def_id(),
|
||||||
|
&t.skip_binder().input_types().skip(1).collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
if_let_chain! {[
|
if_let_chain! {[
|
||||||
!is_self(arg),
|
!is_self(arg),
|
||||||
!ty.is_mutable_pointer(),
|
!ty.is_mutable_pointer(),
|
||||||
!is_copy(cx, ty),
|
!is_copy(cx, ty),
|
||||||
!implements_trait(cx, ty, fn_trait, &[]),
|
!fn_traits.iter().any(|&t| implements_trait(cx, ty, t, &[])),
|
||||||
!implements_trait(cx, ty, asref_trait, &[]),
|
|
||||||
!implements_borrow_trait,
|
!implements_borrow_trait,
|
||||||
|
!all_borrowable_trait,
|
||||||
|
|
||||||
let PatKind::Binding(mode, canonical_id, ..) = arg.pat.node,
|
let PatKind::Binding(mode, canonical_id, ..) = arg.pat.node,
|
||||||
!moved_vars.contains(&canonical_id),
|
!moved_vars.contains(&canonical_id),
|
||||||
], {
|
], {
|
||||||
// Note: `toplevel_ref_arg` warns if `BindByRef`
|
|
||||||
if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut {
|
if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suggestion logic
|
// Dereference suggestion
|
||||||
let sugg = |db: &mut DiagnosticBuilder| {
|
let sugg = |db: &mut DiagnosticBuilder| {
|
||||||
let deref_span = spans_need_deref.get(&canonical_id);
|
let deref_span = spans_need_deref.get(&canonical_id);
|
||||||
if_let_chain! {[
|
if_let_chain! {[
|
||||||
match_type(cx, ty, &paths::VEC),
|
match_type(cx, ty, &paths::VEC),
|
||||||
|
let Some(clone_spans) =
|
||||||
|
get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]),
|
||||||
let TyPath(QPath::Resolved(_, ref path)) = input.node,
|
let TyPath(QPath::Resolved(_, ref path)) = input.node,
|
||||||
let Some(elem_ty) = path.segments.iter()
|
let Some(elem_ty) = path.segments.iter()
|
||||||
.find(|seg| seg.name == "Vec")
|
.find(|seg| seg.name == "Vec")
|
||||||
|
@ -151,34 +175,68 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue {
|
||||||
db.span_suggestion(input.span,
|
db.span_suggestion(input.span,
|
||||||
"consider changing the type to",
|
"consider changing the type to",
|
||||||
slice_ty);
|
slice_ty);
|
||||||
|
|
||||||
|
for (span, suggestion) in clone_spans {
|
||||||
|
db.span_suggestion(
|
||||||
|
span,
|
||||||
|
&snippet_opt(cx, span)
|
||||||
|
.map_or(
|
||||||
|
"change the call to".into(),
|
||||||
|
|x| Cow::from(format!("change `{}` to", x)),
|
||||||
|
),
|
||||||
|
suggestion.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cannot be destructured, no need for `*` suggestion
|
||||||
assert!(deref_span.is_none());
|
assert!(deref_span.is_none());
|
||||||
return; // `Vec` and `String` cannot be destructured - no need for `*` suggestion
|
return;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if match_type(cx, ty, &paths::STRING) {
|
if match_type(cx, ty, &paths::STRING) {
|
||||||
db.span_suggestion(input.span,
|
if let Some(clone_spans) =
|
||||||
"consider changing the type to",
|
get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) {
|
||||||
"&str".to_string());
|
db.span_suggestion(input.span, "consider changing the type to", "&str".to_string());
|
||||||
assert!(deref_span.is_none());
|
|
||||||
return;
|
for (span, suggestion) in clone_spans {
|
||||||
|
db.span_suggestion(
|
||||||
|
span,
|
||||||
|
&snippet_opt(cx, span)
|
||||||
|
.map_or(
|
||||||
|
"change the call to".into(),
|
||||||
|
|x| Cow::from(format!("change `{}` to", x))
|
||||||
|
),
|
||||||
|
suggestion.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(deref_span.is_none());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];
|
let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];
|
||||||
|
|
||||||
// Suggests adding `*` to dereference the added reference.
|
// Suggests adding `*` to dereference the added reference.
|
||||||
if let Some(deref_span) = deref_span {
|
if let Some(deref_span) = deref_span {
|
||||||
spans.extend(deref_span.iter().cloned()
|
spans.extend(
|
||||||
.map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))));
|
deref_span
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))),
|
||||||
|
);
|
||||||
spans.sort_by_key(|&(span, _)| span);
|
spans.sort_by_key(|&(span, _)| span);
|
||||||
}
|
}
|
||||||
multispan_sugg(db, "consider taking a reference instead".to_string(), spans);
|
multispan_sugg(db, "consider taking a reference instead".to_string(), spans);
|
||||||
};
|
};
|
||||||
|
|
||||||
span_lint_and_then(cx,
|
span_lint_and_then(
|
||||||
NEEDLESS_PASS_BY_VALUE,
|
cx,
|
||||||
input.span,
|
NEEDLESS_PASS_BY_VALUE,
|
||||||
"this argument is passed by value, but not consumed in the function body",
|
input.span,
|
||||||
sugg);
|
"this argument is passed by value, but not consumed in the function body",
|
||||||
|
sugg,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,8 +246,7 @@ struct MovedVariablesCtxt<'a, 'tcx: 'a> {
|
||||||
cx: &'a LateContext<'a, 'tcx>,
|
cx: &'a LateContext<'a, 'tcx>,
|
||||||
moved_vars: HashSet<NodeId>,
|
moved_vars: HashSet<NodeId>,
|
||||||
/// Spans which need to be prefixed with `*` for dereferencing the
|
/// Spans which need to be prefixed with `*` for dereferencing the
|
||||||
/// suggested additional
|
/// suggested additional reference.
|
||||||
/// reference.
|
|
||||||
spans_need_deref: HashMap<NodeId, HashSet<Span>>,
|
spans_need_deref: HashMap<NodeId, HashSet<Span>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,9 +270,7 @@ impl<'a, 'tcx> MovedVariablesCtxt<'a, 'tcx> {
|
||||||
fn non_moving_pat(&mut self, matched_pat: &Pat, cmt: mc::cmt<'tcx>) {
|
fn non_moving_pat(&mut self, matched_pat: &Pat, cmt: mc::cmt<'tcx>) {
|
||||||
let cmt = unwrap_downcast_or_interior(cmt);
|
let cmt = unwrap_downcast_or_interior(cmt);
|
||||||
|
|
||||||
if_let_chain! {[
|
if let mc::Categorization::Local(vid) = cmt.cat {
|
||||||
let mc::Categorization::Local(vid) = cmt.cat,
|
|
||||||
], {
|
|
||||||
let mut id = matched_pat.id;
|
let mut id = matched_pat.id;
|
||||||
loop {
|
loop {
|
||||||
let parent = self.cx.tcx.hir.get_parent_node(id);
|
let parent = self.cx.tcx.hir.get_parent_node(id);
|
||||||
|
@ -235,7 +290,7 @@ impl<'a, 'tcx> MovedVariablesCtxt<'a, 'tcx> {
|
||||||
.or_insert_with(HashSet::new)
|
.or_insert_with(HashSet::new)
|
||||||
.insert(c.span);
|
.insert(c.span);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
map::Node::NodeStmt(s) => {
|
map::Node::NodeStmt(s) => {
|
||||||
// `let <pat> = x;`
|
// `let <pat> = x;`
|
||||||
|
@ -251,13 +306,13 @@ impl<'a, 'tcx> MovedVariablesCtxt<'a, 'tcx> {
|
||||||
.map(|e| e.span)
|
.map(|e| e.span)
|
||||||
.expect("`let` stmt without init aren't caught by match_pat"));
|
.expect("`let` stmt without init aren't caught by match_pat"));
|
||||||
}}
|
}}
|
||||||
}
|
},
|
||||||
|
|
||||||
_ => {}
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,15 @@
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use rustc::hir::*;
|
use rustc::hir::*;
|
||||||
use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
|
|
||||||
use rustc::hir::map::NodeItem;
|
use rustc::hir::map::NodeItem;
|
||||||
use rustc::lint::*;
|
use rustc::lint::*;
|
||||||
use rustc::ty;
|
use rustc::ty;
|
||||||
use syntax::ast::{Name, NodeId};
|
use syntax::ast::NodeId;
|
||||||
use syntax::codemap::Span;
|
use syntax::codemap::Span;
|
||||||
use syntax_pos::MultiSpan;
|
use syntax_pos::MultiSpan;
|
||||||
use utils::{get_pat_name, match_qpath, match_type, match_var, paths,
|
use utils::{match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_then,
|
||||||
snippet, snippet_opt, span_lint, span_lint_and_then,
|
|
||||||
walk_ptrs_hir_ty};
|
walk_ptrs_hir_ty};
|
||||||
|
use utils::ptr::get_spans;
|
||||||
|
|
||||||
/// **What it does:** This lint checks for function arguments of type `&String`
|
/// **What it does:** This lint checks for function arguments of type `&String`
|
||||||
/// or `&Vec` unless the references are mutable. It will also suggest you
|
/// or `&Vec` unless the references are mutable. It will also suggest you
|
||||||
|
@ -164,7 +163,7 @@ fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId, opt_body_id: Option<
|
||||||
], {
|
], {
|
||||||
ty_snippet = snippet_opt(cx, parameters.types[0].span);
|
ty_snippet = snippet_opt(cx, parameters.types[0].span);
|
||||||
});
|
});
|
||||||
if let Ok(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) {
|
if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) {
|
||||||
span_lint_and_then(
|
span_lint_and_then(
|
||||||
cx,
|
cx,
|
||||||
PTR_ARG,
|
PTR_ARG,
|
||||||
|
@ -187,7 +186,7 @@ fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId, opt_body_id: Option<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if match_type(cx, ty, &paths::STRING) {
|
} else if match_type(cx, ty, &paths::STRING) {
|
||||||
if let Ok(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) {
|
if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) {
|
||||||
span_lint_and_then(
|
span_lint_and_then(
|
||||||
cx,
|
cx,
|
||||||
PTR_ARG,
|
PTR_ARG,
|
||||||
|
@ -234,66 +233,6 @@ fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId, opt_body_id: Option<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_spans(cx: &LateContext, opt_body_id: Option<BodyId>, idx: usize, replacements: &'static [(&'static str, &'static str)]) -> Result<Vec<(Span, Cow<'static, str>)>, ()> {
|
|
||||||
if let Some(body) = opt_body_id.map(|id| cx.tcx.hir.body(id)) {
|
|
||||||
get_binding_name(&body.arguments[idx]).map_or_else(|| Ok(vec![]),
|
|
||||||
|name| extract_clone_suggestions(cx, name, replacements, body))
|
|
||||||
} else {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_clone_suggestions<'a, 'tcx: 'a>(cx: &LateContext<'a, 'tcx>, name: Name, replace: &'static [(&'static str, &'static str)], body: &'tcx Body) -> Result<Vec<(Span, Cow<'static, str>)>, ()> {
|
|
||||||
let mut visitor = PtrCloneVisitor {
|
|
||||||
cx,
|
|
||||||
name,
|
|
||||||
replace,
|
|
||||||
spans: vec![],
|
|
||||||
abort: false,
|
|
||||||
};
|
|
||||||
visitor.visit_body(body);
|
|
||||||
if visitor.abort { Err(()) } else { Ok(visitor.spans) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PtrCloneVisitor<'a, 'tcx: 'a> {
|
|
||||||
cx: &'a LateContext<'a, 'tcx>,
|
|
||||||
name: Name,
|
|
||||||
replace: &'static [(&'static str, &'static str)],
|
|
||||||
spans: Vec<(Span, Cow<'static, str>)>,
|
|
||||||
abort: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'tcx: 'a> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
|
|
||||||
fn visit_expr(&mut self, expr: &'tcx Expr) {
|
|
||||||
if self.abort { return; }
|
|
||||||
if let ExprMethodCall(ref seg, _, ref args) = expr.node {
|
|
||||||
if args.len() == 1 && match_var(&args[0], self.name) {
|
|
||||||
if seg.name == "capacity" {
|
|
||||||
self.abort = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for &(fn_name, suffix) in self.replace {
|
|
||||||
if seg.name == fn_name {
|
|
||||||
self.spans.push((expr.span, snippet(self.cx, args[0].span, "_") + suffix));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
walk_expr(self, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
|
|
||||||
NestedVisitorMap::None
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_binding_name(arg: &Arg) -> Option<Name> {
|
|
||||||
get_pat_name(&arg.pat)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_rptr_lm(ty: &Ty) -> Option<(&Lifetime, Mutability, Span)> {
|
fn get_rptr_lm(ty: &Ty) -> Option<(&Lifetime, Mutability, Span)> {
|
||||||
if let Ty_::TyRptr(ref lt, ref m) = ty.node {
|
if let Ty_::TyRptr(ref lt, ref m) = ty.node {
|
||||||
Some((lt, m.mutbl, ty.span))
|
Some((lt, m.mutbl, ty.span))
|
||||||
|
|
|
@ -32,6 +32,7 @@ pub mod sugg;
|
||||||
pub mod inspector;
|
pub mod inspector;
|
||||||
pub mod internal_lints;
|
pub mod internal_lints;
|
||||||
pub mod author;
|
pub mod author;
|
||||||
|
pub mod ptr;
|
||||||
pub use self::hir_utils::{SpanlessEq, SpanlessHash};
|
pub use self::hir_utils::{SpanlessEq, SpanlessHash};
|
||||||
|
|
||||||
pub type MethodArgs = HirVec<P<Expr>>;
|
pub type MethodArgs = HirVec<P<Expr>>;
|
||||||
|
|
83
clippy_lints/src/utils/ptr.rs
Normal file
83
clippy_lints/src/utils/ptr.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use rustc::hir::*;
|
||||||
|
use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
|
||||||
|
use rustc::lint::LateContext;
|
||||||
|
use syntax::ast::Name;
|
||||||
|
use syntax::codemap::Span;
|
||||||
|
use utils::{get_pat_name, match_var, snippet};
|
||||||
|
|
||||||
|
pub fn get_spans(
|
||||||
|
cx: &LateContext,
|
||||||
|
opt_body_id: Option<BodyId>,
|
||||||
|
idx: usize,
|
||||||
|
replacements: &'static [(&'static str, &'static str)],
|
||||||
|
) -> Option<Vec<(Span, Cow<'static, str>)>> {
|
||||||
|
if let Some(body) = opt_body_id.map(|id| cx.tcx.hir.body(id)) {
|
||||||
|
get_binding_name(&body.arguments[idx])
|
||||||
|
.map_or_else(|| Some(vec![]), |name| extract_clone_suggestions(cx, name, replacements, body))
|
||||||
|
} else {
|
||||||
|
Some(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_clone_suggestions<'a, 'tcx: 'a>(
|
||||||
|
cx: &LateContext<'a, 'tcx>,
|
||||||
|
name: Name,
|
||||||
|
replace: &'static [(&'static str, &'static str)],
|
||||||
|
body: &'tcx Body,
|
||||||
|
) -> Option<Vec<(Span, Cow<'static, str>)>> {
|
||||||
|
let mut visitor = PtrCloneVisitor {
|
||||||
|
cx,
|
||||||
|
name,
|
||||||
|
replace,
|
||||||
|
spans: vec![],
|
||||||
|
abort: false,
|
||||||
|
};
|
||||||
|
visitor.visit_body(body);
|
||||||
|
if visitor.abort {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(visitor.spans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PtrCloneVisitor<'a, 'tcx: 'a> {
|
||||||
|
cx: &'a LateContext<'a, 'tcx>,
|
||||||
|
name: Name,
|
||||||
|
replace: &'static [(&'static str, &'static str)],
|
||||||
|
spans: Vec<(Span, Cow<'static, str>)>,
|
||||||
|
abort: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx: 'a> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
|
||||||
|
fn visit_expr(&mut self, expr: &'tcx Expr) {
|
||||||
|
if self.abort {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let ExprMethodCall(ref seg, _, ref args) = expr.node {
|
||||||
|
if args.len() == 1 && match_var(&args[0], self.name) {
|
||||||
|
if seg.name == "capacity" {
|
||||||
|
self.abort = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for &(fn_name, suffix) in self.replace {
|
||||||
|
if seg.name == fn_name {
|
||||||
|
self.spans
|
||||||
|
.push((expr.span, snippet(self.cx, args[0].span, "_") + suffix));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
walk_expr(self, expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
|
||||||
|
NestedVisitorMap::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_binding_name(arg: &Arg) -> Option<Name> {
|
||||||
|
get_pat_name(&arg.pat)
|
||||||
|
}
|
|
@ -4,6 +4,9 @@
|
||||||
#![warn(needless_pass_by_value)]
|
#![warn(needless_pass_by_value)]
|
||||||
#![allow(dead_code, single_match, if_let_redundant_pattern_matching, many_single_char_names)]
|
#![allow(dead_code, single_match, if_let_redundant_pattern_matching, many_single_char_names)]
|
||||||
|
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::convert::AsRef;
|
||||||
|
|
||||||
// `v` should be warned
|
// `v` should be warned
|
||||||
// `w`, `x` and `y` are allowed (moved or mutated)
|
// `w`, `x` and `y` are allowed (moved or mutated)
|
||||||
fn foo<T: Default>(v: Vec<T>, w: Vec<T>, mut x: Vec<T>, y: Vec<T>) -> Vec<T> {
|
fn foo<T: Default>(v: Vec<T>, w: Vec<T>, mut x: Vec<T>, y: Vec<T>) -> Vec<T> {
|
||||||
|
@ -25,10 +28,11 @@ fn bar(x: String, y: Wrapper) {
|
||||||
assert_eq!(y.0.len(), 42);
|
assert_eq!(y.0.len(), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
// U implements `Borrow<U>`, but should be warned correctly
|
// V implements `Borrow<V>`, but should be warned correctly
|
||||||
fn test_borrow_trait<T: std::borrow::Borrow<str>, U>(t: T, u: U) {
|
fn test_borrow_trait<T: Borrow<str>, U: AsRef<str>, V>(t: T, u: U, v: V) {
|
||||||
println!("{}", t.borrow());
|
println!("{}", t.borrow());
|
||||||
consume(&u);
|
println!("{}", u.as_ref());
|
||||||
|
consume(&v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ok
|
// ok
|
||||||
|
@ -59,4 +63,20 @@ fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
|
||||||
println!("{}", t);
|
println!("{}", t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait Foo {}
|
||||||
|
|
||||||
|
// `S: Serialize` can be passed by value
|
||||||
|
trait Serialize {}
|
||||||
|
impl<'a, T> Serialize for &'a T where T: Serialize {}
|
||||||
|
impl Serialize for i32 {}
|
||||||
|
|
||||||
|
fn test_blanket_ref<T: Foo, S: Serialize>(_foo: T, _serializable: S) {}
|
||||||
|
|
||||||
|
fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
|
||||||
|
s.capacity();
|
||||||
|
let _ = t.clone();
|
||||||
|
u.capacity();
|
||||||
|
let _ = v.clone();
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,58 +1,106 @@
|
||||||
error: this argument is passed by value, but not consumed in the function body
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
--> $DIR/needless_pass_by_value.rs:9:23
|
--> $DIR/needless_pass_by_value.rs:12:23
|
||||||
|
|
|
|
||||||
9 | fn foo<T: Default>(v: Vec<T>, w: Vec<T>, mut x: Vec<T>, y: Vec<T>) -> Vec<T> {
|
12 | fn foo<T: Default>(v: Vec<T>, w: Vec<T>, mut x: Vec<T>, y: Vec<T>) -> Vec<T> {
|
||||||
| ^^^^^^ help: consider changing the type to: `&[T]`
|
| ^^^^^^ help: consider changing the type to: `&[T]`
|
||||||
|
|
|
|
||||||
= note: `-D needless-pass-by-value` implied by `-D warnings`
|
= note: `-D needless-pass-by-value` implied by `-D warnings`
|
||||||
|
|
||||||
error: this argument is passed by value, but not consumed in the function body
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
--> $DIR/needless_pass_by_value.rs:23:11
|
--> $DIR/needless_pass_by_value.rs:26:11
|
||||||
|
|
|
|
||||||
23 | fn bar(x: String, y: Wrapper) {
|
26 | fn bar(x: String, y: Wrapper) {
|
||||||
| ^^^^^^ help: consider changing the type to: `&str`
|
| ^^^^^^ help: consider changing the type to: `&str`
|
||||||
|
|
||||||
error: this argument is passed by value, but not consumed in the function body
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
--> $DIR/needless_pass_by_value.rs:23:22
|
--> $DIR/needless_pass_by_value.rs:26:22
|
||||||
|
|
|
|
||||||
23 | fn bar(x: String, y: Wrapper) {
|
26 | fn bar(x: String, y: Wrapper) {
|
||||||
| ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
|
| ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
|
||||||
|
|
||||||
error: this argument is passed by value, but not consumed in the function body
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
--> $DIR/needless_pass_by_value.rs:29:63
|
--> $DIR/needless_pass_by_value.rs:32:71
|
||||||
|
|
|
|
||||||
29 | fn test_borrow_trait<T: std::borrow::Borrow<str>, U>(t: T, u: U) {
|
32 | fn test_borrow_trait<T: Borrow<str>, U: AsRef<str>, V>(t: T, u: U, v: V) {
|
||||||
| ^ help: consider taking a reference instead: `&U`
|
| ^ help: consider taking a reference instead: `&V`
|
||||||
|
|
||||||
error: this argument is passed by value, but not consumed in the function body
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
--> $DIR/needless_pass_by_value.rs:40:18
|
--> $DIR/needless_pass_by_value.rs:44:18
|
||||||
|
|
|
|
||||||
40 | fn test_match(x: Option<Option<String>>, y: Option<Option<String>>) {
|
44 | fn test_match(x: Option<Option<String>>, y: Option<Option<String>>) {
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
help: consider taking a reference instead
|
help: consider taking a reference instead
|
||||||
|
|
|
|
||||||
40 | fn test_match(x: &Option<Option<String>>, y: Option<Option<String>>) {
|
44 | fn test_match(x: &Option<Option<String>>, y: Option<Option<String>>) {
|
||||||
41 | match *x {
|
45 | match *x {
|
||||||
|
|
|
|
||||||
|
|
||||||
error: this argument is passed by value, but not consumed in the function body
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
--> $DIR/needless_pass_by_value.rs:53:24
|
--> $DIR/needless_pass_by_value.rs:57:24
|
||||||
|
|
|
|
||||||
53 | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
|
57 | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
|
||||||
| ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
|
| ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
|
||||||
|
|
||||||
error: this argument is passed by value, but not consumed in the function body
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
--> $DIR/needless_pass_by_value.rs:53:36
|
--> $DIR/needless_pass_by_value.rs:57:36
|
||||||
|
|
|
|
||||||
53 | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
|
57 | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
|
|
||||||
help: consider taking a reference instead
|
help: consider taking a reference instead
|
||||||
|
|
|
|
||||||
53 | fn test_destructure(x: Wrapper, y: &Wrapper, z: Wrapper) {
|
57 | fn test_destructure(x: Wrapper, y: &Wrapper, z: Wrapper) {
|
||||||
54 | let Wrapper(s) = z; // moved
|
58 | let Wrapper(s) = z; // moved
|
||||||
55 | let Wrapper(ref t) = *y; // not moved
|
59 | let Wrapper(ref t) = *y; // not moved
|
||||||
56 | let Wrapper(_) = *y; // still not moved
|
60 | let Wrapper(_) = *y; // still not moved
|
||||||
|
|
|
|
||||||
|
|
||||||
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
|
--> $DIR/needless_pass_by_value.rs:73:49
|
||||||
|
|
|
||||||
|
73 | fn test_blanket_ref<T: Foo, S: Serialize>(_foo: T, _serializable: S) {}
|
||||||
|
| ^ help: consider taking a reference instead: `&T`
|
||||||
|
|
||||||
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
|
--> $DIR/needless_pass_by_value.rs:75:18
|
||||||
|
|
|
||||||
|
75 | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
|
||||||
|
| ^^^^^^ help: consider taking a reference instead: `&String`
|
||||||
|
|
||||||
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
|
--> $DIR/needless_pass_by_value.rs:75:29
|
||||||
|
|
|
||||||
|
75 | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
|
||||||
|
| ^^^^^^
|
||||||
|
|
|
||||||
|
help: consider changing the type to
|
||||||
|
|
|
||||||
|
75 | fn issue_2114(s: String, t: &str, u: Vec<i32>, v: Vec<i32>) {
|
||||||
|
| ^^^^
|
||||||
|
help: change `t.clone()` to
|
||||||
|
|
|
||||||
|
77 | let _ = t.to_string();
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
|
--> $DIR/needless_pass_by_value.rs:75:40
|
||||||
|
|
|
||||||
|
75 | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
|
||||||
|
| ^^^^^^^^ help: consider taking a reference instead: `&Vec<i32>`
|
||||||
|
|
||||||
|
error: this argument is passed by value, but not consumed in the function body
|
||||||
|
--> $DIR/needless_pass_by_value.rs:75:53
|
||||||
|
|
|
||||||
|
75 | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
help: consider changing the type to
|
||||||
|
|
|
||||||
|
75 | fn issue_2114(s: String, t: String, u: Vec<i32>, v: &[i32]) {
|
||||||
|
| ^^^^^^
|
||||||
|
help: change `v.clone()` to
|
||||||
|
|
|
||||||
|
79 | let _ = v.to_owned();
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue