Skip to content

Commit 59c8f62

Browse files
committed
Merge pull request #669 from shssoichiro/single-char-pattern
Lint single-character strings as P: Pattern args
2 parents 09ef38b + b1e4b49 commit 59c8f62

File tree

5 files changed

+158
-4
lines changed

5 files changed

+158
-4
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
88
[Jump to usage instructions](#usage)
99

1010
##Lints
11-
There are 121 lints included in this crate:
11+
There are 122 lints included in this crate:
1212

1313
name | default | meaning
1414
---------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -104,6 +104,7 @@ name
104104
[shadow_same](https://github.com/Manishearth/rust-clippy/wiki#shadow_same) | allow | rebinding a name to itself, e.g. `let mut x = &mut x`
105105
[shadow_unrelated](https://github.com/Manishearth/rust-clippy/wiki#shadow_unrelated) | allow | The name is re-bound without even using the original value
106106
[should_implement_trait](https://github.com/Manishearth/rust-clippy/wiki#should_implement_trait) | warn | defining a method that should be implementing a std trait
107+
[single_char_pattern](https://github.com/Manishearth/rust-clippy/wiki#single_char_pattern) | warn | using a single-character str where a char could be used, e.g. `_.split("x")`
107108
[single_match](https://github.com/Manishearth/rust-clippy/wiki#single_match) | warn | a match statement with a single nontrivial arm (i.e, where the other arm is `_ => {}`) is used; recommends `if let` instead
108109
[single_match_else](https://github.com/Manishearth/rust-clippy/wiki#single_match_else) | allow | a match statement with a two arms where the second arm's pattern is a wildcard; recommends `if let` instead
109110
[str_to_string](https://github.com/Manishearth/rust-clippy/wiki#str_to_string) | warn | using `to_string()` on a str, which should be `to_owned()`

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
241241
methods::OR_FUN_CALL,
242242
methods::SEARCH_IS_SOME,
243243
methods::SHOULD_IMPLEMENT_TRAIT,
244+
methods::SINGLE_CHAR_PATTERN,
244245
methods::STR_TO_STRING,
245246
methods::STRING_TO_STRING,
246247
methods::WRONG_SELF_CONVENTION,

src/methods.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use rustc::lint::*;
2+
use rustc::middle::const_eval::{ConstVal, eval_const_expr_partial};
3+
use rustc::middle::const_eval::EvalHint::ExprTypeChecked;
24
use rustc::middle::subst::{Subst, TypeSpace};
35
use rustc::middle::ty;
46
use rustc_front::hir::*;
@@ -295,6 +297,20 @@ declare_lint! {
295297
pub NEW_RET_NO_SELF, Warn, "not returning `Self` in a `new` method"
296298
}
297299

300+
/// **What it does:** This lint checks for string methods that receive a single-character `str` as an argument, e.g. `_.split("x")`.
301+
///
302+
/// **Why is this bad?** Performing these methods using a `char` is faster than using a `str`.
303+
///
304+
/// **Known problems:** Does not catch multi-byte unicode characters.
305+
///
306+
/// **Example:** `_.split("x")` could be `_.split('x')`
307+
declare_lint! {
308+
pub SINGLE_CHAR_PATTERN,
309+
Warn,
310+
"using a single-character str where a char could be used, e.g. \
311+
`_.split(\"x\")`"
312+
}
313+
298314
impl LintPass for MethodsPass {
299315
fn get_lints(&self) -> LintArray {
300316
lint_array!(EXTEND_FROM_SLICE,
@@ -312,7 +328,8 @@ impl LintPass for MethodsPass {
312328
CHARS_NEXT_CMP,
313329
CLONE_ON_COPY,
314330
CLONE_DOUBLE_REF,
315-
NEW_RET_NO_SELF)
331+
NEW_RET_NO_SELF,
332+
SINGLE_CHAR_PATTERN)
316333
}
317334
}
318335

@@ -351,6 +368,11 @@ impl LateLintPass for MethodsPass {
351368
lint_clone_on_copy(cx, expr);
352369
lint_clone_double_ref(cx, expr, &args[0]);
353370
}
371+
for &(method, pos) in &PATTERN_METHODS {
372+
if name.node.as_str() == method && args.len() > pos {
373+
lint_single_char_pattern(cx, expr, &args[pos]);
374+
}
375+
}
354376
}
355377
ExprBinary(op, ref lhs, ref rhs) if op.node == BiEq || op.node == BiNe => {
356378
if !lint_chars_next(cx, expr, lhs, rhs, op.node == BiEq) {
@@ -819,6 +841,22 @@ fn lint_chars_next(cx: &LateContext, expr: &Expr, chain: &Expr, other: &Expr, eq
819841
false
820842
}
821843

844+
/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
845+
fn lint_single_char_pattern(cx: &LateContext, expr: &Expr, arg: &Expr) {
846+
if let Ok(ConstVal::Str(r)) = eval_const_expr_partial(cx.tcx, arg, ExprTypeChecked, None) {
847+
if r.len() == 1 {
848+
let hint = snippet(cx, expr.span, "..").replace(&format!("\"{}\"", r), &format!("'{}'", r));
849+
span_lint_and_then(cx,
850+
SINGLE_CHAR_PATTERN,
851+
arg.span,
852+
"single-character string constant used as pattern",
853+
|db| {
854+
db.span_suggestion(expr.span, "try using a char instead:", hint);
855+
});
856+
}
857+
}
858+
}
859+
822860
/// Given a `Result<T, E>` type, return its error type (`E`).
823861
fn get_error_type<'a>(cx: &LateContext, ty: ty::Ty<'a>) -> Option<ty::Ty<'a>> {
824862
if !match_type(cx, ty, &RESULT_PATH) {
@@ -889,6 +927,28 @@ const TRAIT_METHODS: [(&'static str, usize, SelfKind, OutType, &'static str); 30
889927
("sub", 2, SelfKind::Value, OutType::Any, "std::ops::Sub"),
890928
];
891929

930+
#[cfg_attr(rustfmt, rustfmt_skip)]
931+
const PATTERN_METHODS: [(&'static str, usize); 17] = [
932+
("contains", 1),
933+
("starts_with", 1),
934+
("ends_with", 1),
935+
("find", 1),
936+
("rfind", 1),
937+
("split", 1),
938+
("rsplit", 1),
939+
("split_terminator", 1),
940+
("rsplit_terminator", 1),
941+
("splitn", 2),
942+
("rsplitn", 2),
943+
("matches", 1),
944+
("rmatches", 1),
945+
("match_indices", 1),
946+
("rmatch_indices", 1),
947+
("trim_left_matches", 1),
948+
("trim_right_matches", 1),
949+
];
950+
951+
892952
#[derive(Clone, Copy)]
893953
enum SelfKind {
894954
Value,

src/misc_early.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl EarlyLintPass for MiscEarly {
104104
if let PatIdent(_, sp_ident, None) = arg.pat.node {
105105
let arg_name = sp_ident.node.to_string();
106106

107-
if arg_name.starts_with("_") {
107+
if arg_name.starts_with('_') {
108108
if let Some(correspondance) = registered_names.get(&arg_name[1..]) {
109109
span_lint(cx,
110110
DUPLICATE_UNDERSCORE_ARGUMENT,

tests/compile-fail/methods.rs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#![plugin(clippy)]
33

44
#![deny(clippy, clippy_pedantic)]
5-
#![allow(unused, print_stdout)]
5+
#![allow(unused, print_stdout, non_ascii_literal)]
66

77
use std::collections::BTreeMap;
88
use std::collections::HashMap;
@@ -355,3 +355,95 @@ fn clone_on_double_ref() {
355355
//~^^^ERROR using `clone` on a `Copy` type
356356
println!("{:p} {:p}",*y, z);
357357
}
358+
359+
fn single_char_pattern() {
360+
let x = "foo";
361+
x.split("x");
362+
//~^ ERROR single-character string constant used as pattern
363+
//~| HELP try using a char instead:
364+
//~| SUGGESTION x.split('x');
365+
366+
x.split("xx");
367+
368+
x.split('x');
369+
370+
let y = "x";
371+
x.split(y);
372+
373+
// Not yet testing for multi-byte characters
374+
// Changing `r.len() == 1` to `r.chars().count() == 1` in `lint_single_char_pattern`
375+
// should have done this but produced an ICE
376+
//
377+
// We may not want to suggest changing these anyway
378+
// See: https://github.com/Manishearth/rust-clippy/issues/650#issuecomment-184328984
379+
x.split("ß");
380+
x.split("ℝ");
381+
x.split("💣");
382+
// Can't use this lint for unicode code points which don't fit in a char
383+
x.split("❤️");
384+
385+
x.contains("x");
386+
//~^ ERROR single-character string constant used as pattern
387+
//~| HELP try using a char instead:
388+
//~| SUGGESTION x.contains('x');
389+
x.starts_with("x");
390+
//~^ ERROR single-character string constant used as pattern
391+
//~| HELP try using a char instead:
392+
//~| SUGGESTION x.starts_with('x');
393+
x.ends_with("x");
394+
//~^ ERROR single-character string constant used as pattern
395+
//~| HELP try using a char instead:
396+
//~| SUGGESTION x.ends_with('x');
397+
x.find("x");
398+
//~^ ERROR single-character string constant used as pattern
399+
//~| HELP try using a char instead:
400+
//~| SUGGESTION x.find('x');
401+
x.rfind("x");
402+
//~^ ERROR single-character string constant used as pattern
403+
//~| HELP try using a char instead:
404+
//~| SUGGESTION x.rfind('x');
405+
x.rsplit("x");
406+
//~^ ERROR single-character string constant used as pattern
407+
//~| HELP try using a char instead:
408+
//~| SUGGESTION x.rsplit('x');
409+
x.split_terminator("x");
410+
//~^ ERROR single-character string constant used as pattern
411+
//~| HELP try using a char instead:
412+
//~| SUGGESTION x.split_terminator('x');
413+
x.rsplit_terminator("x");
414+
//~^ ERROR single-character string constant used as pattern
415+
//~| HELP try using a char instead:
416+
//~| SUGGESTION x.rsplit_terminator('x');
417+
x.splitn(0, "x");
418+
//~^ ERROR single-character string constant used as pattern
419+
//~| HELP try using a char instead:
420+
//~| SUGGESTION x.splitn(0, 'x');
421+
x.rsplitn(0, "x");
422+
//~^ ERROR single-character string constant used as pattern
423+
//~| HELP try using a char instead:
424+
//~| SUGGESTION x.rsplitn(0, 'x');
425+
x.matches("x");
426+
//~^ ERROR single-character string constant used as pattern
427+
//~| HELP try using a char instead:
428+
//~| SUGGESTION x.matches('x');
429+
x.rmatches("x");
430+
//~^ ERROR single-character string constant used as pattern
431+
//~| HELP try using a char instead:
432+
//~| SUGGESTION x.rmatches('x');
433+
x.match_indices("x");
434+
//~^ ERROR single-character string constant used as pattern
435+
//~| HELP try using a char instead:
436+
//~| SUGGESTION x.match_indices('x');
437+
x.rmatch_indices("x");
438+
//~^ ERROR single-character string constant used as pattern
439+
//~| HELP try using a char instead:
440+
//~| SUGGESTION x.rmatch_indices('x');
441+
x.trim_left_matches("x");
442+
//~^ ERROR single-character string constant used as pattern
443+
//~| HELP try using a char instead:
444+
//~| SUGGESTION x.trim_left_matches('x');
445+
x.trim_right_matches("x");
446+
//~^ ERROR single-character string constant used as pattern
447+
//~| HELP try using a char instead:
448+
//~| SUGGESTION x.trim_right_matches('x');
449+
}

0 commit comments

Comments
 (0)