Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 62 additions & 30 deletions clippy_lints/src/byte_char_slices.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::{BorrowKind, Expr, ExprKind, Mutability};
use rustc_ast::token::{Lit, LitKind};
use std::borrow::Cow;

use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, span_contains_cfg, span_contains_comment};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;

declare_clippy_lint! {
/// ### What it does
Expand All @@ -30,47 +36,73 @@ declare_clippy_lint! {

declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICES]);

impl EarlyLintPass for ByteCharSlice {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
impl<'tcx> LateLintPass<'tcx> for ByteCharSlice {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if !expr.span.from_expansion()
&& let Some(slice) = is_byte_char_slices(expr)
&& let Some((has_ref, slice)) = is_byte_char_slices(cx, expr)
{
span_lint_and_sugg(
span_lint_and_then(
cx,
BYTE_CHAR_SLICES,
expr.span,
"can be more succinctly written as a byte str",
"try",
format!("b\"{slice}\""),
Applicability::MachineApplicable,
|diag| {
let mut app = Applicability::MachineApplicable;
let mut sugg = Sugg::hir_from_snippet(cx, expr, |_| {
let mut slice = slice.iter().fold("b\"".to_owned(), |mut acc, span| {
let snippet = snippet_with_applicability(cx, *span, "b'?'", &mut app);
acc.push_str(match &snippet[2..snippet.len() - 1] {
"\"" => "\\\"",
"\\'" => "'",
other => other,
});
acc
});
slice.push('"');
Cow::Owned(slice)
});
if !has_ref && !cx.typeck_results().expr_ty_adjusted(expr).is_array_slice() {
sugg = sugg.deref();
}

diag.span_suggestion(expr.span, "try", sugg, app);
},
);
}
}
}

/// Checks whether the slice is that of byte chars, and if so, builds a byte-string out of it
fn is_byte_char_slices(expr: &Expr) -> Option<String> {
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = &expr.kind
&& let ExprKind::Array(members) = &expr.kind
fn is_byte_char_slices<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<(bool, Vec<Span>)> {
let (has_ref, expr) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = expr.kind {
(true, inner)
} else if let Some(parent) = get_parent_expr(cx, expr) // Already checked by the parent expr.
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind
{
return None;
} else {
(false, expr)
};

if let ExprKind::Array(members) = expr.kind
&& !members.is_empty()
&& !span_contains_comment(cx, expr.span)
&& !span_contains_cfg(cx, expr.span)
{
members
return members
.iter()
.map(|member| match &member.kind {
ExprKind::Lit(Lit {
kind: LitKind::Byte,
symbol,
..
}) => Some(symbol.as_str()),
_ => None,
})
.map(|maybe_quote| match maybe_quote {
Some("\"") => Some("\\\""),
Some("\\'") => Some("'"),
other => other,
.try_fold(Vec::new(), |mut acc, member| {
if let ExprKind::Lit(lit) = member.kind
&& let LitKind::Byte(_) = lit.node
&& expr.span.eq_ctxt(member.span)
{
acc.push(lit.span);
return Some(acc);
}
None
})
.collect::<Option<String>>()
} else {
None
.map(|s| (has_ref, s));
}

None
}
2 changes: 1 addition & 1 deletion clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|| Box::new(visibility::Visibility)),
Box::new(|| Box::new(multiple_bound_locations::MultipleBoundLocations)),
Box::new(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)),
Box::new(|| Box::new(byte_char_slices::ByteCharSlice)),
Box::new(|| Box::new(cfg_not_test::CfgNotTest)),
Box::new(|| Box::new(empty_line_after::EmptyLineAfter::new())),
// add early passes here, used by `cargo dev new_lint`
Expand Down Expand Up @@ -867,6 +866,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)),
Box::new(move |tcx| Box::new(manual_pop_if::ManualPopIf::new(tcx, conf))),
Box::new(move |_| Box::new(manual_noop_waker::ManualNoopWaker::new(conf))),
Box::new(|_| Box::new(byte_char_slices::ByteCharSlice)),
// add late passes here, used by `cargo dev new_lint`
];
store.late_passes.extend(late_lints);
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/returns/needless_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ fn emit_return_lint(
// Go backwards while encountering whitespace and extend the given Span to that point.
fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
let ws = [b' ', b'\t', b'\n'];
let ws = *b" \t\n";
if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) {
let len = prev_source.len() - non_ws_pos - 1;
return sp.with_lo(sp.lo() - BytePos::from_usize(len));
Expand Down
40 changes: 37 additions & 3 deletions tests/ui/byte_char_slices.fixed
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![warn(clippy::byte_char_slices)]
#![allow(clippy::useless_vec)]

fn main() {
let bad = b"abc";
Expand All @@ -11,7 +12,40 @@ fn main() {
//~^ byte_char_slices

let good = &[b'a', 0x42];
let good = [b'a', b'a'];
//~^ useless_vec
let good: u8 = [b'a', b'c'].into_iter().sum();
let good = vec![b'a', b'a'];
}

fn takes_array_ref(_: &[u8; 2]) {}

fn takes_array_ref_ref(_: &&[u8; 2]) {}

fn issue16759(bytes: [u32; 3]) {
const START: u32 = u32::from_le_bytes(*b"WORK");
//~^ byte_char_slices

let auto_deref_to_slice: u8 = b"ac".iter().copied().sum();
//~^ byte_char_slices

let with_comment = [
// 1 2 3
b'a', b'b', b'c', // x
b'd', b'e', b'f', // 2x
b'g', b'h', b'i', // 3x
];
let with_cfg = [
b'a',
b'b',
b'c',
#[cfg(feature = "foo")]
b'd',
];

let with_escape: u8 = b"'\"\x00\n\\".iter().copied().sum();
//~^ byte_char_slices

takes_array_ref(b"ab");
//~^ byte_char_slices

takes_array_ref_ref(&b"ab");
//~^ byte_char_slices
}
38 changes: 36 additions & 2 deletions tests/ui/byte_char_slices.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![warn(clippy::byte_char_slices)]
#![allow(clippy::useless_vec)]

fn main() {
let bad = &[b'a', b'b', b'c'];
Expand All @@ -12,6 +13,39 @@ fn main() {

let good = &[b'a', 0x42];
let good = vec![b'a', b'a'];
//~^ useless_vec
let good: u8 = [b'a', b'c'].into_iter().sum();
}

fn takes_array_ref(_: &[u8; 2]) {}

fn takes_array_ref_ref(_: &&[u8; 2]) {}

fn issue16759(bytes: [u32; 3]) {
const START: u32 = u32::from_le_bytes([b'W', b'O', b'R', b'K']);
//~^ byte_char_slices

let auto_deref_to_slice: u8 = [b'a', b'c'].iter().copied().sum();
//~^ byte_char_slices

let with_comment = [
// 1 2 3
b'a', b'b', b'c', // x
b'd', b'e', b'f', // 2x
b'g', b'h', b'i', // 3x
];
let with_cfg = [
b'a',
b'b',
b'c',
#[cfg(feature = "foo")]
b'd',
];

let with_escape: u8 = [b'\'', b'"', b'\x00', b'\n', b'\\'].iter().copied().sum();
//~^ byte_char_slices

takes_array_ref(&[b'a', b'b']);
//~^ byte_char_slices

takes_array_ref_ref(&&[b'a', b'b']);
//~^ byte_char_slices
}
43 changes: 32 additions & 11 deletions tests/ui/byte_char_slices.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:4:15
--> tests/ui/byte_char_slices.rs:5:15
|
LL | let bad = &[b'a', b'b', b'c'];
| ^^^^^^^^^^^^^^^^^^^ help: try: `b"abc"`
Expand All @@ -8,31 +8,52 @@ LL | let bad = &[b'a', b'b', b'c'];
= help: to override `-D warnings` add `#[allow(clippy::byte_char_slices)]`

error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:6:18
--> tests/ui/byte_char_slices.rs:7:18
|
LL | let quotes = &[b'"', b'H', b'i'];
| ^^^^^^^^^^^^^^^^^^^ help: try: `b"\"Hi"`

error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:8:18
--> tests/ui/byte_char_slices.rs:9:18
|
LL | let quotes = &[b'\'', b'S', b'u', b'p'];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"'Sup"`

error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:10:19
--> tests/ui/byte_char_slices.rs:11:19
|
LL | let escapes = &[b'\x42', b'E', b's', b'c'];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"\x42Esc"`

error: useless use of `vec!`
--> tests/ui/byte_char_slices.rs:14:16
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:23:43
|
LL | const START: u32 = u32::from_le_bytes([b'W', b'O', b'R', b'K']);
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `*b"WORK"`

error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:26:35
|
LL | let auto_deref_to_slice: u8 = [b'a', b'c'].iter().copied().sum();
| ^^^^^^^^^^^^ help: try: `b"ac"`

error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:43:27
|
LL | let with_escape: u8 = [b'\'', b'"', b'\x00', b'\n', b'\\'].iter().copied().sum();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"'\"\x00\n\\"`

error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:46:21
|
LL | let good = vec![b'a', b'a'];
| ^^^^^^^^^^^^^^^^ help: you can use an array directly: `[b'a', b'a']`
LL | takes_array_ref(&[b'a', b'b']);
| ^^^^^^^^^^^^^ help: try: `b"ab"`

error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:49:26
|
= note: `-D clippy::useless-vec` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::useless_vec)]`
LL | takes_array_ref_ref(&&[b'a', b'b']);
| ^^^^^^^^^^^^^ help: try: `b"ab"`

error: aborting due to 5 previous errors
error: aborting due to 9 previous errors

2 changes: 1 addition & 1 deletion tests/ui/ptr_offset_by_literal.fixed
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![warn(clippy::ptr_offset_by_literal)]
#![allow(clippy::inconsistent_digit_grouping)]
#![allow(clippy::inconsistent_digit_grouping, clippy::byte_char_slices)]

fn main() {
let arr = [b'a', b'b', b'c'];
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/ptr_offset_by_literal.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![warn(clippy::ptr_offset_by_literal)]
#![allow(clippy::inconsistent_digit_grouping)]
#![allow(clippy::inconsistent_digit_grouping, clippy::byte_char_slices)]

fn main() {
let arr = [b'a', b'b', b'c'];
Expand Down
Loading