diff --git a/CHANGELOG.md b/CHANGELOG.md index dc308285319f..6a697f1e2fcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6778,6 +6778,7 @@ Released 2018-09-13 [`box_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_vec [`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local [`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code +[`bufreader_stdin`]: https://rust-lang.github.io/rust-clippy/master/index.html#bufreader_stdin [`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow [`by_ref_peekable_peek`]: https://rust-lang.github.io/rust-clippy/master/index.html#by_ref_peekable_peek [`byte_char_slices`]: https://rust-lang.github.io/rust-clippy/master/index.html#byte_char_slices diff --git a/clippy_lints/src/bufreader_stdin.rs b/clippy_lints/src/bufreader_stdin.rs new file mode 100644 index 000000000000..090e41fe6e01 --- /dev/null +++ b/clippy_lints/src/bufreader_stdin.rs @@ -0,0 +1,88 @@ +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::source::snippet; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, QPath, TyKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::impl_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `BufReader::new` with `Stdin` or `StdinLock`. + /// + /// ### Why is this bad? + /// `Stdin` is already buffered. Re-buffering it only increases the memcpy calls. + /// + /// ### Example + /// + /// ```ignore + /// let reader = std::io::BufReader::new(std::io::stdin()); + /// ``` + /// + /// Use instead: + /// + /// ```ignore + /// let stdin = std::io::stdin(); + /// let reader = stdin.lock(); + /// ``` + + #[clippy::version = "1.97.0"] + pub BUFREADER_STDIN, + pedantic, + "using `BufReader::new` with `Stdin` or `StdinLock` is unnecessary and less efficient" +} + +impl_lint_pass!(BufreaderStdin => [BUFREADER_STDIN]); + +pub struct BufreaderStdin {} + +impl BufreaderStdin { + pub fn new() -> Self { + Self {} + } +} + +impl<'tcx> LateLintPass<'tcx> for BufreaderStdin { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Call(func, [arg]) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(ty, segment)) = func.kind + && segment.ident.name == sym::new + && let TyKind::Path(ref qpath) = ty.kind + && let Some(did) = cx.qpath_res(qpath, ty.hir_id).opt_def_id() + && cx.tcx.is_diagnostic_item(sym::IoBufReader, did) + && let Some(arg_did) = cx + .typeck_results() + .expr_ty(arg) + .ty_adt_def() + .map(rustc_middle::ty::AdtDef::did) + { + let snip = snippet(cx, arg.span, ".."); + let applicability = if snip == ".." { + Applicability::HasPlaceholders + } else { + Applicability::MaybeIncorrect + }; + + let arg_did_name = cx.tcx.get_diagnostic_name(arg_did); + let (msg, help, sugg) = match arg_did_name { + Some(sym::Stdin) => ( + "wrapping already buffered `Stdin` into a `BufReader`", + "instead of wrapping `Stdin` in `BufReader`, use the `lock` method directly", + format!("{snip}.lock()"), + ), + Some(sym::StdinLock) => ( + "wrapping already buffered `StdinLock` into a `BufReader`", + "instead of wrapping `StdinLock` in `BufReader`, use it directly", + snip.to_string(), + ), + _ => return, + }; + + if arg.span.from_expansion() { + span_lint(cx, BUFREADER_STDIN, expr.span, msg); + } else { + span_lint_and_sugg(cx, BUFREADER_STDIN, expr.span, msg, help, sugg, applicability); + } + } + } +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index b14ee7f3de90..c58f69b1d7f6 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -44,6 +44,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO, crate::borrow_deref_ref::BORROW_DEREF_REF_INFO, crate::box_default::BOX_DEFAULT_INFO, + crate::bufreader_stdin::BUFREADER_STDIN_INFO, crate::byte_char_slices::BYTE_CHAR_SLICES_INFO, crate::cargo::CARGO_COMMON_METADATA_INFO, crate::cargo::LINT_GROUPS_PRIORITY_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 7a233e09e800..66b3fa9014cd 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -84,6 +84,7 @@ mod bool_to_int_with_if; mod booleans; mod borrow_deref_ref; mod box_default; +mod bufreader_stdin; mod byte_char_slices; mod cargo; mod casts; @@ -868,6 +869,7 @@ rustc_lint::late_lint_methods!( RedundantElse: redundant_else::RedundantElse = redundant_else::RedundantElse, RestWhenDestructuringStruct: rest_when_destructuring_struct::RestWhenDestructuringStruct = rest_when_destructuring_struct::RestWhenDestructuringStruct, BlockScrutinee: block_scrutinee::BlockScrutinee = block_scrutinee::BlockScrutinee, + BufreaderStdin: bufreader_stdin::BufreaderStdin = bufreader_stdin::BufreaderStdin::new(), // add late passes here, used by `cargo dev new_lint` ]] ); diff --git a/tests/ui/bufreader_stdin.fixed b/tests/ui/bufreader_stdin.fixed new file mode 100644 index 000000000000..ebf94ec9187d --- /dev/null +++ b/tests/ui/bufreader_stdin.fixed @@ -0,0 +1,19 @@ +#![warn(clippy::bufreader_stdin)] + +use std::io::{self, BufReader}; + +macro_rules! stdin_macro { + () => { + io::stdin() + }; +} + +fn main() { + let a = io::stdin(); + let reader = a.lock(); + //~^ bufreader_stdin + + let b = io::stdin().lock(); + let reader = b; + //~^ bufreader_stdin +} diff --git a/tests/ui/bufreader_stdin.rs b/tests/ui/bufreader_stdin.rs new file mode 100644 index 000000000000..1f16b4a3b7ef --- /dev/null +++ b/tests/ui/bufreader_stdin.rs @@ -0,0 +1,19 @@ +#![warn(clippy::bufreader_stdin)] + +use std::io::{self, BufReader}; + +macro_rules! stdin_macro { + () => { + io::stdin() + }; +} + +fn main() { + let a = io::stdin(); + let reader = BufReader::new(a); + //~^ bufreader_stdin + + let b = io::stdin().lock(); + let reader = BufReader::new(b); + //~^ bufreader_stdin +} diff --git a/tests/ui/bufreader_stdin.stderr b/tests/ui/bufreader_stdin.stderr new file mode 100644 index 000000000000..cde280ae7182 --- /dev/null +++ b/tests/ui/bufreader_stdin.stderr @@ -0,0 +1,22 @@ +error: wrapping already buffered `Stdin` into a `BufReader` + --> tests/ui/bufreader_stdin.rs:13:18 + | +LL | let reader = BufReader::new(a); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::bufreader-stdin` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::bufreader_stdin)]` +help: instead of wrapping `Stdin` in `BufReader`, use the `lock` method directly + | +LL - let reader = BufReader::new(a); +LL + let reader = a.lock(); + | + +error: wrapping already buffered `StdinLock` into a `BufReader` + --> tests/ui/bufreader_stdin.rs:17:18 + | +LL | let reader = BufReader::new(b); + | ^^^^^^^^^^^^^^^^^ help: instead of wrapping `StdinLock` in `BufReader`, use it directly: `b` + +error: aborting due to 2 previous errors +