Skip to content

Commit 2bb8efd

Browse files
authored
Merge pull request #2058 from rust-lang-nursery/ptr_arg-vs-clone
add suggestions for .clone() in ptr_arg fns
2 parents b3d8192 + 72be166 commit 2bb8efd

File tree

6 files changed

+189
-45
lines changed

6 files changed

+189
-45
lines changed

clippy_lints/src/bytecount.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use rustc::hir::*;
22
use rustc::lint::*;
33
use rustc::ty;
44
use syntax::ast::{Name, UintTy};
5-
use utils::{contains_name, match_type, paths, single_segment_path, snippet, span_lint_and_sugg, walk_ptrs_ty};
5+
use utils::{contains_name, get_pat_name, match_type, paths, single_segment_path,
6+
snippet, span_lint_and_sugg, walk_ptrs_ty};
67

78
/// **What it does:** Checks for naive byte counts
89
///
@@ -93,15 +94,6 @@ fn check_arg(name: Name, arg: Name, needle: &Expr) -> bool {
9394
name == arg && !contains_name(name, needle)
9495
}
9596

96-
fn get_pat_name(pat: &Pat) -> Option<Name> {
97-
match pat.node {
98-
PatKind::Binding(_, _, ref spname, _) => Some(spname.node),
99-
PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.name),
100-
PatKind::Box(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p),
101-
_ => None,
102-
}
103-
}
104-
10597
fn get_path_name(expr: &Expr) -> Option<Name> {
10698
match expr.node {
10799
ExprBox(ref e) | ExprAddrOf(_, ref e) | ExprUnary(UnOp::UnDeref, ref e) => get_path_name(e),

clippy_lints/src/loops.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use utils::sugg;
1616
use utils::const_to_u64;
1717

1818
use utils::{get_enclosing_block, get_parent_expr, higher, in_external_macro, is_integer_literal, is_refutable,
19-
last_path_segment, match_trait_method, match_type, multispan_sugg, snippet, snippet_opt,
19+
last_path_segment, match_trait_method, match_type, match_var, multispan_sugg, snippet, snippet_opt,
2020
span_help_and_lint, span_lint, span_lint_and_sugg, span_lint_and_then};
2121
use utils::paths;
2222

@@ -1310,15 +1310,6 @@ fn pat_is_wild<'tcx>(pat: &'tcx PatKind, body: &'tcx Expr) -> bool {
13101310
}
13111311
}
13121312

1313-
fn match_var(expr: &Expr, var: Name) -> bool {
1314-
if let ExprPath(QPath::Resolved(None, ref path)) = expr.node {
1315-
if path.segments.len() == 1 && path.segments[0].name == var {
1316-
return true;
1317-
}
1318-
}
1319-
false
1320-
}
1321-
13221313
struct UsedVisitor {
13231314
var: ast::Name, // var to look for
13241315
used: bool, // has the var been used otherwise?

clippy_lints/src/ptr.rs

Lines changed: 101 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,42 @@
11
//! Checks for usage of `&Vec[_]` and `&String`.
22
33
use rustc::hir::*;
4+
use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
45
use rustc::hir::map::NodeItem;
56
use rustc::lint::*;
67
use rustc::ty;
7-
use syntax::ast::NodeId;
8+
use syntax::ast::{Name, NodeId};
89
use syntax::codemap::Span;
910
use syntax_pos::MultiSpan;
10-
use utils::{match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_then,
11-
span_lint_and_sugg, walk_ptrs_hir_ty};
11+
use utils::{get_pat_name, match_qpath, match_type, match_var, paths,
12+
snippet, snippet_opt, span_lint, span_lint_and_then,
13+
walk_ptrs_hir_ty};
1214

1315
/// **What it does:** This lint checks for function arguments of type `&String`
14-
/// or `&Vec` unless
15-
/// the references are mutable.
16+
/// or `&Vec` unless the references are mutable. It will also suggest you
17+
/// replace `.clone()` calls with the appropriate `.to_owned()`/`to_string()`
18+
/// calls.
1619
///
1720
/// **Why is this bad?** Requiring the argument to be of the specific size
18-
/// makes the function less
19-
/// useful for no benefit; slices in the form of `&[T]` or `&str` usually
20-
/// suffice and can be
21-
/// obtained from other types, too.
21+
/// makes the function less useful for no benefit; slices in the form of `&[T]`
22+
/// or `&str` usually suffice and can be obtained from other types, too.
2223
///
23-
/// **Known problems:** None.
24+
/// **Known problems:** The lint does not follow data. So if you have an
25+
/// argument `x` and write `let y = x; y.clone()` the lint will not suggest
26+
/// changing that `.clone()` to `.to_owned()`.
27+
///
28+
/// Other functions called from this function taking a `&String` or `&Vec`
29+
/// argument may also fail to compile if you change the argument. Applying
30+
/// this lint on them will fix the problem, but they may be in other crates.
31+
///
32+
/// Also there may be `fn(&Vec)`-typed references pointing to your function.
33+
/// If you have them, you will get a compiler error after applying this lint's
34+
/// suggestions. You then have the choice to undo your changes or change the
35+
/// type of the reference.
36+
///
37+
/// Note that if the function is part of your public interface, there may be
38+
/// other crates referencing it you may not be aware. Carefully deprecate the
39+
/// function before applying the lint suggestions in this case.
2440
///
2541
/// **Example:**
2642
/// ```rust
@@ -87,25 +103,26 @@ impl LintPass for PointerPass {
87103

88104
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PointerPass {
89105
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
90-
if let ItemFn(ref decl, _, _, _, _, _) = item.node {
91-
check_fn(cx, decl, item.id);
106+
if let ItemFn(ref decl, _, _, _, _, body_id) = item.node {
107+
check_fn(cx, decl, item.id, Some(body_id));
92108
}
93109
}
94110

95111
fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem) {
96-
if let ImplItemKind::Method(ref sig, _) = item.node {
112+
if let ImplItemKind::Method(ref sig, body_id) = item.node {
97113
if let Some(NodeItem(it)) = cx.tcx.hir.find(cx.tcx.hir.get_parent(item.id)) {
98114
if let ItemImpl(_, _, _, _, Some(_), _, _) = it.node {
99115
return; // ignore trait impls
100116
}
101117
}
102-
check_fn(cx, &sig.decl, item.id);
118+
check_fn(cx, &sig.decl, item.id, Some(body_id));
103119
}
104120
}
105121

106122
fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem) {
107-
if let TraitItemKind::Method(ref sig, _) = item.node {
108-
check_fn(cx, &sig.decl, item.id);
123+
if let TraitItemKind::Method(ref sig, ref trait_method) = item.node {
124+
let body_id = if let TraitMethod::Provided(b) = *trait_method { Some(b) } else { None };
125+
check_fn(cx, &sig.decl, item.id, body_id);
109126
}
110127
}
111128

@@ -123,12 +140,12 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PointerPass {
123140
}
124141
}
125142

126-
fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId) {
143+
fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId, opt_body_id: Option<BodyId>) {
127144
let fn_def_id = cx.tcx.hir.local_def_id(fn_id);
128145
let sig = cx.tcx.fn_sig(fn_def_id);
129146
let fn_ty = sig.skip_binder();
130147

131-
for (arg, ty) in decl.inputs.iter().zip(fn_ty.inputs()) {
148+
for (idx, (arg, ty)) in decl.inputs.iter().zip(fn_ty.inputs()).enumerate() {
132149
if let ty::TyRef(
133150
_,
134151
ty::TypeAndMut {
@@ -146,7 +163,7 @@ fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId) {
146163
], {
147164
ty_snippet = snippet_opt(cx, parameters.types[0].span);
148165
});
149-
//TODO: Suggestion
166+
let spans = get_spans(cx, opt_body_id, idx, "to_owned");
150167
span_lint_and_then(
151168
cx,
152169
PTR_ARG,
@@ -159,16 +176,30 @@ fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId) {
159176
"change this to",
160177
format!("&[{}]", snippet));
161178
}
179+
for (clonespan, suggestion) in spans {
180+
db.span_suggestion(clonespan,
181+
"change the `.clone()` to",
182+
suggestion);
183+
}
162184
}
163185
);
164186
} else if match_type(cx, ty, &paths::STRING) {
165-
span_lint_and_sugg(
187+
let spans = get_spans(cx, opt_body_id, idx, "to_string");
188+
span_lint_and_then(
166189
cx,
167190
PTR_ARG,
168191
arg.span,
169192
"writing `&String` instead of `&str` involves a new object where a slice will do.",
170-
"change this to",
171-
"&str".to_string()
193+
|db| {
194+
db.span_suggestion(arg.span,
195+
"change this to",
196+
"&str".into());
197+
for (clonespan, suggestion) in spans {
198+
db.span_suggestion_short(clonespan,
199+
"change the `.clone` to ",
200+
suggestion);
201+
}
202+
}
172203
);
173204
}
174205
}
@@ -198,6 +229,54 @@ fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId) {
198229
}
199230
}
200231

232+
fn get_spans(cx: &LateContext, opt_body_id: Option<BodyId>, idx: usize, fn_name: &'static str) -> Vec<(Span, String)> {
233+
if let Some(body) = opt_body_id.map(|id| cx.tcx.hir.body(id)) {
234+
get_binding_name(&body.arguments[idx]).map_or_else(Vec::new,
235+
|name| extract_clone_suggestions(cx, name, fn_name, body))
236+
} else {
237+
vec![]
238+
}
239+
}
240+
241+
fn extract_clone_suggestions<'a, 'tcx: 'a>(cx: &LateContext<'a, 'tcx>, name: Name, fn_name: &'static str, body: &'tcx Body) -> Vec<(Span, String)> {
242+
let mut visitor = PtrCloneVisitor {
243+
cx,
244+
name,
245+
fn_name,
246+
spans: vec![]
247+
};
248+
visitor.visit_body(body);
249+
visitor.spans
250+
}
251+
252+
struct PtrCloneVisitor<'a, 'tcx: 'a> {
253+
cx: &'a LateContext<'a, 'tcx>,
254+
name: Name,
255+
fn_name: &'static str,
256+
spans: Vec<(Span, String)>,
257+
}
258+
259+
impl<'a, 'tcx: 'a> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
260+
fn visit_expr(&mut self, expr: &'tcx Expr) {
261+
if let ExprMethodCall(ref seg, _, ref args) = expr.node {
262+
if args.len() == 1 && match_var(&args[0], self.name) && seg.name == "clone" {
263+
self.spans.push((expr.span, format!("{}.{}()", snippet(self.cx, args[0].span, "_"), self.fn_name)));
264+
}
265+
return;
266+
}
267+
walk_expr(self, expr);
268+
}
269+
270+
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
271+
NestedVisitorMap::None
272+
}
273+
274+
}
275+
276+
fn get_binding_name(arg: &Arg) -> Option<Name> {
277+
get_pat_name(&arg.pat)
278+
}
279+
201280
fn get_rptr_lm(ty: &Ty) -> Option<(&Lifetime, Mutability, Span)> {
202281
if let Ty_::TyRptr(ref lt, ref m) = ty.node {
203282
Some((lt, m.mutbl, ty.span))

clippy_lints/src/utils/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,17 @@ pub fn match_trait_method(cx: &LateContext, expr: &Expr, path: &[&str]) -> bool
218218
}
219219
}
220220

221+
/// Check if an expression references a variable of the given name.
222+
pub fn match_var(expr: &Expr, var: Name) -> bool {
223+
if let ExprPath(QPath::Resolved(None, ref path)) = expr.node {
224+
if path.segments.len() == 1 && path.segments[0].name == var {
225+
return true;
226+
}
227+
}
228+
false
229+
}
230+
231+
221232
pub fn last_path_segment(path: &QPath) -> &PathSegment {
222233
match *path {
223234
QPath::Resolved(_, ref path) => path.segments
@@ -393,6 +404,16 @@ pub fn get_item_name(cx: &LateContext, expr: &Expr) -> Option<Name> {
393404
}
394405
}
395406

407+
/// Get the name of a `Pat`, if any
408+
pub fn get_pat_name(pat: &Pat) -> Option<Name> {
409+
match pat.node {
410+
PatKind::Binding(_, _, ref spname, _) => Some(spname.node),
411+
PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.name),
412+
PatKind::Box(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p),
413+
_ => None,
414+
}
415+
}
416+
396417
struct ContainsName {
397418
name: Name,
398419
result: bool,

tests/ui/ptr_arg.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![feature(plugin)]
22
#![plugin(clippy)]
3-
#![allow(unused)]
3+
#![allow(unused, many_single_char_names)]
44
#![warn(ptr_arg)]
55

66
fn do_vec(x: &Vec<i64>) {
@@ -34,5 +34,24 @@ struct Bar;
3434
impl Foo for Bar {
3535
type Item = Vec<u8>;
3636
fn do_vec(x: &Vec<i64>) {}
37-
fn do_item(x: &Vec<u8>) {}
37+
fn do_item(x: &Vec<u8>) {}
38+
}
39+
40+
fn cloned(x: &Vec<u8>) -> Vec<u8> {
41+
let e = x.clone();
42+
let f = e.clone(); // OK
43+
let g = x;
44+
let h = g.clone(); // Alas, we cannot reliably detect this without following data.
45+
let i = (e).clone();
46+
x.clone()
47+
}
48+
49+
fn str_cloned(x: &String) -> String {
50+
let a = x.clone();
51+
let b = x.clone();
52+
let c = b.clone();
53+
let d = a.clone()
54+
.clone()
55+
.clone();
56+
x.clone()
3857
}

tests/ui/ptr_arg.stderr

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,47 @@ error: writing `&Vec<_>` instead of `&[_]` involves one more reference and canno
1818
27 | fn do_vec(x: &Vec<i64>);
1919
| ^^^^^^^^^ help: change this to: `&[i64]`
2020

21-
error: aborting due to 3 previous errors
21+
error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices.
22+
--> $DIR/ptr_arg.rs:40:14
23+
|
24+
40 | fn cloned(x: &Vec<u8>) -> Vec<u8> {
25+
| ^^^^^^^^
26+
|
27+
help: change this to
28+
|
29+
40 | fn cloned(x: &[u8]) -> Vec<u8> {
30+
| ^^^^^
31+
help: change the `.clone()` to
32+
|
33+
41 | let e = x.to_owned();
34+
| ^^^^^^^^^^^^
35+
help: change the `.clone()` to
36+
|
37+
46 | x.to_owned()
38+
| ^^^^^^^^^^^^
39+
40+
error: writing `&String` instead of `&str` involves a new object where a slice will do.
41+
--> $DIR/ptr_arg.rs:49:18
42+
|
43+
49 | fn str_cloned(x: &String) -> String {
44+
| ^^^^^^^
45+
|
46+
help: change this to
47+
|
48+
49 | fn str_cloned(x: &str) -> String {
49+
| ^^^^
50+
help: change the `.clone` to
51+
|
52+
50 | let a = x.to_string();
53+
| ^^^^^^^^^^^^^
54+
help: change the `.clone` to
55+
|
56+
51 | let b = x.to_string();
57+
| ^^^^^^^^^^^^^
58+
help: change the `.clone` to
59+
|
60+
56 | x.to_string()
61+
| ^^^^^^^^^^^^^
62+
63+
error: aborting due to 5 previous errors
2264

0 commit comments

Comments
 (0)