Skip to content

Commit 73a1dd8

Browse files
authored
Merge pull request #2117 from sinkuu/improve_take_by_value
Improve needless_pass_by_value
2 parents 18483f8 + 8ffec33 commit 73a1dd8

File tree

6 files changed

+293
-147
lines changed

6 files changed

+293
-147
lines changed

clippy_lints/src/needless_pass_by_value.rs

Lines changed: 108 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
use rustc::hir::*;
22
use rustc::hir::intravisit::FnKind;
33
use rustc::lint::*;
4-
use rustc::ty::{self, TypeFoldable};
4+
use rustc::ty::{self, RegionKind, TypeFoldable};
55
use rustc::traits;
66
use rustc::middle::expr_use_visitor as euv;
77
use rustc::middle::mem_categorization as mc;
88
use syntax::ast::NodeId;
99
use syntax_pos::Span;
1010
use syntax::errors::DiagnosticBuilder;
1111
use utils::{get_trait_def_id, implements_trait, in_macro, is_copy, is_self, match_type, multispan_sugg, paths,
12-
snippet, span_lint_and_then};
12+
snippet, snippet_opt, span_lint_and_then};
13+
use utils::ptr::get_spans;
1314
use std::collections::{HashMap, HashSet};
15+
use std::borrow::Cow;
1416

1517
/// **What it does:** Checks for functions taking arguments by value, but not
1618
/// consuming them in its
@@ -73,18 +75,29 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue {
7375
_ => return,
7476
}
7577

76-
// Allows these to be passed by value.
77-
let fn_trait = need!(cx.tcx.lang_items().fn_trait());
78-
let asref_trait = need!(get_trait_def_id(cx, &paths::ASREF_TRAIT));
78+
// Allow `Borrow` or functions to be taken by value
7979
let borrow_trait = need!(get_trait_def_id(cx, &paths::BORROW_TRAIT));
80+
let fn_traits = [
81+
need!(cx.tcx.lang_items().fn_trait()),
82+
need!(cx.tcx.lang_items().fn_once_trait()),
83+
need!(cx.tcx.lang_items().fn_mut_trait()),
84+
];
85+
86+
let sized_trait = need!(cx.tcx.lang_items().sized_trait());
8087

8188
let fn_def_id = cx.tcx.hir.local_def_id(node_id);
8289

83-
let preds: Vec<ty::Predicate> = {
84-
traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds.to_vec())
85-
.filter(|p| !p.is_global())
86-
.collect()
87-
};
90+
let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds.to_vec())
91+
.filter(|p| !p.is_global())
92+
.filter_map(|pred| if let ty::Predicate::Trait(poly_trait_ref) = pred {
93+
if poly_trait_ref.def_id() == sized_trait || poly_trait_ref.skip_binder().has_escaping_regions() {
94+
return None;
95+
}
96+
Some(poly_trait_ref)
97+
} else {
98+
None
99+
})
100+
.collect::<Vec<_>>();
88101

89102
// Collect moved variables and spans which will need dereferencings from the
90103
// function body.
@@ -102,45 +115,56 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue {
102115
let fn_sig = cx.tcx.fn_sig(fn_def_id);
103116
let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig);
104117

105-
for ((input, &ty), arg) in decl.inputs.iter().zip(fn_sig.inputs()).zip(&body.arguments) {
106-
// Determines whether `ty` implements `Borrow<U>` (U != ty) specifically.
107-
// This is needed due to the `Borrow<T> for T` blanket impl.
108-
let implements_borrow_trait = preds
109-
.iter()
110-
.filter_map(|pred| if let ty::Predicate::Trait(ref poly_trait_ref) = *pred {
111-
Some(poly_trait_ref.skip_binder())
112-
} else {
113-
None
114-
})
115-
.filter(|tpred| tpred.def_id() == borrow_trait && tpred.self_ty() == ty)
116-
.any(|tpred| {
117-
tpred
118-
.input_types()
119-
.nth(1)
120-
.expect("Borrow trait must have an parameter") != ty
121-
});
118+
for (idx, ((input, &ty), arg)) in decl.inputs
119+
.iter()
120+
.zip(fn_sig.inputs())
121+
.zip(&body.arguments)
122+
.enumerate()
123+
{
124+
// * Exclude a type that is specifically bounded by `Borrow`.
125+
// * Exclude a type whose reference also fulfills its bound.
126+
// (e.g. `std::convert::AsRef`, `serde::Serialize`)
127+
let (implements_borrow_trait, all_borrowable_trait) = {
128+
let preds = preds
129+
.iter()
130+
.filter(|t| t.skip_binder().self_ty() == ty)
131+
.collect::<Vec<_>>();
132+
133+
(
134+
preds.iter().any(|t| t.def_id() == borrow_trait),
135+
!preds.is_empty() && preds.iter().all(|t| {
136+
implements_trait(
137+
cx,
138+
cx.tcx.mk_imm_ref(&RegionKind::ReErased, ty),
139+
t.def_id(),
140+
&t.skip_binder().input_types().skip(1).collect::<Vec<_>>(),
141+
)
142+
}),
143+
)
144+
};
122145

123146
if_let_chain! {[
124147
!is_self(arg),
125148
!ty.is_mutable_pointer(),
126149
!is_copy(cx, ty),
127-
!implements_trait(cx, ty, fn_trait, &[]),
128-
!implements_trait(cx, ty, asref_trait, &[]),
150+
!fn_traits.iter().any(|&t| implements_trait(cx, ty, t, &[])),
129151
!implements_borrow_trait,
152+
!all_borrowable_trait,
130153

131154
let PatKind::Binding(mode, canonical_id, ..) = arg.pat.node,
132155
!moved_vars.contains(&canonical_id),
133156
], {
134-
// Note: `toplevel_ref_arg` warns if `BindByRef`
135157
if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut {
136158
continue;
137159
}
138160

139-
// Suggestion logic
161+
// Dereference suggestion
140162
let sugg = |db: &mut DiagnosticBuilder| {
141163
let deref_span = spans_need_deref.get(&canonical_id);
142164
if_let_chain! {[
143165
match_type(cx, ty, &paths::VEC),
166+
let Some(clone_spans) =
167+
get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]),
144168
let TyPath(QPath::Resolved(_, ref path)) = input.node,
145169
let Some(elem_ty) = path.segments.iter()
146170
.find(|seg| seg.name == "Vec")
@@ -151,34 +175,68 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue {
151175
db.span_suggestion(input.span,
152176
"consider changing the type to",
153177
slice_ty);
178+
179+
for (span, suggestion) in clone_spans {
180+
db.span_suggestion(
181+
span,
182+
&snippet_opt(cx, span)
183+
.map_or(
184+
"change the call to".into(),
185+
|x| Cow::from(format!("change `{}` to", x)),
186+
),
187+
suggestion.into()
188+
);
189+
}
190+
191+
// cannot be destructured, no need for `*` suggestion
154192
assert!(deref_span.is_none());
155-
return; // `Vec` and `String` cannot be destructured - no need for `*` suggestion
193+
return;
156194
}}
157195

158196
if match_type(cx, ty, &paths::STRING) {
159-
db.span_suggestion(input.span,
160-
"consider changing the type to",
161-
"&str".to_string());
162-
assert!(deref_span.is_none());
163-
return;
197+
if let Some(clone_spans) =
198+
get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) {
199+
db.span_suggestion(input.span, "consider changing the type to", "&str".to_string());
200+
201+
for (span, suggestion) in clone_spans {
202+
db.span_suggestion(
203+
span,
204+
&snippet_opt(cx, span)
205+
.map_or(
206+
"change the call to".into(),
207+
|x| Cow::from(format!("change `{}` to", x))
208+
),
209+
suggestion.into(),
210+
);
211+
}
212+
213+
assert!(deref_span.is_none());
214+
return;
215+
}
164216
}
165217

166218
let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];
167219

168220
// Suggests adding `*` to dereference the added reference.
169221
if let Some(deref_span) = deref_span {
170-
spans.extend(deref_span.iter().cloned()
171-
.map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))));
222+
spans.extend(
223+
deref_span
224+
.iter()
225+
.cloned()
226+
.map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))),
227+
);
172228
spans.sort_by_key(|&(span, _)| span);
173229
}
174230
multispan_sugg(db, "consider taking a reference instead".to_string(), spans);
175231
};
176232

177-
span_lint_and_then(cx,
178-
NEEDLESS_PASS_BY_VALUE,
179-
input.span,
180-
"this argument is passed by value, but not consumed in the function body",
181-
sugg);
233+
span_lint_and_then(
234+
cx,
235+
NEEDLESS_PASS_BY_VALUE,
236+
input.span,
237+
"this argument is passed by value, but not consumed in the function body",
238+
sugg,
239+
);
182240
}}
183241
}
184242
}
@@ -188,8 +246,7 @@ struct MovedVariablesCtxt<'a, 'tcx: 'a> {
188246
cx: &'a LateContext<'a, 'tcx>,
189247
moved_vars: HashSet<NodeId>,
190248
/// Spans which need to be prefixed with `*` for dereferencing the
191-
/// suggested additional
192-
/// reference.
249+
/// suggested additional reference.
193250
spans_need_deref: HashMap<NodeId, HashSet<Span>>,
194251
}
195252

@@ -213,9 +270,7 @@ impl<'a, 'tcx> MovedVariablesCtxt<'a, 'tcx> {
213270
fn non_moving_pat(&mut self, matched_pat: &Pat, cmt: mc::cmt<'tcx>) {
214271
let cmt = unwrap_downcast_or_interior(cmt);
215272

216-
if_let_chain! {[
217-
let mc::Categorization::Local(vid) = cmt.cat,
218-
], {
273+
if let mc::Categorization::Local(vid) = cmt.cat {
219274
let mut id = matched_pat.id;
220275
loop {
221276
let parent = self.cx.tcx.hir.get_parent_node(id);
@@ -235,7 +290,7 @@ impl<'a, 'tcx> MovedVariablesCtxt<'a, 'tcx> {
235290
.or_insert_with(HashSet::new)
236291
.insert(c.span);
237292
}
238-
}
293+
},
239294

240295
map::Node::NodeStmt(s) => {
241296
// `let <pat> = x;`
@@ -251,13 +306,13 @@ impl<'a, 'tcx> MovedVariablesCtxt<'a, 'tcx> {
251306
.map(|e| e.span)
252307
.expect("`let` stmt without init aren't caught by match_pat"));
253308
}}
254-
}
309+
},
255310

256-
_ => {}
311+
_ => {},
257312
}
258313
}
259314
}
260-
}}
315+
}
261316
}
262317
}
263318

clippy_lints/src/ptr.rs

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
33
use std::borrow::Cow;
44
use rustc::hir::*;
5-
use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
65
use rustc::hir::map::NodeItem;
76
use rustc::lint::*;
87
use rustc::ty;
9-
use syntax::ast::{Name, NodeId};
8+
use syntax::ast::NodeId;
109
use syntax::codemap::Span;
1110
use syntax_pos::MultiSpan;
12-
use utils::{get_pat_name, match_qpath, match_type, match_var, paths,
13-
snippet, snippet_opt, span_lint, span_lint_and_then,
11+
use utils::{match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_then,
1412
walk_ptrs_hir_ty};
13+
use utils::ptr::get_spans;
1514

1615
/// **What it does:** This lint checks for function arguments of type `&String`
1716
/// 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<
164163
], {
165164
ty_snippet = snippet_opt(cx, parameters.types[0].span);
166165
});
167-
if let Ok(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) {
166+
if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) {
168167
span_lint_and_then(
169168
cx,
170169
PTR_ARG,
@@ -187,7 +186,7 @@ fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId, opt_body_id: Option<
187186
);
188187
}
189188
} else if match_type(cx, ty, &paths::STRING) {
190-
if let Ok(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) {
189+
if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) {
191190
span_lint_and_then(
192191
cx,
193192
PTR_ARG,
@@ -234,66 +233,6 @@ fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId, opt_body_id: Option<
234233
}
235234
}
236235

237-
fn get_spans(cx: &LateContext, opt_body_id: Option<BodyId>, idx: usize, replacements: &'static [(&'static str, &'static str)]) -> Result<Vec<(Span, Cow<'static, str>)>, ()> {
238-
if let Some(body) = opt_body_id.map(|id| cx.tcx.hir.body(id)) {
239-
get_binding_name(&body.arguments[idx]).map_or_else(|| Ok(vec![]),
240-
|name| extract_clone_suggestions(cx, name, replacements, body))
241-
} else {
242-
Ok(vec![])
243-
}
244-
}
245-
246-
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>)>, ()> {
247-
let mut visitor = PtrCloneVisitor {
248-
cx,
249-
name,
250-
replace,
251-
spans: vec![],
252-
abort: false,
253-
};
254-
visitor.visit_body(body);
255-
if visitor.abort { Err(()) } else { Ok(visitor.spans) }
256-
}
257-
258-
struct PtrCloneVisitor<'a, 'tcx: 'a> {
259-
cx: &'a LateContext<'a, 'tcx>,
260-
name: Name,
261-
replace: &'static [(&'static str, &'static str)],
262-
spans: Vec<(Span, Cow<'static, str>)>,
263-
abort: bool,
264-
}
265-
266-
impl<'a, 'tcx: 'a> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
267-
fn visit_expr(&mut self, expr: &'tcx Expr) {
268-
if self.abort { return; }
269-
if let ExprMethodCall(ref seg, _, ref args) = expr.node {
270-
if args.len() == 1 && match_var(&args[0], self.name) {
271-
if seg.name == "capacity" {
272-
self.abort = true;
273-
return;
274-
}
275-
for &(fn_name, suffix) in self.replace {
276-
if seg.name == fn_name {
277-
self.spans.push((expr.span, snippet(self.cx, args[0].span, "_") + suffix));
278-
return;
279-
}
280-
}
281-
}
282-
return;
283-
}
284-
walk_expr(self, expr);
285-
}
286-
287-
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
288-
NestedVisitorMap::None
289-
}
290-
291-
}
292-
293-
fn get_binding_name(arg: &Arg) -> Option<Name> {
294-
get_pat_name(&arg.pat)
295-
}
296-
297236
fn get_rptr_lm(ty: &Ty) -> Option<(&Lifetime, Mutability, Span)> {
298237
if let Ty_::TyRptr(ref lt, ref m) = ty.node {
299238
Some((lt, m.mutbl, ty.span))

clippy_lints/src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod sugg;
3232
pub mod inspector;
3333
pub mod internal_lints;
3434
pub mod author;
35+
pub mod ptr;
3536
pub use self::hir_utils::{SpanlessEq, SpanlessHash};
3637

3738
pub type MethodArgs = HirVec<P<Expr>>;

0 commit comments

Comments
 (0)