Skip to content

Commit 2e49b11

Browse files
Add new lint for byte char slices
This patch adds a new lint that checks for potentially harder to read byte char slices: `&[b'a', b'b']` and suggests to replace them with the easier to read `b"ab"` form. Signed-Off-By: Marcel Müller <[email protected]> Co-authored-by: Matthias Beyer <[email protected]>
1 parent 0bca8dd commit 2e49b11

11 files changed

+126
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4080,6 +4080,7 @@ Released 2018-09-13
40804080
[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local
40814081
[`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code
40824082
[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow
4083+
[`byte_char_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#byte_char_slice
40834084
[`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len
40844085
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
40854086
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata

clippy_lints/src/byte_char_slice.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use rustc_ast::ast::{BorrowKind, Expr, ExprKind, Mutability};
3+
use rustc_ast::token::{Lit, LitKind};
4+
use rustc_errors::Applicability;
5+
use rustc_lint::{EarlyContext, EarlyLintPass};
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
8+
declare_clippy_lint! {
9+
/// ### What it does
10+
/// Checks for hard to read slices of byte characters, that could be more easily expressed as a
11+
/// byte string.
12+
///
13+
/// ### Why is this bad?
14+
///
15+
/// Potentially makes the string harder to read.
16+
///
17+
/// ### Example
18+
/// ```rust
19+
/// &[b'H', b'e', b'l', b'l', b'o']
20+
/// ```
21+
/// Use instead:
22+
/// ```rust
23+
/// b"Hello"
24+
/// ```
25+
#[clippy::version = "1.68.0"]
26+
pub BYTE_CHAR_SLICE,
27+
style,
28+
"hard to read byte char slice"
29+
}
30+
declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICE]);
31+
32+
impl EarlyLintPass for ByteCharSlice {
33+
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
34+
if let Some(slice) = is_byte_char_slice(expr) && !expr.span.from_expansion() {
35+
span_lint_and_sugg(
36+
cx,
37+
BYTE_CHAR_SLICE,
38+
expr.span,
39+
"can be more succinctly written as a byte str",
40+
"try",
41+
format!("b\"{slice}\""),
42+
Applicability::MaybeIncorrect,
43+
);
44+
}
45+
}
46+
}
47+
48+
fn is_byte_char_slice(expr: &Expr) -> Option<String> {
49+
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = &expr.kind {
50+
match &expr.kind {
51+
ExprKind::Array(members) => {
52+
let all_member_bytes = members.iter().all(|member| {
53+
matches!(
54+
&member.kind,
55+
ExprKind::Lit(Lit {
56+
kind: LitKind::Byte,
57+
..
58+
})
59+
)
60+
});
61+
62+
if !all_member_bytes || members.is_empty() {
63+
return None;
64+
}
65+
66+
Some(
67+
members
68+
.iter()
69+
.map(|member| match &member.kind {
70+
ExprKind::Lit(Lit {
71+
kind: LitKind::Byte,
72+
symbol,
73+
..
74+
}) => symbol.as_str(),
75+
_ => unreachable!(),
76+
})
77+
.collect::<Vec<_>>()
78+
.join(""),
79+
)
80+
},
81+
_ => None,
82+
}
83+
} else {
84+
None
85+
}
86+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
6161
crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO,
6262
crate::borrow_deref_ref::BORROW_DEREF_REF_INFO,
6363
crate::box_default::BOX_DEFAULT_INFO,
64+
crate::byte_char_slice::BYTE_CHAR_SLICE_INFO,
6465
crate::cargo::CARGO_COMMON_METADATA_INFO,
6566
crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,
6667
crate::cargo::NEGATIVE_FEATURE_NAMES_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ mod bool_to_int_with_if;
8181
mod booleans;
8282
mod borrow_deref_ref;
8383
mod box_default;
84+
mod byte_char_slice;
8485
mod cargo;
8586
mod casts;
8687
mod checked_conversions;
@@ -908,6 +909,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
908909
store.register_late_pass(|_| Box::new(fn_null_check::FnNullCheck));
909910
store.register_late_pass(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse));
910911
store.register_late_pass(|_| Box::new(size_of_ref::SizeOfRef));
912+
store.register_early_pass(|| Box::new(byte_char_slice::ByteCharSlice));
911913
// add lints here, do not remove this comment, it's used in `new_lint`
912914
}
913915

tests/ui/byte_char_slice.fixed

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
#![warn(clippy::byte_char_slice)]
4+
5+
fn main() {
6+
let bad = b"abc";
7+
let good = &[b'a', 0x42];
8+
let good = vec![b'a', b'a'];
9+
let good: u8 = [b'a', b'c'].into_iter().sum();
10+
}

tests/ui/byte_char_slice.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
#![warn(clippy::byte_char_slice)]
4+
5+
fn main() {
6+
let bad = &[b'a', b'b', b'c'];
7+
let good = &[b'a', 0x42];
8+
let good = vec![b'a', b'a'];
9+
let good: u8 = [b'a', b'c'].into_iter().sum();
10+
}

tests/ui/byte_char_slice.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: can be more succinctly written as a byte str
2+
--> $DIR/byte_char_slice.rs:6:15
3+
|
4+
LL | let bad = &[b'a', b'b', b'c'];
5+
| ^^^^^^^^^^^^^^^^^^^ help: try: `b"abc"`
6+
|
7+
= note: `-D clippy::byte-char-slice` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

tests/ui/invalid_utf8_in_unchecked.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::invalid_utf8_in_unchecked)]
2+
#![allow(clippy::byte_char_slice)]
23

34
fn main() {
45
// Valid

tests/ui/invalid_utf8_in_unchecked.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
error: non UTF-8 literal in `std::str::from_utf8_unchecked`
2-
--> $DIR/invalid_utf8_in_unchecked.rs:16:9
2+
--> $DIR/invalid_utf8_in_unchecked.rs:17:9
33
|
44
LL | std::str::from_utf8_unchecked(&[99, 108, 130, 105, 112, 112, 121]);
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66
|
77
= note: `-D clippy::invalid-utf8-in-unchecked` implied by `-D warnings`
88

99
error: non UTF-8 literal in `std::str::from_utf8_unchecked`
10-
--> $DIR/invalid_utf8_in_unchecked.rs:17:9
10+
--> $DIR/invalid_utf8_in_unchecked.rs:18:9
1111
|
1212
LL | std::str::from_utf8_unchecked(&[b'c', b'l', b'/x82', b'i', b'p', b'p', b'y']);
1313
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1414

1515
error: non UTF-8 literal in `std::str::from_utf8_unchecked`
16-
--> $DIR/invalid_utf8_in_unchecked.rs:18:9
16+
--> $DIR/invalid_utf8_in_unchecked.rs:19:9
1717
|
1818
LL | std::str::from_utf8_unchecked(b"cl/x82ippy");
1919
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/ui/ptr_offset_with_cast.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// run-rustfix
2-
#![allow(clippy::unnecessary_cast)]
2+
#![allow(clippy::unnecessary_cast, clippy::byte_char_slice)]
33

44
fn main() {
55
let vec = vec![b'a', b'b', b'c'];

tests/ui/ptr_offset_with_cast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// run-rustfix
2-
#![allow(clippy::unnecessary_cast)]
2+
#![allow(clippy::unnecessary_cast, clippy::byte_char_slice)]
33

44
fn main() {
55
let vec = vec![b'a', b'b', b'c'];

0 commit comments

Comments
 (0)