Skip to content

Commit ff3228c

Browse files
author
rudyardrichter
committed
Fix check_expr and add check_local
1 parent b9e161a commit ff3228c

File tree

6 files changed

+283
-51
lines changed

6 files changed

+283
-51
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
419419
reg.register_early_lint_pass(box multiple_crate_versions::Pass);
420420
reg.register_late_lint_pass(box map_unit_fn::Pass);
421421
reg.register_late_lint_pass(box infallible_destructuring_match::Pass);
422+
reg.register_late_lint_pass(box use_shared_from_slice::Pass);
422423
reg.register_late_lint_pass(box inherent_impl::Pass::default());
423424

424425

clippy_lints/src/types.rs

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use std::borrow::Cow;
1212
use syntax::ast::{FloatTy, IntTy, UintTy};
1313
use syntax::codemap::Span;
1414
use syntax::errors::DiagnosticBuilder;
15-
use crate::utils::{comparisons, differing_macro_contexts, higher, in_constant, in_external_macro, in_macro, last_path_segment, match_def_path, match_path,
16-
match_type, multispan_sugg, opt_def_id, same_tys, snippet, snippet_opt, span_help_and_lint, span_lint,
15+
use crate::utils::{comparisons, differing_macro_contexts, higher, in_constant, in_external_macro, in_macro, match_def_path, match_path,
16+
match_type, multispan_sugg, opt_def_id, same_tys, snippet, snippet_opt, span_help_and_lint, span_lint, match_type_parameter,
1717
span_lint_and_sugg, span_lint_and_then, clip, unsext, sext, int_bits};
1818
use crate::utils::paths;
1919
use crate::consts::{constant, Constant};
@@ -176,23 +176,6 @@ fn check_fn_decl(cx: &LateContext, decl: &FnDecl) {
176176
}
177177
}
178178

179-
/// Check if `qpath` has last segment with type parameter matching `path`
180-
fn match_type_parameter(cx: &LateContext, qpath: &QPath, path: &[&str]) -> bool {
181-
let last = last_path_segment(qpath);
182-
if_chain! {
183-
if let Some(ref params) = last.parameters;
184-
if !params.parenthesized;
185-
if let Some(ty) = params.types.get(0);
186-
if let TyPath(ref qpath) = ty.node;
187-
if let Some(did) = opt_def_id(cx.tables.qpath_def(qpath, cx.tcx.hir.node_to_hir_id(ty.id)));
188-
if match_def_path(cx.tcx, did, path);
189-
then {
190-
return true;
191-
}
192-
}
193-
false
194-
}
195-
196179
/// Recursively check for `TypePass` lints in the given type. Stop at the first
197180
/// lint found.
198181
///
Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
use rustc::hir::Expr;
2-
use rustc::lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass};
1+
use rustc::hir;
2+
use rustc::hir::{Expr, Local};
3+
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
34
use rustc::ty;
4-
use utils::{match_type, span_lint_and_sugg, walk_ptrs_ty};
5-
use utils::paths;
5+
use crate::utils::{match_qpath, match_type, match_type_parameter, snippet, span_lint_and_sugg, walk_ptrs_ty, get_type_parameter};
6+
use crate::utils::paths;
67

78
/// **What it does:**
89
/// Checks for usage of `Rc<String>` or `Rc<Vec<T>>`.
910
///
1011
/// **Why is this bad?**
11-
/// Using a `Rc<str>` or `Rc<[T]>` is more efficient and easy to construct with
12+
/// Using `Rc<str>` or `Rc<[T]>` is more efficient and easy to construct with
1213
/// `into()`.
1314
///
1415
/// **Known problems:**
@@ -23,15 +24,15 @@ use utils::paths;
2324
/// let bad_ref: Rc<Vec<usize>> = Rc::new(vec!(1, 2, 3));
2425
///
2526
/// // Good
26-
/// let good_ref: Rc<[usize]> = Rc::new(vec!(1, 2, 3).into());
27+
/// let good_ref: Rc<[usize]> = vec!(1, 2, 3).into();
2728
/// ```
2829
declare_clippy_lint! {
2930
pub USE_SHARED_FROM_SLICE,
3031
nursery,
31-
"use `into()` to construct `Rc` from slice"
32+
"constructing reference-counted type from `Vec` or `String`"
3233
}
3334

34-
#[derive(Copy, Clone, Debug)]
35+
#[derive(Copy, Clone)]
3536
pub struct Pass;
3637

3738
impl LintPass for Pass {
@@ -40,31 +41,99 @@ impl LintPass for Pass {
4041
}
4142
}
4243

43-
impl <'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
44-
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
45-
let expr_ty = walk_ptrs_ty(cx.tables.expr_ty(expr));
44+
/// If the given `expr` is constructing an `Rc` or `Arc` containing a `Vec` or
45+
/// `String`, output a suggestion to fix accordingly.
46+
fn check_rc_construction<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
47+
let expr_ty = walk_ptrs_ty(cx.tables.expr_ty(expr));
48+
49+
// Check for expressions with the type `Rc<Vec<T>>`.
50+
if_chain! {
51+
// Check if this expression is constructing an `Rc` or `Arc`.
52+
let is_rc = match_type(cx, expr_ty, &paths::RC);
53+
let is_arc = match_type(cx, expr_ty, &paths::ARC);
54+
if is_rc || is_arc;
55+
56+
// Check if the `Rc` or `Arc` is constructed with `Vec` or `String`.
57+
if let ty::TyAdt(_, subst) = expr_ty.sty;
58+
let arg_type = subst.type_at(0);
59+
let arg_is_vec = match_type(cx, arg_type, &paths::VEC);
60+
let arg_is_string = match_type(cx, arg_type, &paths::STRING);
61+
if arg_is_vec || arg_is_string;
62+
63+
// Get the argument, to use for writing out the lint message.
64+
if let hir::ExprCall(_, ref args) = expr.node;
65+
if let Some(arg) = args.get(0);
4666

47-
// Check for expressions with the type `Rc<Vec<T>>`.
48-
if_chain! {
49-
if let ty::TyAdt(_, subst) = expr_ty.sty;
50-
if match_type(cx, expr_ty, &paths::RC);
51-
if match_type(cx, subst.type_at(1), &paths::VEC);
52-
then {
53-
cx.sess().note_without_error(&format!("{:?}", subst));
54-
span_lint_and_sugg(
55-
cx,
56-
USE_SHARED_FROM_SLICE,
57-
expr.span,
58-
"constructing reference-counted type from vec",
59-
"consider using `into()`",
60-
format!("{}", "TODO"),
61-
);
67+
then {
68+
if arg_is_vec {
69+
let msg = "avoid constructing reference-counted type from Vec; convert from slice instead";
70+
let help = "use";
71+
let argument = snippet(cx, arg.span.source_callsite(), "..");
72+
let sugg = format!("{}.into()", argument);
73+
span_lint_and_sugg(cx, USE_SHARED_FROM_SLICE, expr.span, &msg, help, sugg);
74+
} else if arg_is_string {
75+
let msg = "avoid constructing reference-counted type from String; convert from slice instead";
76+
let help = "use";
77+
let argument = snippet(cx, arg.span.source_callsite(), "..");
78+
let sugg = format!("{}.as_str().into()", argument);
79+
span_lint_and_sugg(cx, USE_SHARED_FROM_SLICE, expr.span, &msg, help, sugg);
6280
}
6381
}
82+
}
83+
}
84+
85+
/// Check a type declaration to lint, such as in
86+
///
87+
/// let x: Rc<String> = Rc::new(some_string)
88+
///
89+
/// If `ty`, the declared type, is an `Rc` or `Arc` containing a `Vec` or
90+
/// `String` then output a suggestion to change it.
91+
fn check_rc_type<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: &hir::Ty) {
92+
match ty.node {
93+
hir::TyPath(ref qpath) => {
94+
let matches_rc = match_qpath(qpath, &paths::RC);
95+
let matches_arc = match_qpath(qpath, &paths::ARC);
96+
if matches_rc || matches_arc {
97+
let has_vec = match_type_parameter(cx, qpath, &paths::VEC);
98+
let has_string = match_type_parameter(cx, qpath, &paths::STRING);
99+
// Keep the type for making suggestions later.
100+
let constructor = if matches_arc { "Arc" } else { "Rc" };
101+
if_chain! {
102+
if has_vec;
103+
// In the case we have something like `Rc<Vec<usize>>`, get the inner parameter
104+
// type out from the parameter type of the `Rc`; so in this example, get the
105+
// type `usize`. Use this to suggest using the type `Rc<[usize]>` instead.
106+
let mut vec_ty = get_type_parameter(qpath).expect("");
107+
if let hir::TyPath(ref vec_qpath) = vec_ty.node;
108+
if let Some(param_ty) = get_type_parameter(&vec_qpath);
109+
then {
110+
let msg = "use slice instead of `Vec` in reference-counted type";
111+
let help = "use";
112+
let sugg = format!("{}<[{}]>", constructor, snippet(cx, param_ty.span.source_callsite(), ".."));
113+
span_lint_and_sugg(cx, USE_SHARED_FROM_SLICE, ty.span, msg, help, sugg);
114+
}
115+
}
116+
if has_string {
117+
//ty.node = TyPath(hir::Resolved(None, P()))
118+
let msg = "use slice instead of `String` in reference-counted type";
119+
let help = "use";
120+
let sugg = format!("{}<str>", constructor);
121+
span_lint_and_sugg(cx, USE_SHARED_FROM_SLICE, ty.span, msg, help, sugg);
122+
}
123+
}
124+
},
125+
_ => {},
126+
}
127+
}
64128

65-
// TODO
66-
// Check for expressions with the type `Rc<String>`.
67-
// Check for expressions with the type `Arc<String>`.
68-
// Check for expressions with the type `Arc<Vec<T>>`.
129+
impl <'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
130+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
131+
check_rc_construction(cx, expr);
132+
}
133+
134+
fn check_local(&mut self, cx: &LateContext<'a, 'tcx>, local: &Local) {
135+
if let Some(ref ty) = local.ty {
136+
check_rc_type(cx, ty);
137+
}
69138
}
70139
}

clippy_lints/src/utils/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,45 @@ pub fn match_type(cx: &LateContext, ty: Ty, path: &[&str]) -> bool {
149149
}
150150
}
151151

152+
/// Return the type parameter from a path.
153+
///
154+
/// # Examples
155+
/// ```rust,ignore
156+
/// // let path be a QPath representing `Vec<usize>`
157+
/// get_type_parameter(path)
158+
/// // => type(usize)
159+
/// ```
160+
pub fn get_type_parameter<'a>(qpath: &'a QPath) -> Option<&'a hir::Ty> {
161+
let last = last_path_segment(qpath);
162+
if_chain! {
163+
if let Some(ref params) = last.parameters;
164+
if !params.parenthesized;
165+
if let Some(ty) = params.types.get(0);
166+
then {
167+
Some(ty)
168+
} else {
169+
None
170+
}
171+
}
172+
}
173+
174+
/// Check if `qpath` has last segment with type parameter matching `path`
175+
pub fn match_type_parameter(cx: &LateContext, qpath: &QPath, path: &[&str]) -> bool {
176+
let last = last_path_segment(qpath);
177+
if_chain! {
178+
if let Some(ref params) = last.parameters;
179+
if !params.parenthesized;
180+
if let Some(ty) = params.types.get(0);
181+
if let TyPath(ref qpath) = ty.node;
182+
if let Some(did) = opt_def_id(cx.tables.qpath_def(qpath, cx.tcx.hir.node_to_hir_id(ty.id)));
183+
if match_def_path(cx.tcx, did, path);
184+
then {
185+
return true;
186+
}
187+
}
188+
false
189+
}
190+
152191
/// Check if the method call given in `expr` belongs to given type.
153192
pub fn match_impl_method(cx: &LateContext, expr: &Expr, path: &[&str]) -> bool {
154193
let method_call = cx.tables.type_dependent_defs()[expr.hir_id];

tests/ui/use_shared_from_slice.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,35 @@
11
use std::rc::Rc;
2+
use std::rc;
3+
use std::sync::Arc;
24

5+
#[warn(clippy, use_shared_from_slice)]
6+
#[allow(unused_variables)]
37
fn main() {
4-
let _bad_ref: Rc<Vec<usize>> = Rc::new(vec!(1, 2, 3));
8+
// Test constructing `Rc` directly from `vec!` macro.
9+
let bad_rc_vec_0: Rc<Vec<usize>> = Rc::new(vec!(1, 2, 3));
10+
let bad_rc_vec_00: rc::Rc<Vec<usize>> = rc::Rc::new(vec!(1, 2, 3));
11+
// Test constructing `Rc` from `Vec` variable.
12+
let example_vec: Vec<usize> = vec!(4, 5, 6);
13+
let bad_rc_vec_1: Rc<Vec<usize>> = Rc::new(example_vec);
14+
// Test constructing `Rc` with a `String`.
15+
let bad_rc_string_0: Rc<String> = Rc::new("test".to_string());
16+
// Test constructing `Rc` with a `String` variable.
17+
let example_string: String = "test".to_string();
18+
let bad_rc_string_1: Rc<String> = Rc::new(example_string);
519

6-
let _good_ref: Rc<[usize]> = vec!(1, 2, 3).into();
20+
// Test constructing `Arc` from `vec!` macro.
21+
let bad_arc_vec_0: Arc<Vec<usize>> = Arc::new(vec!(1, 2, 3));
22+
// Test constructing `Arc` from `Vec` variable.
23+
let example_vec: Vec<usize> = vec!(4, 5, 6);
24+
let bad_arc_vec_1: Arc<Vec<usize>> = Arc::new(example_vec);
25+
// Test constructing `Arc` with a `String`.
26+
let bad_arc_string_0: Arc<String> = Arc::new("test".to_string());
27+
// Test constructing `Arc` with a `String` variable.
28+
let example_string: String = "test".to_string();
29+
let bad_arc_string_0: Arc<String> = Arc::new(example_string);
30+
31+
// Test that using `.into()` doesn't cause suggestions.
32+
let good_rc_0: Rc<[usize]> = vec!(1, 2, 3).into();
33+
let example_vec: Vec<usize> = vec!(4, 5, 6);
34+
let good_rc_1: Rc<[usize]> = example_vec.into();
735
}

0 commit comments

Comments
 (0)