From 471de0cb9f7ed11a437d830329050ee419d2ead0 Mon Sep 17 00:00:00 2001 From: Daniel Parks Date: Wed, 25 Jan 2023 21:22:25 -0800 Subject: [PATCH 01/86] Document `cargo-clippy` feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is possible to use conditional compilation to prevent Clippy from evaluating certain code. This adds a brief explanation of how to use the feature with conditional compilation, and mentions that generally it’s preferable to use something like `#[allow(clippy::all)]`. Fixes #10220 — Ability to skip files or blocks entirely --- book/src/configuration.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/book/src/configuration.md b/book/src/configuration.md index 87f4a697af9fd..b044507dfdb16 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -100,3 +100,24 @@ Note: `custom_inner_attributes` is an unstable feature so it has to be enabled e Lints that recognize this configuration option can be found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv) + +### Disabling evaluation of certain code + +> **Note:** This should only be used in cases where other solutions, like `#[allow(clippy::all)]`, are not sufficient. + +Very rarely, you may wish to prevent Clippy from evaluating certain sections of code entirely. You can do this with +[conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html) by checking that the +`cargo-clippy` feature is not set. You may need to provide a stub so that the code compiles: + +```rust +#[cfg(not(feature = "cargo-clippy"))] +include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs")); + +#[cfg(feature = "cargo-clippy")] +fn my_big_function(_input: &str) -> Option { + None +} +``` + +This feature is not actually part of your crate, so specifying `--all-features` to other tools, e.g. `cargo test +--all-features`, will not disable it. From 1e17a443b347098085981f4cb9df9313aedaaae0 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 31 Jan 2023 22:13:25 +0100 Subject: [PATCH 02/86] Remove the `NodeId` of `ast::ExprKind::Async` --- clippy_lints/src/redundant_async_block.rs | 2 +- clippy_lints/src/suspicious_operation_groupings.rs | 2 +- clippy_utils/src/ast_utils.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/redundant_async_block.rs b/clippy_lints/src/redundant_async_block.rs index 27ad430863743..56be8c07e9244 100644 --- a/clippy_lints/src/redundant_async_block.rs +++ b/clippy_lints/src/redundant_async_block.rs @@ -42,7 +42,7 @@ impl EarlyLintPass for RedundantAsyncBlock { if expr.span.from_expansion() { return; } - if let ExprKind::Async(_, _, block) = &expr.kind && block.stmts.len() == 1 && + if let ExprKind::Async(_, block) = &expr.kind && block.stmts.len() == 1 && let Some(Stmt { kind: StmtKind::Expr(last), .. }) = block.stmts.last() && let ExprKind::Await(future) = &last.kind && !future.span.from_expansion() && diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index 8aa47b62ebff4..fab8e9c2ec1c1 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -578,7 +578,7 @@ fn ident_difference_expr_with_base_location( | (Assign(_, _, _), Assign(_, _, _)) | (TryBlock(_), TryBlock(_)) | (Await(_), Await(_)) - | (Async(_, _, _), Async(_, _, _)) + | (Async(_, _), Async(_, _)) | (Block(_, _), Block(_, _)) | (Closure(_), Closure(_)) | (Match(_, _), Match(_, _)) diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 809d654603a69..d2dedc2043957 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -209,7 +209,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { && eq_fn_decl(lf, rf) && eq_expr(le, re) }, - (Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb), + (Async(lc, lb), Async(rc, rb)) => lc == rc && eq_block(lb, rb), (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt), (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re), (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp), From 4fdae81c70bfa1481eebc7ebec09f121c6eaf53c Mon Sep 17 00:00:00 2001 From: csmoe Date: Mon, 27 Feb 2023 11:57:32 +0000 Subject: [PATCH 03/86] add large future lint --- CHANGELOG.md | 3 +- clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/large_futures.rs | 90 +++++++++++++++++++ clippy_lints/src/lib.rs | 3 + clippy_lints/src/utils/conf.rs | 4 + tests/ui-toml/large_futures/clippy.toml | 1 + .../ui-toml/large_futures/large_futures.fixed | 29 ++++++ tests/ui-toml/large_futures/large_futures.rs | 29 ++++++ .../large_futures/large_futures.stderr | 10 +++ .../toml_unknown_key/conf_unknown_key.stderr | 1 + tests/ui/large_futures.fixed | 41 +++++++++ tests/ui/large_futures.rs | 41 +++++++++ tests/ui/large_futures.stderr | 40 +++++++++ 13 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 clippy_lints/src/large_futures.rs create mode 100644 tests/ui-toml/large_futures/clippy.toml create mode 100644 tests/ui-toml/large_futures/large_futures.fixed create mode 100644 tests/ui-toml/large_futures/large_futures.rs create mode 100644 tests/ui-toml/large_futures/large_futures.stderr create mode 100644 tests/ui/large_futures.fixed create mode 100644 tests/ui/large_futures.rs create mode 100644 tests/ui/large_futures.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 765826ed867d0..0352f956dfb75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -278,7 +278,7 @@ Released 2022-12-15 [#9490](https://github.com/rust-lang/rust-clippy/pull/9490) * [`almost_complete_letter_range`]: No longer lints in external macros [#9467](https://github.com/rust-lang/rust-clippy/pull/9467) -* [`drop_copy`]: No longer lints on idiomatic cases in match arms +* [`drop_copy`]: No longer lints on idiomatic cases in match arms [#9491](https://github.com/rust-lang/rust-clippy/pull/9491) * [`question_mark`]: No longer lints in const context [#9487](https://github.com/rust-lang/rust-clippy/pull/9487) @@ -4485,6 +4485,7 @@ Released 2018-09-13 [`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays [`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups [`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant +[`large_futures`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_futures [`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file [`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays [`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index cd5dd7a570653..208112b4025a1 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -216,6 +216,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO, crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO, crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO, + crate::large_futures::LARGE_FUTURES_INFO, crate::large_include_file::LARGE_INCLUDE_FILE_INFO, crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO, crate::len_zero::COMPARISON_TO_EMPTY_INFO, diff --git a/clippy_lints/src/large_futures.rs b/clippy_lints/src/large_futures.rs new file mode 100644 index 0000000000000..494bb2a97d265 --- /dev/null +++ b/clippy_lints/src/large_futures.rs @@ -0,0 +1,90 @@ +use clippy_utils::source::snippet; +use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_target::abi::Size; + +declare_clippy_lint! { + /// ### What it does + /// It checks for the size of a `Future` created by `async fn` or `async {}`. + /// + /// ### Why is this bad? + /// Due to the current [unideal implemention](https://github.com/rust-lang/rust/issues/69826) of `Generator`, + /// large size of a `Future` may cause stack overflows. + /// + /// ### Example + /// ```rust + /// async fn wait(f: impl std::future::Future) {} + /// + /// async fn big_fut(arg: [u8; 1024]) {} + /// + /// pub async fn test() { + /// let fut = big_fut([0u8; 1024]); + /// wait(fut).await; + /// } + /// ``` + /// + /// `Box::pin` the big future instead. + /// + /// ```rust + /// async fn wait(f: impl std::future::Future) {} + /// + /// async fn big_fut(arg: [u8; 1024]) {} + /// + /// pub async fn test() { + /// let fut = Box::pin(big_fut([0u8; 1024])); + /// wait(fut).await; + /// } + /// ``` + #[clippy::version = "1.68.0"] + pub LARGE_FUTURES, + pedantic, + "large future may lead to unexpected stack overflows" +} + +#[derive(Copy, Clone)] +pub struct LargeFuture { + future_size_threshold: u64, +} + +impl LargeFuture { + pub fn new(future_size_threshold: u64) -> Self { + Self { future_size_threshold } + } +} + +impl_lint_pass!(LargeFuture => [LARGE_FUTURES]); + +impl<'tcx> LateLintPass<'tcx> for LargeFuture { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind { + if let ExprKind::Call(func, [expr, ..]) = expr.kind { + if matches!( + func.kind, + ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) + ) { + let ty = cx.typeck_results().expr_ty(expr); + if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() + && implements_trait(cx, ty, future_trait_def_id, &[]) { + if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) { + let size = layout.layout.size(); + if size >= Size::from_bytes(self.future_size_threshold) { + span_lint_and_sugg( + cx, + LARGE_FUTURES, + expr.span, + &format!("large future with a size of {} bytes", size.bytes()), + "consider `Box::pin` on it", + format!("Box::pin({})", snippet(cx, expr.span, "..")), + Applicability::MachineApplicable, + ); + } + } + } + } + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 145cf524652f1..155aa106322be 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -161,6 +161,7 @@ mod items_after_statements; mod iter_not_returning_iterator; mod large_const_arrays; mod large_enum_variant; +mod large_futures; mod large_include_file; mod large_stack_arrays; mod len_zero; @@ -800,6 +801,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv()))); store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse)); store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend)); + let future_size_threshold = conf.future_size_threshold; + store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(future_size_threshold))); store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex)); store.register_late_pass(|_| Box::new(if_not_else::IfNotElse)); store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality)); diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 5f74de5a28867..639f8d9587768 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -459,6 +459,10 @@ define_Conf! { /// Whether to **only** check for missing documentation in items visible within the current /// crate. For example, `pub(crate)` items. (missing_docs_in_crate_items: bool = false), + /// Lint: LARGE_FUTURES. + /// + /// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint + (future_size_threshold: u64 = 16 * 1024), } /// Search for the configuration file. diff --git a/tests/ui-toml/large_futures/clippy.toml b/tests/ui-toml/large_futures/clippy.toml new file mode 100644 index 0000000000000..61bb17fdf6bdb --- /dev/null +++ b/tests/ui-toml/large_futures/clippy.toml @@ -0,0 +1 @@ +future-size-threshold = 1024 diff --git a/tests/ui-toml/large_futures/large_futures.fixed b/tests/ui-toml/large_futures/large_futures.fixed new file mode 100644 index 0000000000000..1238c512b0fa2 --- /dev/null +++ b/tests/ui-toml/large_futures/large_futures.fixed @@ -0,0 +1,29 @@ +// run-rustfix + +#![warn(clippy::large_futures)] + +fn main() {} + +pub async fn should_warn() { + let x = [0u8; 1024]; + async {}.await; + dbg!(x); +} + +pub async fn should_not_warn() { + let x = [0u8; 1020]; + async {}.await; + dbg!(x); +} + +pub async fn bar() { + Box::pin(should_warn()).await; + + async { + let x = [0u8; 1024]; + dbg!(x); + } + .await; + + should_not_warn().await; +} diff --git a/tests/ui-toml/large_futures/large_futures.rs b/tests/ui-toml/large_futures/large_futures.rs new file mode 100644 index 0000000000000..80039d9047b39 --- /dev/null +++ b/tests/ui-toml/large_futures/large_futures.rs @@ -0,0 +1,29 @@ +// run-rustfix + +#![warn(clippy::large_futures)] + +fn main() {} + +pub async fn should_warn() { + let x = [0u8; 1024]; + async {}.await; + dbg!(x); +} + +pub async fn should_not_warn() { + let x = [0u8; 1020]; + async {}.await; + dbg!(x); +} + +pub async fn bar() { + should_warn().await; + + async { + let x = [0u8; 1024]; + dbg!(x); + } + .await; + + should_not_warn().await; +} diff --git a/tests/ui-toml/large_futures/large_futures.stderr b/tests/ui-toml/large_futures/large_futures.stderr new file mode 100644 index 0000000000000..f7895f8eaf7a1 --- /dev/null +++ b/tests/ui-toml/large_futures/large_futures.stderr @@ -0,0 +1,10 @@ +error: large future with a size of 1026 bytes + --> $DIR/large_futures.rs:20:5 + | +LL | should_warn().await; + | ^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(should_warn())` + | + = note: `-D clippy::large-futures` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 6a246afac76e0..8447c31722dd0 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -24,6 +24,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie enforced-import-renames enum-variant-name-threshold enum-variant-size-threshold + future-size-threshold ignore-interior-mutability large-error-threshold literal-representation-threshold diff --git a/tests/ui/large_futures.fixed b/tests/ui/large_futures.fixed new file mode 100644 index 0000000000000..9d839998afd7b --- /dev/null +++ b/tests/ui/large_futures.fixed @@ -0,0 +1,41 @@ +// run-rustfix + +#![feature(generators)] +#![warn(clippy::large_futures)] +#![allow(clippy::future_not_send)] +#![allow(clippy::manual_async_fn)] + +async fn big_fut(_arg: [u8; 1024 * 16]) {} + +async fn wait() { + let f = async { + Box::pin(big_fut([0u8; 1024 * 16])).await; + }; + Box::pin(f).await +} +async fn calls_fut(fut: impl std::future::Future) { + loop { + Box::pin(wait()).await; + if true { + return fut.await; + } else { + Box::pin(wait()).await; + } + } +} + +pub async fn test() { + let fut = big_fut([0u8; 1024 * 16]); + Box::pin(foo()).await; + Box::pin(calls_fut(fut)).await; +} + +pub fn foo() -> impl std::future::Future { + async { + let x = [0i32; 1024 * 16]; + async {}.await; + dbg!(x); + } +} + +fn main() {} diff --git a/tests/ui/large_futures.rs b/tests/ui/large_futures.rs new file mode 100644 index 0000000000000..8b7aaa61b888b --- /dev/null +++ b/tests/ui/large_futures.rs @@ -0,0 +1,41 @@ +// run-rustfix + +#![feature(generators)] +#![warn(clippy::large_futures)] +#![allow(clippy::future_not_send)] +#![allow(clippy::manual_async_fn)] + +async fn big_fut(_arg: [u8; 1024 * 16]) {} + +async fn wait() { + let f = async { + big_fut([0u8; 1024 * 16]).await; + }; + f.await +} +async fn calls_fut(fut: impl std::future::Future) { + loop { + wait().await; + if true { + return fut.await; + } else { + wait().await; + } + } +} + +pub async fn test() { + let fut = big_fut([0u8; 1024 * 16]); + foo().await; + calls_fut(fut).await; +} + +pub fn foo() -> impl std::future::Future { + async { + let x = [0i32; 1024 * 16]; + async {}.await; + dbg!(x); + } +} + +fn main() {} diff --git a/tests/ui/large_futures.stderr b/tests/ui/large_futures.stderr new file mode 100644 index 0000000000000..557455299a9c0 --- /dev/null +++ b/tests/ui/large_futures.stderr @@ -0,0 +1,40 @@ +error: large future with a size of 16385 bytes + --> $DIR/large_futures.rs:12:9 + | +LL | big_fut([0u8; 1024 * 16]).await; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(big_fut([0u8; 1024 * 16]))` + | + = note: `-D clippy::large-futures` implied by `-D warnings` + +error: large future with a size of 16386 bytes + --> $DIR/large_futures.rs:14:5 + | +LL | f.await + | ^ help: consider `Box::pin` on it: `Box::pin(f)` + +error: large future with a size of 16387 bytes + --> $DIR/large_futures.rs:18:9 + | +LL | wait().await; + | ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())` + +error: large future with a size of 16387 bytes + --> $DIR/large_futures.rs:22:13 + | +LL | wait().await; + | ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())` + +error: large future with a size of 65540 bytes + --> $DIR/large_futures.rs:29:5 + | +LL | foo().await; + | ^^^^^ help: consider `Box::pin` on it: `Box::pin(foo())` + +error: large future with a size of 49159 bytes + --> $DIR/large_futures.rs:30:5 + | +LL | calls_fut(fut).await; + | ^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(calls_fut(fut))` + +error: aborting due to 6 previous errors + From 7f44530f54002401ba771bcc19f56f4950b22bb1 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sat, 18 Mar 2023 10:43:02 +0100 Subject: [PATCH 04/86] Create `clear_with_drain` lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/methods/clear_with_drain.rs | 8 ++++++++ clippy_lints/src/methods/mod.rs | 21 ++++++++++++++++++++ tests/ui/clear_with_drain.rs | 12 +++++++++++ 5 files changed, 43 insertions(+) create mode 100644 clippy_lints/src/methods/clear_with_drain.rs create mode 100644 tests/ui/clear_with_drain.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 47a503510c107..81d0ff809686d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4441,6 +4441,7 @@ Released 2018-09-13 [`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp [`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp [`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions +[`clear_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#clear_with_drain [`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref [`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy [`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 2331e857b1f79..f7b108c923adf 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -307,6 +307,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO, crate::methods::CHARS_LAST_CMP_INFO, crate::methods::CHARS_NEXT_CMP_INFO, + crate::methods::CLEAR_WITH_DRAIN_INFO, crate::methods::CLONED_INSTEAD_OF_COPIED_INFO, crate::methods::CLONE_DOUBLE_REF_INFO, crate::methods::CLONE_ON_COPY_INFO, diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints/src/methods/clear_with_drain.rs new file mode 100644 index 0000000000000..9e55852ef7a68 --- /dev/null +++ b/clippy_lints/src/methods/clear_with_drain.rs @@ -0,0 +1,8 @@ +use rustc_lint::{LateContext, LintContext}; + +use super::CLEAR_WITH_DRAIN; + +// TODO: Adjust the parameters as necessary +pub(super) fn check(cx: &LateContext) { + todo!(); +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 56e3988bf097b..859284b6a5cc3 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -9,6 +9,7 @@ mod chars_last_cmp; mod chars_last_cmp_with_unwrap; mod chars_next_cmp; mod chars_next_cmp_with_unwrap; +mod clear_with_drain; mod clone_on_copy; mod clone_on_ref_ptr; mod cloned_instead_of_copied; @@ -3190,6 +3191,25 @@ declare_clippy_lint! { "single command line argument that looks like it should be multiple arguments" } +declare_clippy_lint! { + /// ### What it does + /// + /// ### Why is this bad? + /// + /// ### Example + /// ```rust + /// // example code where clippy issues a warning + /// ``` + /// Use instead: + /// ```rust + /// // example code which does not raise clippy warning + /// ``` + #[clippy::version = "1.69.0"] + pub CLEAR_WITH_DRAIN, + nursery, + "default lint description" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -3318,6 +3338,7 @@ impl_lint_pass!(Methods => [ SEEK_TO_START_INSTEAD_OF_REWIND, NEEDLESS_COLLECT, SUSPICIOUS_COMMAND_ARG_SPACE, + CLEAR_WITH_DRAIN, ]); /// Extracts a method call name, args, and `Span` of the method name. diff --git a/tests/ui/clear_with_drain.rs b/tests/ui/clear_with_drain.rs new file mode 100644 index 0000000000000..bb3a371cc0897 --- /dev/null +++ b/tests/ui/clear_with_drain.rs @@ -0,0 +1,12 @@ +#![allow(unused)] +#![warn(clippy::clear_with_drain)] + +fn main() { + let mut vec: Vec = Vec::new(); + //Lint + vec.drain(..); + vec.drain(0..vec.len()); + + // Dont Lint + let iter = vec.drain(..); +} From 85d428101dc757f7aa41e47696c94683d8398903 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sat, 18 Mar 2023 11:34:30 +0100 Subject: [PATCH 05/86] Pull `is_full_range` method from `iter_with_drain` Rename method to `is_range_full` because the type is actually `RangeFull`. Method moved to `clippy_utils` for reuse in `clear_with_drain`. --- clippy_lints/src/methods/iter_with_drain.rs | 23 +++------------------ clippy_utils/src/lib.rs | 21 ++++++++++++++++++- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints/src/methods/iter_with_drain.rs index 3da230e12d7fe..ea92e3a549f49 100644 --- a/clippy_lints/src/methods/iter_with_drain.rs +++ b/clippy_lints/src/methods/iter_with_drain.rs @@ -1,9 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::Range; -use clippy_utils::is_integer_const; -use rustc_ast::ast::RangeLimits; +use clippy_utils::is_range_full; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; use rustc_span::Span; @@ -16,7 +15,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span && let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did()) && matches!(ty_name, sym::Vec | sym::VecDeque) && let Some(range) = Range::hir(arg) - && is_full_range(cx, recv, range) + && is_range_full(cx, recv, range) { span_lint_and_sugg( cx, @@ -29,19 +28,3 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span ); }; } - -fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool { - range.start.map_or(true, |e| is_integer_const(cx, e, 0)) - && range.end.map_or(true, |e| { - if range.limits == RangeLimits::HalfOpen - && let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind - && let ExprKind::MethodCall(name, self_arg, [], _) = e.kind - && name.ident.name == sym::len - && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind - { - container_path.res == path.res - } else { - false - } - }) -} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 44b6b9f7b0b14..0fdbd7a63411d 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -78,7 +78,7 @@ use std::sync::OnceLock; use std::sync::{Mutex, MutexGuard}; use if_chain::if_chain; -use rustc_ast::ast::{self, LitKind}; +use rustc_ast::ast::{self, LitKind, RangeLimits}; use rustc_ast::Attribute; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::unhash::UnhashMap; @@ -115,6 +115,7 @@ use rustc_span::Span; use rustc_target::abi::Integer; use crate::consts::{constant, Constant}; +use crate::higher::Range; use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param}; use crate::visitors::for_each_expr; @@ -1491,6 +1492,24 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { } } +/// Checks whether the given `Range` is equivalent to a `RangeFull`. +/// Inclusive ranges are not considered because they already constitute a lint. +pub fn is_range_full(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool { + range.start.map_or(true, |e| is_integer_const(cx, e, 0)) + && range.end.map_or(true, |e| { + if range.limits == RangeLimits::HalfOpen + && let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind + && let ExprKind::MethodCall(name, self_arg, [], _) = e.kind + && name.ident.name == sym::len + && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind + { + container_path.res == path.res + } else { + false + } + }) +} + /// Checks whether the given expression is a constant integer of the given value. /// unlike `is_integer_literal`, this version does const folding pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool { From 589d7e1ab40af9950d33715ef6cf866efa04f9a2 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sat, 18 Mar 2023 14:45:57 +0100 Subject: [PATCH 06/86] Add tests to cover all cases --- tests/ui/clear_with_drain.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/ui/clear_with_drain.rs b/tests/ui/clear_with_drain.rs index bb3a371cc0897..3f8517011cdb0 100644 --- a/tests/ui/clear_with_drain.rs +++ b/tests/ui/clear_with_drain.rs @@ -1,12 +1,28 @@ #![allow(unused)] #![warn(clippy::clear_with_drain)] -fn main() { - let mut vec: Vec = Vec::new(); - //Lint - vec.drain(..); - vec.drain(0..vec.len()); +fn range() { + let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); + let iter = u.drain(0..u.len()); // Yay + v.drain(0..v.len()); // Nay +} + +fn range_from() { + let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); + let iter = u.drain(0..); // Yay + v.drain(0..); // Nay +} - // Dont Lint - let iter = vec.drain(..); +fn range_full() { + let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); + let iter = u.drain(..); // Yay + v.drain(..); // Nay } + +fn range_to() { + let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); + let iter = u.drain(..u.len()); // Yay + v.drain(..v.len()); // Nay +} + +fn main() {} From 484c82e0418325df5d25e8d0e11e10a5d1371502 Mon Sep 17 00:00:00 2001 From: bluthej Date: Mon, 20 Mar 2023 18:46:18 +0100 Subject: [PATCH 07/86] Update lint declaration --- clippy_lints/src/methods/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 859284b6a5cc3..4e74ac6af2bb5 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3193,21 +3193,27 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does + /// Checks for usage of `.drain(..)` for the sole purpose of clearing a `Vec`. /// /// ### Why is this bad? + /// This creates an unnecessary iterator that is dropped immediately. + /// + /// Calling `.clear()` also makes the intent clearer. /// /// ### Example /// ```rust - /// // example code where clippy issues a warning + /// let mut v = vec![1, 2, 3]; + /// v.drain(..); /// ``` /// Use instead: /// ```rust - /// // example code which does not raise clippy warning + /// let mut v = vec![1, 2, 3]; + /// v.clear(); /// ``` #[clippy::version = "1.69.0"] pub CLEAR_WITH_DRAIN, nursery, - "default lint description" + "calling `drain` in order to `clear` a `Vec`" } pub struct Methods { From c7e3e304d5d2ac82dbebe5f3d3e0611b407d81be Mon Sep 17 00:00:00 2001 From: bluthej Date: Tue, 21 Mar 2023 22:08:37 +0100 Subject: [PATCH 08/86] Add the lint logic --- clippy_lints/src/methods/clear_with_drain.rs | 27 +++++++++++++++++--- clippy_lints/src/methods/mod.rs | 10 ++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints/src/methods/clear_with_drain.rs index 9e55852ef7a68..ac6c0e57257c7 100644 --- a/clippy_lints/src/methods/clear_with_drain.rs +++ b/clippy_lints/src/methods/clear_with_drain.rs @@ -1,8 +1,29 @@ -use rustc_lint::{LateContext, LintContext}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::higher::Range; +use clippy_utils::is_range_full; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; +use rustc_span::Span; use super::CLEAR_WITH_DRAIN; // TODO: Adjust the parameters as necessary -pub(super) fn check(cx: &LateContext) { - todo!(); +// see clippy_lints/src/methods/mod.rs to add call to this check in `check_methods` +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) { + let ty = cx.typeck_results().expr_ty(recv); + if is_type_diagnostic_item(cx, ty, sym::Vec) && let Some(range) = Range::hir(arg) && is_range_full(cx, recv, range) + { + span_lint_and_sugg( + cx, + CLEAR_WITH_DRAIN, + span.with_hi(expr.span.hi()), + "`drain` used to clear a `Vec`", + "try", + "clear()".to_string(), + Applicability::MachineApplicable, + ); + } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 4e74ac6af2bb5..257bc4eccc302 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -111,7 +111,7 @@ use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_ use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, return_ty}; use if_chain::if_chain; use rustc_hir as hir; -use rustc_hir::{Expr, ExprKind, TraitItem, TraitItemKind}; +use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; @@ -3590,7 +3590,13 @@ impl Methods { _ => {}, }, ("drain", [arg]) => { - iter_with_drain::check(cx, expr, recv, span, arg); + if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.hir().get_parent(expr.hir_id) + && matches!(kind, StmtKind::Semi(_)) + { + clear_with_drain::check(cx, expr, recv, span, arg); + } else { + iter_with_drain::check(cx, expr, recv, span, arg); + } }, ("ends_with", [arg]) => { if let ExprKind::MethodCall(.., span) = expr.kind { From 1d0acce984f9be9f085fd5ebcb44dd6988b1f295 Mon Sep 17 00:00:00 2001 From: bluthej Date: Tue, 21 Mar 2023 22:09:26 +0100 Subject: [PATCH 09/86] Add test for partial range --- tests/ui/clear_with_drain.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/ui/clear_with_drain.rs b/tests/ui/clear_with_drain.rs index 3f8517011cdb0..28119deaebbe9 100644 --- a/tests/ui/clear_with_drain.rs +++ b/tests/ui/clear_with_drain.rs @@ -25,4 +25,15 @@ fn range_to() { v.drain(..v.len()); // Nay } +fn partial_drains() { + let mut v = vec![1, 2, 3]; + v.drain(1..); // Yay + + let mut v = vec![1, 2, 3]; + v.drain(..v.len() - 1); // Yay + + let mut v = vec![1, 2, 3]; + v.drain(1..v.len() - 1); // Yay +} + fn main() {} From e8ec242a61e7fcadf1a8cb3c6f7ec162b5e2726e Mon Sep 17 00:00:00 2001 From: bluthej Date: Tue, 21 Mar 2023 22:46:59 +0100 Subject: [PATCH 10/86] Finish tests - add rustfix --- clippy_lints/src/methods/clear_with_drain.rs | 2 - tests/ui/clear_with_drain.fixed | 40 ++++++++++++++++++++ tests/ui/clear_with_drain.rs | 1 + tests/ui/clear_with_drain.stderr | 28 ++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 tests/ui/clear_with_drain.fixed create mode 100644 tests/ui/clear_with_drain.stderr diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints/src/methods/clear_with_drain.rs index ac6c0e57257c7..bf7e7d1240511 100644 --- a/clippy_lints/src/methods/clear_with_drain.rs +++ b/clippy_lints/src/methods/clear_with_drain.rs @@ -10,8 +10,6 @@ use rustc_span::Span; use super::CLEAR_WITH_DRAIN; -// TODO: Adjust the parameters as necessary -// see clippy_lints/src/methods/mod.rs to add call to this check in `check_methods` pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv); if is_type_diagnostic_item(cx, ty, sym::Vec) && let Some(range) = Range::hir(arg) && is_range_full(cx, recv, range) diff --git a/tests/ui/clear_with_drain.fixed b/tests/ui/clear_with_drain.fixed new file mode 100644 index 0000000000000..62b0af08eeae3 --- /dev/null +++ b/tests/ui/clear_with_drain.fixed @@ -0,0 +1,40 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::clear_with_drain)] + +fn range() { + let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); + let iter = u.drain(0..u.len()); // Yay + v.clear(); // Nay +} + +fn range_from() { + let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); + let iter = u.drain(0..); // Yay + v.clear(); // Nay +} + +fn range_full() { + let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); + let iter = u.drain(..); // Yay + v.clear(); // Nay +} + +fn range_to() { + let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); + let iter = u.drain(..u.len()); // Yay + v.clear(); // Nay +} + +fn partial_drains() { + let mut v = vec![1, 2, 3]; + v.drain(1..); // Yay + + let mut v = vec![1, 2, 3]; + v.drain(..v.len() - 1); // Yay + + let mut v = vec![1, 2, 3]; + v.drain(1..v.len() - 1); // Yay +} + +fn main() {} diff --git a/tests/ui/clear_with_drain.rs b/tests/ui/clear_with_drain.rs index 28119deaebbe9..721af7d2ea7e3 100644 --- a/tests/ui/clear_with_drain.rs +++ b/tests/ui/clear_with_drain.rs @@ -1,3 +1,4 @@ +// run-rustfix #![allow(unused)] #![warn(clippy::clear_with_drain)] diff --git a/tests/ui/clear_with_drain.stderr b/tests/ui/clear_with_drain.stderr new file mode 100644 index 0000000000000..0d6d8263e10da --- /dev/null +++ b/tests/ui/clear_with_drain.stderr @@ -0,0 +1,28 @@ +error: `drain` used to clear a `Vec` + --> $DIR/clear_with_drain.rs:8:7 + | +LL | v.drain(0..v.len()); // Nay + | ^^^^^^^^^^^^^^^^^ help: try: `clear()` + | + = note: `-D clippy::clear-with-drain` implied by `-D warnings` + +error: `drain` used to clear a `Vec` + --> $DIR/clear_with_drain.rs:14:7 + | +LL | v.drain(0..); // Nay + | ^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `Vec` + --> $DIR/clear_with_drain.rs:20:7 + | +LL | v.drain(..); // Nay + | ^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `Vec` + --> $DIR/clear_with_drain.rs:26:7 + | +LL | v.drain(..v.len()); // Nay + | ^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: aborting due to 4 previous errors + From a90e5cc9af1fcf7897194ac6a275407dc54db689 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 17 Mar 2023 13:43:32 +0000 Subject: [PATCH 11/86] Use split-debuginfo = "unpacked" for debug builds --- .cargo/config.toml | 3 +++ .github/workflows/clippy_bors.yml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index f3dd9275a42be..4d80d3ce63dac 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -11,3 +11,6 @@ target-dir = "target" [unstable] binary-dep-depinfo = true + +[profile.dev] +split-debuginfo = "unpacked" diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 24e677ce8e170..93198aabdb5f5 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -180,6 +180,8 @@ jobs: # Run - name: Build Integration Test + env: + CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: off run: cargo test --test integration --features integration --no-run # Upload From ecc201253ee060071442f6f0c1a52b380aa3b3af Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Thu, 23 Mar 2023 18:58:10 +0000 Subject: [PATCH 12/86] Wrap transmutes_expressible_as_ptr_casts suggestions in parentheses --- .../transmute/transmutes_expressible_as_ptr_casts.rs | 11 +++++++++-- tests/ui/transmutes_expressible_as_ptr_casts.fixed | 8 +++++++- tests/ui/transmutes_expressible_as_ptr_casts.rs | 8 +++++++- tests/ui/transmutes_expressible_as_ptr_casts.stderr | 8 +++++++- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs index 8530b43243fa3..85cd74f23ef7e 100644 --- a/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs +++ b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs @@ -2,8 +2,9 @@ use super::utils::check_cast; use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; +use rustc_ast::ExprPrecedence; use rustc_errors::Applicability; -use rustc_hir::Expr; +use rustc_hir::{Expr, Node}; use rustc_lint::LateContext; use rustc_middle::ty::{cast::CastKind, Ty}; @@ -19,7 +20,7 @@ pub(super) fn check<'tcx>( ) -> bool { use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast}; let mut app = Applicability::MachineApplicable; - let sugg = match check_cast(cx, e, from_ty, to_ty) { + let mut sugg = match check_cast(cx, e, from_ty, to_ty) { Some(PtrPtrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) => { Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app) .as_ty(to_ty.to_string()) @@ -39,6 +40,12 @@ pub(super) fn check<'tcx>( _ => return false, }; + if let Node::Expr(parent) = cx.tcx.hir().get_parent(e.hir_id) + && parent.precedence().order() > ExprPrecedence::Cast.order() + { + sugg = format!("({sugg})"); + } + span_lint_and_sugg( cx, TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/tests/ui/transmutes_expressible_as_ptr_casts.fixed index 55307506eb3c7..cc84ba25bd0d7 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.fixed +++ b/tests/ui/transmutes_expressible_as_ptr_casts.fixed @@ -4,7 +4,7 @@ // would otherwise be responsible for #![warn(clippy::useless_transmute)] #![warn(clippy::transmute_ptr_to_ptr)] -#![allow(dead_code, unused_unsafe, clippy::borrow_as_ptr)] +#![allow(unused, clippy::borrow_as_ptr)] use std::mem::{size_of, transmute}; @@ -77,3 +77,9 @@ fn cannot_be_expressed_as_pointer_cast(in_param: Single) -> Pair { unsafe { transmute::(in_param) } } + +fn issue_10449() { + fn f() {} + + let _x: u8 = unsafe { *(f as *const u8) }; +} diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.rs b/tests/ui/transmutes_expressible_as_ptr_casts.rs index e7360f3f9dcba..aa65ab4dd2475 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.rs +++ b/tests/ui/transmutes_expressible_as_ptr_casts.rs @@ -4,7 +4,7 @@ // would otherwise be responsible for #![warn(clippy::useless_transmute)] #![warn(clippy::transmute_ptr_to_ptr)] -#![allow(dead_code, unused_unsafe, clippy::borrow_as_ptr)] +#![allow(unused, clippy::borrow_as_ptr)] use std::mem::{size_of, transmute}; @@ -77,3 +77,9 @@ fn cannot_be_expressed_as_pointer_cast(in_param: Single) -> Pair { unsafe { transmute::(in_param) } } + +fn issue_10449() { + fn f() {} + + let _x: u8 = unsafe { *std::mem::transmute::(f) }; +} diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.stderr b/tests/ui/transmutes_expressible_as_ptr_casts.stderr index e862fcb67a4a0..58f5162c78e78 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.stderr +++ b/tests/ui/transmutes_expressible_as_ptr_casts.stderr @@ -58,5 +58,11 @@ error: transmute from a reference to a pointer LL | unsafe { transmute::<&[i32; 1], *const u8>(in_param) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `in_param as *const [i32; 1] as *const u8` -error: aborting due to 9 previous errors +error: transmute from `fn()` to `*const u8` which could be expressed as a pointer cast instead + --> $DIR/transmutes_expressible_as_ptr_casts.rs:84:28 + | +LL | let _x: u8 = unsafe { *std::mem::transmute::(f) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(f as *const u8)` + +error: aborting due to 10 previous errors From 765c6b865cc5bac5dc173bdfa9524ca210d1c10f Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Thu, 10 Nov 2022 11:37:28 -0500 Subject: [PATCH 13/86] A MIR transform that checks pointers are aligned --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 9f6adf3e3fab7..cd9cc8bccb6e8 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -169,7 +169,7 @@ fn check_rvalue<'tcx>( Err((span, "unsizing casts are not allowed in const fn".into())) } }, - Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => { + Rvalue::Cast(CastKind::PointerExposeAddress | CastKind::PointerAddress, _, _) => { Err((span, "casting pointers to ints is unstable in const fn".into())) }, Rvalue::Cast(CastKind::DynStar, _, _) => { From f96e38becba21476460a8596f9af058f14f24339 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Thu, 23 Mar 2023 20:19:45 -0400 Subject: [PATCH 14/86] Fix clippy --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index cd9cc8bccb6e8..9f6adf3e3fab7 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -169,7 +169,7 @@ fn check_rvalue<'tcx>( Err((span, "unsizing casts are not allowed in const fn".into())) } }, - Rvalue::Cast(CastKind::PointerExposeAddress | CastKind::PointerAddress, _, _) => { + Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => { Err((span, "casting pointers to ints is unstable in const fn".into())) }, Rvalue::Cast(CastKind::DynStar, _, _) => { From 50d92d0b60a0ac28b5311e0a04e3353af862079c Mon Sep 17 00:00:00 2001 From: Michael Krasnitski Date: Wed, 22 Mar 2023 22:34:53 -0400 Subject: [PATCH 15/86] Add suggestions to `extra_unused_type_parameters` --- .../src/extra_unused_type_parameters.rs | 177 ++++++++++++------ .../extra_unused_type_parameters/clippy.toml | 1 + .../extra_unused_type_parameters.rs | 9 + tests/ui/extra_unused_type_parameters.fixed | 105 +++++++++++ tests/ui/extra_unused_type_parameters.rs | 11 +- tests/ui/extra_unused_type_parameters.stderr | 79 ++++---- .../extra_unused_type_parameters_unfixable.rs | 24 +++ ...ra_unused_type_parameters_unfixable.stderr | 27 +++ 8 files changed, 320 insertions(+), 113 deletions(-) create mode 100644 tests/ui-toml/extra_unused_type_parameters/clippy.toml create mode 100644 tests/ui-toml/extra_unused_type_parameters/extra_unused_type_parameters.rs create mode 100644 tests/ui/extra_unused_type_parameters.fixed create mode 100644 tests/ui/extra_unused_type_parameters_unfixable.rs create mode 100644 tests/ui/extra_unused_type_parameters_unfixable.stderr diff --git a/clippy_lints/src/extra_unused_type_parameters.rs b/clippy_lints/src/extra_unused_type_parameters.rs index 20565e1d232ee..eeb4de8b58f48 100644 --- a/clippy_lints/src/extra_unused_type_parameters.rs +++ b/clippy_lints/src/extra_unused_type_parameters.rs @@ -1,10 +1,10 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; use clippy_utils::trait_ref_of_method; -use rustc_data_structures::fx::FxHashMap; -use rustc_errors::MultiSpan; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_impl_item, walk_item, walk_param_bound, walk_ty, Visitor}; use rustc_hir::{ - BodyId, ExprKind, GenericBound, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind, + BodyId, ExprKind, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind, PredicateOrigin, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -53,13 +53,19 @@ impl ExtraUnusedTypeParameters { } } - /// Don't lint external macros or functions with empty bodies. Also, don't lint public items if - /// the `avoid_breaking_exported_api` config option is set. - fn check_false_positive(&self, cx: &LateContext<'_>, span: Span, def_id: LocalDefId, body_id: BodyId) -> bool { + /// Don't lint external macros or functions with empty bodies. Also, don't lint exported items + /// if the `avoid_breaking_exported_api` config option is set. + fn is_empty_exported_or_macro( + &self, + cx: &LateContext<'_>, + span: Span, + def_id: LocalDefId, + body_id: BodyId, + ) -> bool { let body = cx.tcx.hir().body(body_id).value; let fn_empty = matches!(&body.kind, ExprKind::Block(blk, None) if blk.stmts.is_empty() && blk.expr.is_none()); let is_exported = cx.effective_visibilities.is_exported(def_id); - in_external_macro(cx.sess(), span) || (self.avoid_breaking_exported_api && is_exported) || fn_empty + in_external_macro(cx.sess(), span) || fn_empty || (is_exported && self.avoid_breaking_exported_api) } } @@ -69,85 +75,129 @@ impl_lint_pass!(ExtraUnusedTypeParameters => [EXTRA_UNUSED_TYPE_PARAMETERS]); /// trait bounds those parameters have. struct TypeWalker<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, - /// Collection of all the function's type parameters. + /// Collection of the function's type parameters. Once the function has been walked, this will + /// contain only unused type parameters. ty_params: FxHashMap, - /// Collection of any (inline) trait bounds corresponding to each type parameter. - bounds: FxHashMap, + /// Collection of any inline trait bounds corresponding to each type parameter. + inline_bounds: FxHashMap, + /// Collection of any type parameters with trait bounds that appear in a where clause. + where_bounds: FxHashSet, /// The entire `Generics` object of the function, useful for querying purposes. generics: &'tcx Generics<'tcx>, - /// The value of this will remain `true` if *every* parameter: - /// 1. Is a type parameter, and - /// 2. Goes unused in the function. - /// Otherwise, if any type parameters end up being used, or if any lifetime or const-generic - /// parameters are present, this will be set to `false`. - all_params_unused: bool, } impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { fn new(cx: &'cx LateContext<'tcx>, generics: &'tcx Generics<'tcx>) -> Self { - let mut all_params_unused = true; let ty_params = generics .params .iter() - .filter_map(|param| { - if let GenericParamKind::Type { synthetic, .. } = param.kind { - (!synthetic).then_some((param.def_id.into(), param.span)) - } else { - if !param.is_elided_lifetime() { - all_params_unused = false; - } - None - } + .filter_map(|param| match param.kind { + GenericParamKind::Type { synthetic, .. } if !synthetic => Some((param.def_id.into(), param.span)), + _ => None, }) .collect(); Self { cx, ty_params, - bounds: FxHashMap::default(), + inline_bounds: FxHashMap::default(), + where_bounds: FxHashSet::default(), generics, - all_params_unused, } } - fn mark_param_used(&mut self, def_id: DefId) { - if self.ty_params.remove(&def_id).is_some() { - self.all_params_unused = false; - } + fn get_bound_span(&self, param: &'tcx GenericParam<'tcx>) -> Span { + self.inline_bounds + .get(¶m.def_id.to_def_id()) + .map_or(param.span, |bound_span| param.span.with_hi(bound_span.hi())) + } + + fn emit_help(&self, spans: Vec, msg: &str, help: &'static str) { + span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, None, help); + } + + fn emit_sugg(&self, spans: Vec, msg: &str, help: &'static str) { + let suggestions: Vec<(Span, String)> = spans.iter().copied().zip(std::iter::repeat(String::new())).collect(); + span_lint_and_then(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, |diag| { + diag.multipart_suggestion(help, suggestions, Applicability::MachineApplicable); + }); } fn emit_lint(&self) { - let (msg, help) = match self.ty_params.len() { + let explicit_params = self + .generics + .params + .iter() + .filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait()) + .collect::>(); + + let extra_params = explicit_params + .iter() + .enumerate() + .filter(|(_, param)| self.ty_params.contains_key(¶m.def_id.to_def_id())) + .collect::>(); + + let (msg, help) = match extra_params.len() { 0 => return, 1 => ( - "type parameter goes unused in function definition", + format!( + "type parameter `{}` goes unused in function definition", + extra_params[0].1.name.ident() + ), "consider removing the parameter", ), _ => ( - "type parameters go unused in function definition", + format!( + "type parameters go unused in function definition: {}", + extra_params + .iter() + .map(|(_, param)| param.name.ident().to_string()) + .collect::>() + .join(", ") + ), "consider removing the parameters", ), }; - let source_map = self.cx.sess().source_map(); - let span = if self.all_params_unused { - self.generics.span.into() // Remove the entire list of generics + // If any parameters are bounded in where clauses, don't try to form a suggestion. + // Otherwise, the leftover where bound would produce code that wouldn't compile. + if extra_params + .iter() + .any(|(_, param)| self.where_bounds.contains(¶m.def_id.to_def_id())) + { + let spans = extra_params + .iter() + .map(|(_, param)| self.get_bound_span(param)) + .collect::>(); + self.emit_help(spans, &msg, help); } else { - MultiSpan::from_spans( - self.ty_params + let spans = if explicit_params.len() == extra_params.len() { + vec![self.generics.span] // Remove the entire list of generics + } else { + let mut end: Option = None; + extra_params .iter() - .map(|(def_id, &span)| { - // Extend the span past any trait bounds, and include the comma at the end. - let span_to_extend = self.bounds.get(def_id).copied().map_or(span, Span::shrink_to_hi); - let comma_range = source_map.span_extend_to_next_char(span_to_extend, '>', false); - let comma_span = source_map.span_through_char(comma_range, ','); - span.with_hi(comma_span.hi()) + .rev() + .map(|(idx, param)| { + if let Some(next) = explicit_params.get(idx + 1) && end != Some(next.def_id) { + // Extend the current span forward, up until the next param in the list. + param.span.until(next.span) + } else { + // Extend the current span back to include the comma following the previous + // param. If the span of the next param in the list has already been + // extended, we continue the chain. This is why we're iterating in reverse. + end = Some(param.def_id); + + // idx will never be 0, else we'd be removing the entire list of generics + let prev = explicit_params[idx - 1]; + let prev_span = self.get_bound_span(prev); + self.get_bound_span(param).with_lo(prev_span.hi()) + } }) - .collect(), - ) + .collect() + }; + self.emit_sugg(spans, &msg, help); }; - - span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, span, msg, None, help); } } @@ -162,7 +212,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) { if let Some((def_id, _)) = t.peel_refs().as_generic_param() { - self.mark_param_used(def_id); + self.ty_params.remove(&def_id); } else if let TyKind::OpaqueDef(id, _, _) = t.kind { // Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls // `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're @@ -176,9 +226,18 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) { if let WherePredicate::BoundPredicate(predicate) = predicate { - // Collect spans for any bounds on type parameters. We only keep bounds that appear in - // the list of generics (not in a where-clause). + // Collect spans for any bounds on type parameters. if let Some((def_id, _)) = predicate.bounded_ty.peel_refs().as_generic_param() { + match predicate.origin { + PredicateOrigin::GenericParam => { + self.inline_bounds.insert(def_id, predicate.span); + }, + PredicateOrigin::WhereClause => { + self.where_bounds.insert(def_id); + }, + PredicateOrigin::ImplTrait => (), + } + // If the bound contains non-public traits, err on the safe side and don't lint the // corresponding parameter. if !predicate @@ -187,12 +246,10 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { .filter_map(bound_to_trait_def_id) .all(|id| self.cx.effective_visibilities.is_exported(id)) { - self.mark_param_used(def_id); - } else if let PredicateOrigin::GenericParam = predicate.origin { - self.bounds.insert(def_id, predicate.span); + self.ty_params.remove(&def_id); } } - // Only walk the right-hand side of where-bounds + // Only walk the right-hand side of where bounds for bound in predicate.bounds { walk_param_bound(self, bound); } @@ -207,7 +264,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { if let ItemKind::Fn(_, generics, body_id) = item.kind - && !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id) + && !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id) { let mut walker = TypeWalker::new(cx, generics); walk_item(&mut walker, item); @@ -219,7 +276,7 @@ impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters { // Only lint on inherent methods, not trait methods. if let ImplItemKind::Fn(.., body_id) = item.kind && trait_ref_of_method(cx, item.owner_id.def_id).is_none() - && !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id) + && !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id) { let mut walker = TypeWalker::new(cx, item.generics); walk_impl_item(&mut walker, item); diff --git a/tests/ui-toml/extra_unused_type_parameters/clippy.toml b/tests/ui-toml/extra_unused_type_parameters/clippy.toml new file mode 100644 index 0000000000000..5f304987aa94a --- /dev/null +++ b/tests/ui-toml/extra_unused_type_parameters/clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = true diff --git a/tests/ui-toml/extra_unused_type_parameters/extra_unused_type_parameters.rs b/tests/ui-toml/extra_unused_type_parameters/extra_unused_type_parameters.rs new file mode 100644 index 0000000000000..5655232455cb7 --- /dev/null +++ b/tests/ui-toml/extra_unused_type_parameters/extra_unused_type_parameters.rs @@ -0,0 +1,9 @@ +pub struct S; + +impl S { + pub fn exported_fn() { + unimplemented!(); + } +} + +fn main() {} diff --git a/tests/ui/extra_unused_type_parameters.fixed b/tests/ui/extra_unused_type_parameters.fixed new file mode 100644 index 0000000000000..19e718625582c --- /dev/null +++ b/tests/ui/extra_unused_type_parameters.fixed @@ -0,0 +1,105 @@ +// run-rustfix + +#![allow(unused, clippy::needless_lifetimes)] +#![warn(clippy::extra_unused_type_parameters)] + +fn unused_ty(x: u8) { + unimplemented!() +} + +fn unused_multi(x: u8) { + unimplemented!() +} + +fn unused_with_lt<'a>(x: &'a u8) { + unimplemented!() +} + +fn used_ty(x: T, y: u8) {} + +fn used_ref<'a, T>(x: &'a T) {} + +fn used_ret(x: u8) -> T { + T::default() +} + +fn unused_bounded(x: U) { + unimplemented!(); +} + +fn some_unused(b: B, c: C) { + unimplemented!(); +} + +fn used_opaque(iter: impl Iterator) -> usize { + iter.count() +} + +fn used_ret_opaque() -> impl Iterator { + std::iter::empty() +} + +fn used_vec_box(x: Vec>) {} + +fn used_body() -> String { + T::default().to_string() +} + +fn used_closure() -> impl Fn() { + || println!("{}", T::default().to_string()) +} + +struct S; + +impl S { + fn unused_ty_impl(&self) { + unimplemented!() + } +} + +// Don't lint on trait methods +trait Foo { + fn bar(&self); +} + +impl Foo for S { + fn bar(&self) {} +} + +fn skip_index(iter: Iter, index: usize) -> impl Iterator +where + Iter: Iterator, +{ + iter.enumerate() + .filter_map(move |(i, a)| if i == index { None } else { Some(a) }) +} + +fn unused_opaque(dummy: impl Default) { + unimplemented!() +} + +mod unexported_trait_bounds { + mod private { + pub trait Private {} + } + + fn priv_trait_bound() { + unimplemented!(); + } + + fn unused_with_priv_trait_bound() { + unimplemented!(); + } +} + +mod issue10319 { + fn assert_send() {} + + fn assert_send_where() + where + T: Send, + { + } +} + +fn main() {} diff --git a/tests/ui/extra_unused_type_parameters.rs b/tests/ui/extra_unused_type_parameters.rs index 4801743427657..e53bb587e89ac 100644 --- a/tests/ui/extra_unused_type_parameters.rs +++ b/tests/ui/extra_unused_type_parameters.rs @@ -1,3 +1,5 @@ +// run-rustfix + #![allow(unused, clippy::needless_lifetimes)] #![warn(clippy::extra_unused_type_parameters)] @@ -21,14 +23,7 @@ fn used_ret(x: u8) -> T { T::default() } -fn unused_bounded(x: U) { - unimplemented!(); -} - -fn unused_where_clause(x: U) -where - T: Default, -{ +fn unused_bounded(x: U) { unimplemented!(); } diff --git a/tests/ui/extra_unused_type_parameters.stderr b/tests/ui/extra_unused_type_parameters.stderr index 86c88fc9bf006..c042a5a2290e5 100644 --- a/tests/ui/extra_unused_type_parameters.stderr +++ b/tests/ui/extra_unused_type_parameters.stderr @@ -1,75 +1,64 @@ -error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:4:13 +error: type parameter `T` goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:6:13 | LL | fn unused_ty(x: u8) { - | ^^^ + | ^^^ help: consider removing the parameter | - = help: consider removing the parameter = note: `-D clippy::extra-unused-type-parameters` implied by `-D warnings` -error: type parameters go unused in function definition - --> $DIR/extra_unused_type_parameters.rs:8:16 +error: type parameters go unused in function definition: T, U + --> $DIR/extra_unused_type_parameters.rs:10:16 | LL | fn unused_multi(x: u8) { - | ^^^^^^ - | - = help: consider removing the parameters + | ^^^^^^ help: consider removing the parameters -error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:12:23 +error: type parameter `T` goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:14:21 | LL | fn unused_with_lt<'a, T>(x: &'a u8) { - | ^ - | - = help: consider removing the parameter + | ^^^ help: consider removing the parameter -error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:24:19 +error: type parameters go unused in function definition: T, V + --> $DIR/extra_unused_type_parameters.rs:26:19 | -LL | fn unused_bounded(x: U) { - | ^^^^^^^^^^^ +LL | fn unused_bounded(x: U) { + | ^^^^^^^^^^^^ ^^^^^^^^^^^^ | - = help: consider removing the parameter - -error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:28:24 +help: consider removing the parameters | -LL | fn unused_where_clause(x: U) - | ^^ +LL - fn unused_bounded(x: U) { +LL + fn unused_bounded(x: U) { | - = help: consider removing the parameter -error: type parameters go unused in function definition - --> $DIR/extra_unused_type_parameters.rs:35:16 +error: type parameters go unused in function definition: A, D, E + --> $DIR/extra_unused_type_parameters.rs:30:16 | LL | fn some_unused, E>(b: B, c: C) { - | ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ + | ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider removing the parameters + | +LL - fn some_unused, E>(b: B, c: C) { +LL + fn some_unused(b: B, c: C) { | - = help: consider removing the parameters -error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:60:22 +error: type parameter `T` goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:55:22 | LL | fn unused_ty_impl(&self) { - | ^^^ - | - = help: consider removing the parameter + | ^^^ help: consider removing the parameter -error: type parameters go unused in function definition - --> $DIR/extra_unused_type_parameters.rs:82:17 +error: type parameters go unused in function definition: A, B + --> $DIR/extra_unused_type_parameters.rs:77:17 | LL | fn unused_opaque(dummy: impl Default) { - | ^^^^^^ - | - = help: consider removing the parameters + | ^^^^^^ help: consider removing the parameters -error: type parameter goes unused in function definition - --> $DIR/extra_unused_type_parameters.rs:95:58 +error: type parameter `U` goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:90:56 | LL | fn unused_with_priv_trait_bound() { - | ^ - | - = help: consider removing the parameter + | ^^^ help: consider removing the parameter -error: aborting due to 9 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/extra_unused_type_parameters_unfixable.rs b/tests/ui/extra_unused_type_parameters_unfixable.rs new file mode 100644 index 0000000000000..10b39aa8f2c5b --- /dev/null +++ b/tests/ui/extra_unused_type_parameters_unfixable.rs @@ -0,0 +1,24 @@ +#![warn(clippy::extra_unused_type_parameters)] + +fn unused_where_clause(x: U) +where + T: Default, +{ + unimplemented!(); +} + +fn unused_multi_where_clause(x: U) +where + T: Default, +{ + unimplemented!(); +} + +fn unused_all_where_clause() +where + T: Default, +{ + unimplemented!(); +} + +fn main() {} diff --git a/tests/ui/extra_unused_type_parameters_unfixable.stderr b/tests/ui/extra_unused_type_parameters_unfixable.stderr new file mode 100644 index 0000000000000..a9580cc894f35 --- /dev/null +++ b/tests/ui/extra_unused_type_parameters_unfixable.stderr @@ -0,0 +1,27 @@ +error: type parameter `T` goes unused in function definition + --> $DIR/extra_unused_type_parameters_unfixable.rs:3:24 + | +LL | fn unused_where_clause(x: U) + | ^ + | + = help: consider removing the parameter + = note: `-D clippy::extra-unused-type-parameters` implied by `-D warnings` + +error: type parameters go unused in function definition: T, V + --> $DIR/extra_unused_type_parameters_unfixable.rs:10:30 + | +LL | fn unused_multi_where_clause(x: U) + | ^ ^^^^^^^^^^ + | + = help: consider removing the parameters + +error: type parameters go unused in function definition: T, U, V + --> $DIR/extra_unused_type_parameters_unfixable.rs:17:28 + | +LL | fn unused_all_where_clause() + | ^ ^^^^^^^^^^ ^^^^^^^^^^ + | + = help: consider removing the parameters + +error: aborting due to 3 previous errors + From 8df896c076fd993bad58878ee8a6ed29d8e586ba Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 24 Mar 2023 14:04:35 +0100 Subject: [PATCH 16/86] Merge commit 'd5e2a7aca55ed49fc943b7a07a8eba05ab5a0079' into clippyup --- CHANGELOG.md | 9 +- CONTRIBUTING.md | 2 +- book/src/lint_configuration.md | 1 + clippy_lints/src/allow_attributes.rs | 71 +++ clippy_lints/src/booleans.rs | 25 +- clippy_lints/src/copies.rs | 70 ++- clippy_lints/src/declared_lints.rs | 3 + clippy_lints/src/default.rs | 9 +- clippy_lints/src/derive.rs | 8 +- clippy_lints/src/functions/must_use.rs | 5 +- clippy_lints/src/if_then_some_else_none.rs | 17 +- clippy_lints/src/let_with_type_underscore.rs | 2 +- clippy_lints/src/lib.rs | 9 +- clippy_lints/src/loops/same_item_push.rs | 32 +- clippy_lints/src/manual_async_fn.rs | 15 +- clippy_lints/src/manual_clamp.rs | 7 +- clippy_lints/src/manual_main_separator_str.rs | 74 +++ clippy_lints/src/matches/manual_unwrap_or.rs | 12 +- clippy_lints/src/matches/match_bool.rs | 20 +- clippy_lints/src/matches/match_ref_pats.rs | 28 +- .../src/matches/match_single_binding.rs | 52 +- .../src/matches/redundant_pattern_match.rs | 17 +- clippy_lints/src/matches/single_match.rs | 18 +- .../src/methods/bind_instead_of_map.rs | 11 +- clippy_lints/src/methods/clone_on_ref_ptr.rs | 8 +- clippy_lints/src/methods/or_fun_call.rs | 28 +- clippy_lints/src/misc.rs | 17 +- clippy_lints/src/mut_key.rs | 60 +-- clippy_lints/src/needless_bool.rs | 13 +- clippy_lints/src/no_mangle_with_rust_abi.rs | 28 +- clippy_lints/src/option_if_let_else.rs | 28 +- clippy_lints/src/redundant_async_block.rs | 39 +- .../src/semicolon_if_nothing_returned.rs | 13 +- .../src/significant_drop_tightening.rs | 38 +- clippy_lints/src/swap.rs | 7 +- clippy_lints/src/unit_types/let_unit_value.rs | 7 +- .../src/unnecessary_struct_initialization.rs | 84 ++++ clippy_lints/src/useless_conversion.rs | 14 +- clippy_lints/src/utils/conf.rs | 2 +- clippy_lints/src/wildcard_imports.rs | 8 +- clippy_utils/src/attrs.rs | 2 +- clippy_utils/src/eager_or_lazy.rs | 7 +- clippy_utils/src/msrvs.rs | 1 + clippy_utils/src/paths.rs | 1 + clippy_utils/src/qualify_min_const_fn.rs | 7 +- clippy_utils/src/source.rs | 42 +- clippy_utils/src/sugg.rs | 10 +- clippy_utils/src/ty.rs | 63 ++- clippy_utils/src/visitors.rs | 4 +- rust-toolchain | 2 +- tests/dogfood.rs | 4 +- tests/ui-internal/custom_ice_message.stderr | 1 + tests/ui-toml/ifs_same_cond/clippy.toml | 1 + tests/ui-toml/ifs_same_cond/ifs_same_cond.rs | 18 + .../ifs_same_cond/ifs_same_cond.stderr | 15 + tests/ui/allow_attributes.fixed | 25 + tests/ui/allow_attributes.rs | 25 + tests/ui/allow_attributes.stderr | 16 + tests/ui/almost_complete_range.fixed | 49 +- tests/ui/almost_complete_range.rs | 49 +- tests/ui/almost_complete_range.stderr | 93 ++-- tests/ui/as_conversions.rs | 13 +- tests/ui/as_conversions.stderr | 6 +- tests/ui/auxiliary/doc_unsafe_macros.rs | 16 - tests/ui/auxiliary/implicit_hasher_macros.rs | 6 - tests/ui/auxiliary/macro_rules.rs | 141 +----- tests/ui/auxiliary/macro_use_helper.rs | 2 +- tests/ui/auxiliary/proc_macro_with_span.rs | 32 -- tests/ui/auxiliary/proc_macros.rs | 474 ++++++++++++++++++ .../auxiliary/helper.rs | 2 +- tests/ui/crashes/ice-10148.rs | 6 +- tests/ui/default_numeric_fallback_f64.fixed | 17 +- tests/ui/default_numeric_fallback_f64.rs | 17 +- tests/ui/default_numeric_fallback_f64.stderr | 11 +- tests/ui/default_numeric_fallback_i32.fixed | 17 +- tests/ui/default_numeric_fallback_i32.rs | 17 +- tests/ui/default_numeric_fallback_i32.stderr | 11 +- tests/ui/default_trait_access.fixed | 6 +- tests/ui/default_trait_access.rs | 6 +- tests/ui/deref_addrof.fixed | 24 +- tests/ui/deref_addrof.rs | 24 +- tests/ui/deref_addrof.stderr | 38 +- tests/ui/deref_addrof_macro.rs | 15 +- tests/ui/doc_unsafe.rs | 12 +- tests/ui/empty_loop.rs | 15 +- tests/ui/equatable_if_let.fixed | 16 +- tests/ui/equatable_if_let.rs | 16 +- tests/ui/equatable_if_let.stderr | 32 +- tests/ui/field_reassign_with_default.rs | 36 +- tests/ui/field_reassign_with_default.stderr | 44 +- tests/ui/ifs_same_cond.rs | 26 + tests/ui/ifs_same_cond.stderr | 14 +- tests/ui/implicit_hasher.rs | 25 +- tests/ui/implicit_hasher.stderr | 55 +- .../ui/inconsistent_struct_constructor.fixed | 21 +- tests/ui/inconsistent_struct_constructor.rs | 21 +- .../ui/inconsistent_struct_constructor.stderr | 4 +- tests/ui/large_enum_variant.rs | 13 +- tests/ui/macro_use_imports.fixed | 4 +- tests/ui/macro_use_imports.rs | 2 +- tests/ui/macro_use_imports.stderr | 2 +- tests/ui/macro_use_imports_expect.rs | 2 +- tests/ui/manual_async_fn.fixed | 6 + tests/ui/manual_async_fn.rs | 12 + tests/ui/manual_async_fn.stderr | 47 +- tests/ui/manual_clamp.rs | 19 + tests/ui/manual_main_separator_str.fixed | 39 ++ tests/ui/manual_main_separator_str.rs | 39 ++ tests/ui/manual_main_separator_str.stderr | 28 ++ tests/ui/manual_rem_euclid.fixed | 24 +- tests/ui/manual_rem_euclid.rs | 24 +- tests/ui/manual_rem_euclid.stderr | 17 +- tests/ui/match_single_binding.fixed | 41 +- tests/ui/match_single_binding.rs | 55 +- tests/ui/match_single_binding.stderr | 111 +++- tests/ui/match_single_binding2.fixed | 4 +- tests/ui/match_single_binding2.stderr | 4 +- tests/ui/mem_replace_macro.rs | 23 +- tests/ui/mem_replace_macro.stderr | 11 +- .../ui/missing_const_for_fn/cant_be_const.rs | 6 +- tests/ui/missing_doc.rs | 6 +- tests/ui/missing_doc_impl.rs | 6 +- tests/ui/mistyped_literal_suffix.fixed | 6 +- tests/ui/mistyped_literal_suffix.rs | 6 +- tests/ui/multiple_unsafe_ops_per_block.rs | 11 +- tests/ui/multiple_unsafe_ops_per_block.stderr | 18 +- tests/ui/must_use_unit.fixed | 11 +- tests/ui/must_use_unit.rs | 11 +- tests/ui/mut_mut.rs | 11 +- tests/ui/mut_mut.stderr | 25 +- tests/ui/needless_late_init.fixed | 36 +- tests/ui/needless_late_init.rs | 36 +- tests/ui/needless_late_init.stderr | 32 +- tests/ui/needless_lifetimes.fixed | 37 +- tests/ui/needless_lifetimes.rs | 37 +- tests/ui/needless_lifetimes.stderr | 15 +- tests/ui/needless_update.rs | 2 +- tests/ui/no_effect.rs | 7 +- tests/ui/no_effect.stderr | 58 +-- tests/ui/no_mangle_with_rust_abi.fixed | 48 -- tests/ui/no_mangle_with_rust_abi.rs | 2 - tests/ui/no_mangle_with_rust_abi.stderr | 74 ++- tests/ui/nonminimal_bool.rs | 29 ++ tests/ui/option_env_unwrap.rs | 24 +- tests/ui/option_env_unwrap.stderr | 42 +- tests/ui/ptr_as_ptr.fixed | 16 +- tests/ui/ptr_as_ptr.rs | 16 +- tests/ui/ptr_as_ptr.stderr | 25 +- tests/ui/redundant_async_block.fixed | 47 ++ tests/ui/redundant_async_block.rs | 47 ++ tests/ui/redundant_async_block.stderr | 22 +- .../redundant_pattern_matching_result.fixed | 4 +- .../redundant_pattern_matching_result.stderr | 4 +- tests/ui/single_match_else.rs | 6 +- tests/ui/string_add.rs | 11 +- tests/ui/swap.fixed | 15 +- tests/ui/swap.rs | 15 +- tests/ui/swap.stderr | 34 +- tests/ui/toplevel_ref_arg.fixed | 24 +- tests/ui/toplevel_ref_arg.rs | 24 +- tests/ui/toplevel_ref_arg.stderr | 21 +- tests/ui/toplevel_ref_arg_non_rustfix.rs | 22 +- tests/ui/toplevel_ref_arg_non_rustfix.stderr | 7 +- tests/ui/try_err.fixed | 65 +-- tests/ui/try_err.rs | 65 +-- tests/ui/try_err.stderr | 32 +- tests/ui/uninit.rs | 23 +- tests/ui/uninit.stderr | 12 +- tests/ui/uninit_vec.rs | 27 + tests/ui/uninit_vec.stderr | 33 +- tests/ui/uninlined_format_args.fixed | 6 +- tests/ui/uninlined_format_args.rs | 6 +- tests/ui/unit_arg.rs | 6 +- tests/ui/unnecessary_lazy_eval.fixed | 6 +- tests/ui/unnecessary_lazy_eval.rs | 6 +- tests/ui/unnecessary_operation.fixed | 8 +- tests/ui/unnecessary_operation.rs | 8 +- tests/ui/unnecessary_operation.stderr | 38 +- .../unnecessary_struct_initialization.fixed | 73 +++ tests/ui/unnecessary_struct_initialization.rs | 77 +++ .../unnecessary_struct_initialization.stderr | 46 ++ tests/ui/unnecessary_unsafety_doc.rs | 12 +- tests/ui/unnecessary_unsafety_doc.stderr | 2 +- triagebot.toml | 1 + 184 files changed, 2992 insertions(+), 1627 deletions(-) create mode 100644 clippy_lints/src/allow_attributes.rs create mode 100644 clippy_lints/src/manual_main_separator_str.rs create mode 100644 clippy_lints/src/unnecessary_struct_initialization.rs create mode 100644 tests/ui-toml/ifs_same_cond/clippy.toml create mode 100644 tests/ui-toml/ifs_same_cond/ifs_same_cond.rs create mode 100644 tests/ui-toml/ifs_same_cond/ifs_same_cond.stderr create mode 100644 tests/ui/allow_attributes.fixed create mode 100644 tests/ui/allow_attributes.rs create mode 100644 tests/ui/allow_attributes.stderr delete mode 100644 tests/ui/auxiliary/doc_unsafe_macros.rs delete mode 100644 tests/ui/auxiliary/implicit_hasher_macros.rs delete mode 100644 tests/ui/auxiliary/proc_macro_with_span.rs create mode 100644 tests/ui/auxiliary/proc_macros.rs create mode 100644 tests/ui/manual_main_separator_str.fixed create mode 100644 tests/ui/manual_main_separator_str.rs create mode 100644 tests/ui/manual_main_separator_str.stderr delete mode 100644 tests/ui/no_mangle_with_rust_abi.fixed create mode 100644 tests/ui/unnecessary_struct_initialization.fixed create mode 100644 tests/ui/unnecessary_struct_initialization.rs create mode 100644 tests/ui/unnecessary_struct_initialization.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 0abe234fc8f7e..1323f973ccfdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -152,6 +152,8 @@ Current stable, released 2023-03-09 * `SYSROOT` and `--sysroot` can now be set at the same time [#10149](https://github.com/rust-lang/rust-clippy/pull/10149) +* Fix error when providing an `array-size-threshold` in `clippy.toml` + [#10423](https://github.com/rust-lang/rust-clippy/pull/10423) ## Rust 1.67 @@ -186,8 +188,6 @@ Released 2023-01-26 ### Moves and Deprecations -* Moved [`uninlined_format_args`] to `style` (Now warn-by-default) - [#9865](https://github.com/rust-lang/rust-clippy/pull/9865) * Moved [`needless_collect`] to `nursery` (Now allow-by-default) [#9705](https://github.com/rust-lang/rust-clippy/pull/9705) * Moved [`or_fun_call`] to `nursery` (Now allow-by-default) @@ -423,7 +423,7 @@ Released 2022-12-15 [#9490](https://github.com/rust-lang/rust-clippy/pull/9490) * [`almost_complete_letter_range`]: No longer lints in external macros [#9467](https://github.com/rust-lang/rust-clippy/pull/9467) -* [`drop_copy`]: No longer lints on idiomatic cases in match arms +* [`drop_copy`]: No longer lints on idiomatic cases in match arms [#9491](https://github.com/rust-lang/rust-clippy/pull/9491) * [`question_mark`]: No longer lints in const context [#9487](https://github.com/rust-lang/rust-clippy/pull/9487) @@ -4382,6 +4382,7 @@ Released 2018-09-13 [`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons [`alloc_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#alloc_instead_of_core +[`allow_attributes`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes [`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason [`almost_complete_letter_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_letter_range [`almost_complete_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range @@ -4661,6 +4662,7 @@ Released 2018-09-13 [`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed [`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check [`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else +[`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str [`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map [`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive @@ -4985,6 +4987,7 @@ Released 2018-09-13 [`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc [`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports [`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by +[`unnecessary_struct_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_struct_initialization [`unnecessary_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned [`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap [`unnecessary_wraps`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3158080d2b309..3df1328036948 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,7 +50,7 @@ a [developer guide] and is a good place to start your journey. All issues on Clippy are mentored, if you want help simply ask someone from the Clippy team directly by mentioning them in the issue or over on [Zulip]. All currently active team members can be found -[here](https://github.com/rust-lang/highfive/blob/master/highfive/configs/rust-lang/rust-clippy.json#L3) +[here](https://github.com/rust-lang/rust-clippy/blob/master/triagebot.toml#L18) Some issues are easier than others. The [`good-first-issue`] label can be used to find the easy issues. You can use `@rustbot claim` to assign the issue to yourself. diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 995dd2f04b1e6..9ed6627b74130 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -519,6 +519,7 @@ for the generic parameters for determining interior mutability **Default Value:** `["bytes::Bytes"]` (`Vec`) * [mutable_key_type](https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type) +* [ifs_same_cond](https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond) ### allow-mixed-uninlined-format-args diff --git a/clippy_lints/src/allow_attributes.rs b/clippy_lints/src/allow_attributes.rs new file mode 100644 index 0000000000000..15d46e954a9a5 --- /dev/null +++ b/clippy_lints/src/allow_attributes.rs @@ -0,0 +1,71 @@ +use ast::AttrStyle; +use clippy_utils::diagnostics::span_lint_and_sugg; +use rustc_ast as ast; +use rustc_errors::Applicability; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// Detects uses of the `#[allow]` attribute and suggests replacing it with + /// the `#[expect]` (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) + /// + /// The expect attribute is still unstable and requires the `lint_reasons` + /// on nightly. It can be enabled by adding `#![feature(lint_reasons)]` to + /// the crate root. + /// + /// This lint only warns outer attributes (`#[allow]`), as inner attributes + /// (`#![allow]`) are usually used to enable or disable lints on a global scale. + /// + /// ### Why is this bad? + /// + /// `#[expect]` attributes suppress the lint emission, but emit a warning, if + /// the expectation is unfulfilled. This can be useful to be notified when the + /// lint is no longer triggered. + /// + /// ### Example + /// ```rust,ignore + /// #[allow(unused_mut)] + /// fn foo() -> usize { + /// let mut a = Vec::new(); + /// a.len() + /// } + /// ``` + /// Use instead: + /// ```rust,ignore + /// #![feature(lint_reasons)] + /// #[expect(unused_mut)] + /// fn foo() -> usize { + /// let mut a = Vec::new(); + /// a.len() + /// } + /// ``` + #[clippy::version = "1.69.0"] + pub ALLOW_ATTRIBUTES, + restriction, + "`#[allow]` will not trigger if a warning isn't found. `#[expect]` triggers if there are no warnings." +} + +declare_lint_pass!(AllowAttribute => [ALLOW_ATTRIBUTES]); + +impl LateLintPass<'_> for AllowAttribute { + // Separate each crate's features. + fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) { + if_chain! { + if cx.tcx.features().lint_reasons; + if let AttrStyle::Outer = attr.style; + if let Some(ident) = attr.ident(); + if ident.name == rustc_span::symbol::sym::allow; + then { + span_lint_and_sugg( + cx, + ALLOW_ATTRIBUTES, + ident.span, + "#[allow] attribute found", + "replace it with", + "expect".into(), + Applicability::MachineApplicable, + ); + } + } + } +} diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index e8106beec3742..29fde9336c0b9 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -495,18 +495,19 @@ struct NotSimplificationVisitor<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind { - if let Some(suggestion) = simplify_not(self.cx, inner) { - span_lint_and_sugg( - self.cx, - NONMINIMAL_BOOL, - expr.span, - "this boolean expression can be simplified", - "try", - suggestion, - Applicability::MachineApplicable, - ); - } + if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind && + !inner.span.from_expansion() && + let Some(suggestion) = simplify_not(self.cx, inner) + { + span_lint_and_sugg( + self.cx, + NONMINIMAL_BOOL, + expr.span, + "this boolean expression can be simplified", + "try", + suggestion, + Applicability::MachineApplicable, + ); } walk_expr(self, expr); diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index f10c35cde52a1..970f50049935c 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -1,18 +1,20 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then}; use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt}; -use clippy_utils::ty::needs_ordered_drop; +use clippy_utils::ty::{is_interior_mut_ty, needs_ordered_drop}; use clippy_utils::visitors::for_each_expr; use clippy_utils::{ - capture_local_usage, eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, - is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq, + capture_local_usage, def_path_def_ids, eq_expr_value, find_binding_init, get_enclosing_block, hash_expr, hash_stmt, + if_sequence, is_else_clause, is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq, }; use core::iter; use core::ops::ControlFlow; use rustc_errors::Applicability; +use rustc_hir::def_id::DefIdSet; use rustc_hir::intravisit; use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_middle::query::Key; +use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::hygiene::walk_chain; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, Span, Symbol}; @@ -159,7 +161,21 @@ declare_clippy_lint! { "`if` statement with shared code in all blocks" } -declare_lint_pass!(CopyAndPaste => [ +pub struct CopyAndPaste { + ignore_interior_mutability: Vec, + ignored_ty_ids: DefIdSet, +} + +impl CopyAndPaste { + pub fn new(ignore_interior_mutability: Vec) -> Self { + Self { + ignore_interior_mutability, + ignored_ty_ids: DefIdSet::new(), + } + } +} + +impl_lint_pass!(CopyAndPaste => [ IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE, @@ -167,10 +183,18 @@ declare_lint_pass!(CopyAndPaste => [ ]); impl<'tcx> LateLintPass<'tcx> for CopyAndPaste { + fn check_crate(&mut self, cx: &LateContext<'tcx>) { + for ignored_ty in &self.ignore_interior_mutability { + let path: Vec<&str> = ignored_ty.split("::").collect(); + for id in def_path_def_ids(cx, path.as_slice()) { + self.ignored_ty_ids.insert(id); + } + } + } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) { let (conds, blocks) = if_sequence(expr); - lint_same_cond(cx, &conds); + lint_same_cond(cx, &conds, &self.ignored_ty_ids); lint_same_fns_in_if_cond(cx, &conds); let all_same = !is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && lint_if_same_then_else(cx, &conds, &blocks); @@ -547,9 +571,39 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo }) } +fn method_caller_is_mutable(cx: &LateContext<'_>, caller_expr: &Expr<'_>, ignored_ty_ids: &DefIdSet) -> bool { + let caller_ty = cx.typeck_results().expr_ty(caller_expr); + // Check if given type has inner mutability and was not set to ignored by the configuration + let is_inner_mut_ty = is_interior_mut_ty(cx, caller_ty) + && !matches!(caller_ty.ty_adt_id(), Some(adt_id) if ignored_ty_ids.contains(&adt_id)); + + is_inner_mut_ty + || caller_ty.is_mutable_ptr() + // `find_binding_init` will return the binding iff its not mutable + || path_to_local(caller_expr) + .and_then(|hid| find_binding_init(cx, hid)) + .is_none() +} + /// Implementation of `IFS_SAME_COND`. -fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { - for (i, j) in search_same(conds, |e| hash_expr(cx, e), |lhs, rhs| eq_expr_value(cx, lhs, rhs)) { +fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>], ignored_ty_ids: &DefIdSet) { + for (i, j) in search_same( + conds, + |e| hash_expr(cx, e), + |lhs, rhs| { + // Ignore eq_expr side effects iff one of the expressin kind is a method call + // and the caller is not a mutable, including inner mutable type. + if let ExprKind::MethodCall(_, caller, _, _) = lhs.kind { + if method_caller_is_mutable(cx, caller, ignored_ty_ids) { + false + } else { + SpanlessEq::new(cx).eq_expr(lhs, rhs) + } + } else { + eq_expr_value(cx, lhs, rhs) + } + }, + ) { span_lint_and_note( cx, IFS_SAME_COND, diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index cc6024b87cdae..8ca91301472e6 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -35,6 +35,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO, #[cfg(feature = "internal")] crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO, + crate::allow_attributes::ALLOW_ATTRIBUTES_INFO, crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO, crate::approx_const::APPROX_CONSTANT_INFO, crate::as_conversions::AS_CONVERSIONS_INFO, @@ -262,6 +263,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::manual_clamp::MANUAL_CLAMP_INFO, crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO, crate::manual_let_else::MANUAL_LET_ELSE_INFO, + crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO, crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO, crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO, crate::manual_retain::MANUAL_RETAIN_INFO, @@ -616,6 +618,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO, crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO, crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO, + crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO, crate::unnecessary_wraps::UNNECESSARY_WRAPS_INFO, crate::unnested_or_patterns::UNNESTED_OR_PATTERNS_INFO, crate::unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME_INFO, diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 080d44e6398c1..80c22742ba442 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; -use clippy_utils::source::snippet_with_macro_callsite; +use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{has_drop, is_copy}; use clippy_utils::{ any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro, match_def_path, paths, @@ -160,6 +160,8 @@ impl<'tcx> LateLintPass<'tcx> for Default { } }; + let init_ctxt = local.span.ctxt(); + // find all "later statement"'s where the fields of the binding set as // Default::default() get reassigned, unless the reassignment refers to the original binding let mut first_assign = None; @@ -169,7 +171,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { // find out if and which field was set by this `consecutive_statement` if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) { // interrupt and cancel lint if assign_rhs references the original binding - if contains_name(binding_name, assign_rhs, cx) { + if contains_name(binding_name, assign_rhs, cx) || init_ctxt != consecutive_statement.span.ctxt() { cancel_lint = true; break; } @@ -204,11 +206,12 @@ impl<'tcx> LateLintPass<'tcx> for Default { .iter() .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.name)); + let mut app = Applicability::Unspecified; let field_list = assigned_fields .into_iter() .map(|(field, rhs)| { // extract and store the assigned value for help message - let value_snippet = snippet_with_macro_callsite(cx, rhs.span, ".."); + let value_snippet = snippet_with_context(cx, rhs.span, init_ctxt, "..", &mut app).0; format!("{field}: {value_snippet}") }) .collect::>() diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 715348e869ef9..f425dd5fb70b5 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -24,8 +24,8 @@ use rustc_span::sym; declare_clippy_lint! { /// ### What it does - /// Checks for deriving `Hash` but implementing `PartialEq` - /// explicitly or vice versa. + /// Lints against manual `PartialEq` implementations for types with a derived `Hash` + /// implementation. /// /// ### Why is this bad? /// The implementation of these traits must agree (for @@ -54,8 +54,8 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for deriving `Ord` but implementing `PartialOrd` - /// explicitly or vice versa. + /// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord` + /// or `PartialOrd` implementation. /// /// ### Why is this bad? /// The implementation of these traits must agree (for diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 67877780c0e90..1e9e826631c37 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -50,10 +50,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp let attr = cx.tcx.get_attr(item.owner_id, sym::must_use); if let Some(attr) = attr { check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr); - } else if is_public - && !is_proc_macro(attrs) - && trait_ref_of_method(cx, item.owner_id.def_id).is_none() - { + } else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id.def_id).is_none() { check_must_use_candidate( cx, sig.decl, diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index 9cadaaa493e46..725bd3d54bc22 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -1,8 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_macro_callsite; +use clippy_utils::source::snippet_with_context; +use clippy_utils::sugg::Sugg; use clippy_utils::{contains_return, higher, is_else_clause, is_res_lang_ctor, path_res, peel_blocks}; +use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -72,21 +74,20 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { return; } + let ctxt = expr.span.ctxt(); + if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr) && let ExprKind::Block(then_block, _) = then.kind && let Some(then_expr) = then_block.expr && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind + && then_expr.span.ctxt() == ctxt && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome) && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone) && !stmts_contains_early_return(then_block.stmts) { - let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]"); - let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) { - format!("({cond_snip})") - } else { - cond_snip.into_owned() - }; - let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, ""); + let mut app = Applicability::Unspecified; + let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app).maybe_par().to_string(); + let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; let mut method_body = if then_block.stmts.is_empty() { arg_snip.into_owned() } else { diff --git a/clippy_lints/src/let_with_type_underscore.rs b/clippy_lints/src/let_with_type_underscore.rs index ba51973f2f9c4..c01e3882d529f 100644 --- a/clippy_lints/src/let_with_type_underscore.rs +++ b/clippy_lints/src/let_with_type_underscore.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use rustc_hir::*; +use rustc_hir::{Local, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 491732be2087f..c9210bf73f896 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -67,6 +67,7 @@ mod declared_lints; mod renamed_lints; // begin lints modules, do not remove this comment, it’s used in `update_lints` +mod allow_attributes; mod almost_complete_range; mod approx_const; mod as_conversions; @@ -179,6 +180,7 @@ mod manual_bits; mod manual_clamp; mod manual_is_ascii_check; mod manual_let_else; +mod manual_main_separator_str; mod manual_non_exhaustive; mod manual_rem_euclid; mod manual_retain; @@ -300,6 +302,7 @@ mod unit_types; mod unnamed_address; mod unnecessary_owned_empty_strings; mod unnecessary_self_imports; +mod unnecessary_struct_initialization; mod unnecessary_wraps; mod unnested_or_patterns; mod unsafe_removed_from_name; @@ -656,7 +659,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum)); store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons)); store.register_late_pass(|_| Box::new(regex::Regex)); - store.register_late_pass(|_| Box::new(copies::CopyAndPaste)); + let ignore_interior_mutability = conf.ignore_interior_mutability.clone(); + store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone()))); store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator)); store.register_late_pass(|_| Box::new(format::UselessFormat)); store.register_late_pass(|_| Box::new(swap::Swap)); @@ -933,6 +937,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage)); store.register_early_pass(|| Box::new(redundant_async_block::RedundantAsyncBlock)); store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped)); + store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute)); + store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv()))); + store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs index 540656a2cd991..9d9341559ac72 100644 --- a/clippy_lints/src/loops/same_item_push.rs +++ b/clippy_lints/src/loops/same_item_push.rs @@ -1,15 +1,17 @@ use super::SAME_ITEM_PUSH; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::path_to_local; -use clippy_utils::source::snippet_with_macro_callsite; +use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use if_chain::if_chain; use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind, Stmt, StmtKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; +use rustc_span::SyntaxContext; use std::iter::Iterator; /// Detects for loop pushing the same item into a Vec @@ -20,9 +22,10 @@ pub(super) fn check<'tcx>( body: &'tcx Expr<'_>, _: &'tcx Expr<'_>, ) { - fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) { - let vec_str = snippet_with_macro_callsite(cx, vec.span, ""); - let item_str = snippet_with_macro_callsite(cx, pushed_item.span, ""); + fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>, ctxt: SyntaxContext) { + let mut app = Applicability::Unspecified; + let vec_str = snippet_with_context(cx, vec.span, ctxt, "", &mut app).0; + let item_str = snippet_with_context(cx, pushed_item.span, ctxt, "", &mut app).0; span_lint_and_help( cx, @@ -43,7 +46,7 @@ pub(super) fn check<'tcx>( walk_expr(&mut same_item_push_visitor, body); if_chain! { if same_item_push_visitor.should_lint(); - if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push; + if let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push; let vec_ty = cx.typeck_results().expr_ty(vec); let ty = vec_ty.walk().nth(1).unwrap().expect_ty(); if cx @@ -69,11 +72,11 @@ pub(super) fn check<'tcx>( then { match init.kind { // immutable bindings that are initialized with literal - ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt), // immutable bindings that are initialized with constant ExprKind::Path(ref path) => { if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) { - emit_lint(cx, vec, pushed_item); + emit_lint(cx, vec, pushed_item, ctxt); } } _ => {}, @@ -82,11 +85,11 @@ pub(super) fn check<'tcx>( } }, // constant - Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item), + Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt), _ => {}, } }, - ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt), _ => {}, } } @@ -98,7 +101,7 @@ struct SameItemPushVisitor<'a, 'tcx> { non_deterministic_expr: bool, multiple_pushes: bool, // this field holds the last vec push operation visited, which should be the only push seen - vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>, + vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)>, cx: &'a LateContext<'tcx>, used_locals: FxHashSet, } @@ -118,7 +121,7 @@ impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> { if_chain! { if !self.non_deterministic_expr; if !self.multiple_pushes; - if let Some((vec, _)) = self.vec_push; + if let Some((vec, _, _)) = self.vec_push; if let Some(hir_id) = path_to_local(vec); then { !self.used_locals.contains(&hir_id) @@ -173,7 +176,10 @@ impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> { // Given some statement, determine if that statement is a push on a Vec. If it is, return // the Vec being pushed into and the item being pushed -fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { +fn get_vec_push<'tcx>( + cx: &LateContext<'tcx>, + stmt: &'tcx Stmt<'_>, +) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)> { if_chain! { // Extract method being called if let StmtKind::Semi(semi_stmt) = &stmt.kind; @@ -184,7 +190,7 @@ fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(& if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec); if path.ident.name.as_str() == "push"; then { - return Some((self_expr, pushed_item)) + return Some((self_expr, pushed_item, semi_stmt.span.ctxt())) } } None diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index f97c6bcb5d18c..577bc1d661dbd 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -5,7 +5,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, - ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind, + ImplItem, Item, ItemKind, LifetimeName, Node, Term, TraitRef, Ty, TyKind, TypeBindingKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -45,7 +45,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, span: Span, - _: LocalDefId, + def_id: LocalDefId, ) { if_chain! { if let Some(header) = kind.header(); @@ -59,6 +59,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { if let ExprKind::Block(block, _) = body.value.kind; if block.stmts.is_empty(); if let Some(closure_body) = desugared_async_block(cx, block); + if let Node::Item(Item {vis_span, ..}) | Node::ImplItem(ImplItem {vis_span, ..}) = + cx.tcx.hir().get_by_def_id(def_id); then { let header_span = span.with_hi(ret_ty.span.hi()); @@ -69,15 +71,22 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { "this function can be simplified using the `async fn` syntax", |diag| { if_chain! { + if let Some(vis_snip) = snippet_opt(cx, *vis_span); if let Some(header_snip) = snippet_opt(cx, header_span); if let Some(ret_pos) = position_before_rarrow(&header_snip); if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output); then { + let header_snip = if vis_snip.is_empty() { + format!("async {}", &header_snip[..ret_pos]) + } else { + format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos]) + }; + let help = format!("make the function `async` and {ret_sugg}"); diag.span_suggestion( header_span, help, - format!("async {}{ret_snip}", &header_snip[..ret_pos]), + format!("{header_snip}{ret_snip}"), Applicability::MachineApplicable ); diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs index f239736d38a4c..440362b96b470 100644 --- a/clippy_lints/src/manual_clamp.rs +++ b/clippy_lints/src/manual_clamp.rs @@ -6,7 +6,8 @@ use clippy_utils::ty::implements_trait; use clippy_utils::visitors::is_const_evaluatable; use clippy_utils::MaybePath; use clippy_utils::{ - eq_expr_value, is_diag_trait_item, is_trait_method, path_res, path_to_local_id, peel_blocks, peel_blocks_with_stmt, + eq_expr_value, in_constant, is_diag_trait_item, is_trait_method, path_res, path_to_local_id, peel_blocks, + peel_blocks_with_stmt, }; use itertools::Itertools; use rustc_errors::Applicability; @@ -117,7 +118,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualClamp { if !self.msrv.meets(msrvs::CLAMP) { return; } - if !expr.span.from_expansion() { + if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) { let suggestion = is_if_elseif_else_pattern(cx, expr) .or_else(|| is_max_min_pattern(cx, expr)) .or_else(|| is_call_max_min_pattern(cx, expr)) @@ -130,7 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualClamp { } fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) { - if !self.msrv.meets(msrvs::CLAMP) { + if !self.msrv.meets(msrvs::CLAMP) || in_constant(cx, block.hir_id) { return; } for suggestion in is_two_if_pattern(cx, block) { diff --git a/clippy_lints/src/manual_main_separator_str.rs b/clippy_lints/src/manual_main_separator_str.rs new file mode 100644 index 0000000000000..c292bbe4e9344 --- /dev/null +++ b/clippy_lints/src/manual_main_separator_str.rs @@ -0,0 +1,74 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::{is_trait_method, match_def_path, paths, peel_hir_expr_refs}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Expr, ExprKind, Mutability, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Checks for references on `std::path::MAIN_SEPARATOR.to_string()` used + /// to build a `&str`. + /// + /// ### Why is this bad? + /// There exists a `std::path::MAIN_SEPARATOR_STR` which does not require + /// an extra memory allocation. + /// + /// ### Example + /// ```rust + /// let s: &str = &std::path::MAIN_SEPARATOR.to_string(); + /// ``` + /// Use instead: + /// ```rust + /// let s: &str = std::path::MAIN_SEPARATOR_STR; + /// ``` + #[clippy::version = "1.70.0"] + pub MANUAL_MAIN_SEPARATOR_STR, + complexity, + "`&std::path::MAIN_SEPARATOR.to_string()` can be replaced by `std::path::MAIN_SEPARATOR_STR`" +} + +pub struct ManualMainSeparatorStr { + msrv: Msrv, +} + +impl ManualMainSeparatorStr { + #[must_use] + pub fn new(msrv: Msrv) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(ManualMainSeparatorStr => [MANUAL_MAIN_SEPARATOR_STR]); + +impl LateLintPass<'_> for ManualMainSeparatorStr { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if self.msrv.meets(msrvs::PATH_MAIN_SEPARATOR_STR) && + let (target, _) = peel_hir_expr_refs(expr) && + is_trait_method(cx, target, sym::ToString) && + let ExprKind::MethodCall(path, receiver, &[], _) = target.kind && + path.ident.name == sym::to_string && + let ExprKind::Path(QPath::Resolved(None, path)) = receiver.kind && + let Res::Def(DefKind::Const, receiver_def_id) = path.res && + match_def_path(cx, receiver_def_id, &paths::PATH_MAIN_SEPARATOR) && + let ty::Ref(_, ty, Mutability::Not) = cx.typeck_results().expr_ty_adjusted(expr).kind() && + ty.is_str() + { + span_lint_and_sugg( + cx, + MANUAL_MAIN_SEPARATOR_STR, + expr.span, + "taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String`", + "replace with", + "std::path::MAIN_SEPARATOR_STR".to_owned(), + Applicability::MachineApplicable, + ); + } + } + + extract_msrv_attr!(LateContext); +} diff --git a/clippy_lints/src/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs index 6447899f2b94c..b94501bf0ad38 100644 --- a/clippy_lints/src/matches/manual_unwrap_or.rs +++ b/clippy_lints/src/matches/manual_unwrap_or.rs @@ -32,14 +32,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, scrutinee: let reindented_or_body = reindent_multiline(or_body_snippet.into(), true, Some(indent)); - let suggestion = if scrutinee.span.from_expansion() { - // we don't want parentheses around macro, e.g. `(some_macro!()).unwrap_or(0)` - sugg::Sugg::hir_with_macro_callsite(cx, scrutinee, "..") - } - else { - sugg::Sugg::hir(cx, scrutinee, "..").maybe_par() - }; - + let mut app = Applicability::MachineApplicable; + let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_par(); span_lint_and_sugg( cx, MANUAL_UNWRAP_OR, expr.span, @@ -48,7 +42,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, scrutinee: format!( "{suggestion}.unwrap_or({reindented_or_body})", ), - Applicability::MachineApplicable, + app, ); } } diff --git a/clippy_lints/src/matches/match_bool.rs b/clippy_lints/src/matches/match_bool.rs index 1c216e135704c..df1e585f10b21 100644 --- a/clippy_lints/src/matches/match_bool.rs +++ b/clippy_lints/src/matches/match_bool.rs @@ -10,9 +10,9 @@ use rustc_middle::ty; use super::MATCH_BOOL; -pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { +pub(crate) fn check(cx: &LateContext<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { // Type of expression is `bool`. - if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool { + if *cx.typeck_results().expr_ty(scrutinee).kind() == ty::Bool { span_lint_and_then( cx, MATCH_BOOL, @@ -36,24 +36,26 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: }; if let Some((true_expr, false_expr)) = exprs { + let mut app = Applicability::HasPlaceholders; + let ctxt = expr.span.ctxt(); let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { (false, false) => Some(format!( "if {} {} else {}", - snippet(cx, ex.span, "b"), - expr_block(cx, true_expr, None, "..", Some(expr.span)), - expr_block(cx, false_expr, None, "..", Some(expr.span)) + snippet(cx, scrutinee.span, "b"), + expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app), + expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app) )), (false, true) => Some(format!( "if {} {}", - snippet(cx, ex.span, "b"), - expr_block(cx, true_expr, None, "..", Some(expr.span)) + snippet(cx, scrutinee.span, "b"), + expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app) )), (true, false) => { - let test = Sugg::hir(cx, ex, ".."); + let test = Sugg::hir(cx, scrutinee, ".."); Some(format!( "if {} {}", !test, - expr_block(cx, false_expr, None, "..", Some(expr.span)) + expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app) )) }, (true, true) => None, diff --git a/clippy_lints/src/matches/match_ref_pats.rs b/clippy_lints/src/matches/match_ref_pats.rs index 80f964ba1b72c..aba4c85c59e28 100644 --- a/clippy_lints/src/matches/match_ref_pats.rs +++ b/clippy_lints/src/matches/match_ref_pats.rs @@ -1,13 +1,14 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; -use clippy_utils::source::snippet; +use clippy_utils::source::{snippet, walk_span_to_context}; use clippy_utils::sugg::Sugg; use core::iter::once; +use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; use rustc_lint::LateContext; use super::MATCH_REF_PATS; -pub(crate) fn check<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>) +pub(crate) fn check<'a, 'b, I>(cx: &LateContext<'_>, scrutinee: &Expr<'_>, pats: I, expr: &Expr<'_>) where 'b: 'a, I: Clone + Iterator>, @@ -17,13 +18,28 @@ where } let (first_sugg, msg, title); - let span = ex.span.source_callsite(); - if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind { - first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string())); + let ctxt = expr.span.ctxt(); + let mut app = Applicability::Unspecified; + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = scrutinee.kind { + if scrutinee.span.ctxt() != ctxt { + return; + } + first_sugg = once(( + scrutinee.span, + Sugg::hir_with_context(cx, inner, ctxt, "..", &mut app).to_string(), + )); msg = "try"; title = "you don't need to add `&` to both the expression and the patterns"; } else { - first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string())); + let Some(span) = walk_span_to_context(scrutinee.span, ctxt) else { + return; + }; + first_sugg = once(( + span, + Sugg::hir_with_context(cx, scrutinee, ctxt, "..", &mut app) + .deref() + .to_string(), + )); msg = "instead of prefixing all patterns with `&`, you can dereference the expression"; title = "you don't need to add `&` to all patterns"; } diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index 065a5c72621cd..89da7a55cbd5f 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::HirNode; -use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability}; -use clippy_utils::sugg::Sugg; +use clippy_utils::source::{indent_of, snippet, snippet_block_with_context, snippet_with_applicability}; use clippy_utils::{get_parent_expr, is_refutable, peel_blocks}; use rustc_errors::Applicability; -use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind}; +use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind, StmtKind}; use rustc_lint::LateContext; use rustc_span::Span; @@ -24,21 +23,30 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e let matched_vars = ex.span; let bind_names = arms[0].pat.span; let match_body = peel_blocks(arms[0].body); - let mut snippet_body = if match_body.span.from_expansion() { - Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string() - } else { - snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string() - }; + let mut app = Applicability::MaybeIncorrect; + let mut snippet_body = snippet_block_with_context( + cx, + match_body.span, + arms[0].span.ctxt(), + "..", + Some(expr.span), + &mut app, + ) + .0 + .to_string(); // Do we need to add ';' to suggestion ? - if let ExprKind::Block(block, _) = match_body.kind { - // macro + expr_ty(body) == () - if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() { - snippet_body.push(';'); + if let Node::Stmt(stmt) = cx.tcx.hir().get_parent(expr.hir_id) + && let StmtKind::Expr(_) = stmt.kind + && match match_body.kind { + // We don't need to add a ; to blocks, unless that block is from a macro expansion + ExprKind::Block(block, _) => block.span.from_expansion(), + _ => true, } + { + snippet_body.push(';'); } - let mut applicability = Applicability::MaybeIncorrect; match arms[0].pat.kind { PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => { let (target_span, sugg) = match opt_parent_assign_span(cx, ex) { @@ -48,7 +56,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e (ex, expr), (bind_names, matched_vars), &snippet_body, - &mut applicability, + &mut app, Some(span), true, ); @@ -60,7 +68,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e "this assignment could be simplified", "consider removing the `match` expression", sugg, - applicability, + app, ); return; @@ -69,10 +77,10 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e span, format!( "let {} = {};\n{}let {} = {snippet_body};", - snippet_with_applicability(cx, bind_names, "..", &mut applicability), - snippet_with_applicability(cx, matched_vars, "..", &mut applicability), + snippet_with_applicability(cx, bind_names, "..", &mut app), + snippet_with_applicability(cx, matched_vars, "..", &mut app), " ".repeat(indent_of(cx, expr.span).unwrap_or(0)), - snippet_with_applicability(cx, pat_span, "..", &mut applicability) + snippet_with_applicability(cx, pat_span, "..", &mut app) ), ), None => { @@ -81,7 +89,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e (ex, expr), (bind_names, matched_vars), &snippet_body, - &mut applicability, + &mut app, None, true, ); @@ -96,7 +104,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e "this match could be written as a `let` statement", "consider using a `let` statement", sugg, - applicability, + app, ); }, PatKind::Wild => { @@ -106,7 +114,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e (ex, expr), (bind_names, matched_vars), &snippet_body, - &mut applicability, + &mut app, None, false, ); @@ -118,7 +126,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e "this match could be replaced by its scrutinee and body", "consider using the scrutinee and body instead", sugg, - applicability, + app, ); } else { span_lint_and_sugg( diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index df0ea7f5b863b..7b609ff3df8fb 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -1,6 +1,6 @@ use super::REDUNDANT_PATTERN_MATCHING; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet; +use clippy_utils::source::{snippet, walk_span_to_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop}; use clippy_utils::visitors::any_temporaries_need_ordered_drop; @@ -150,22 +150,25 @@ fn find_sugg_for_if_let<'tcx>( // if/while let ... = ... { ... } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ let expr_span = expr.span; + let ctxt = expr.span.ctxt(); // if/while let ... = ... { ... } - // ^^^ - let op_span = result_expr.span.source_callsite(); + // ^^^ + let Some(res_span) = walk_span_to_context(result_expr.span.source_callsite(), ctxt) else { + return; + }; // if/while let ... = ... { ... } - // ^^^^^^^^^^^^^^^^^^^ - let span = expr_span.until(op_span.shrink_to_hi()); + // ^^^^^^^^^^^^^^^^^^^^^^ + let span = expr_span.until(res_span.shrink_to_hi()); - let app = if needs_drop { + let mut app = if needs_drop { Applicability::MaybeIncorrect } else { Applicability::MachineApplicable }; - let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_") + let sugg = Sugg::hir_with_context(cx, result_expr, ctxt, "_", &mut app) .maybe_par() .to_string(); diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 19b49c44d5704..ad47c13896c58 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -67,8 +67,10 @@ fn report_single_pattern( els: Option<&Expr<'_>>, ) { let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH }; + let ctxt = expr.span.ctxt(); + let mut app = Applicability::HasPlaceholders; let els_str = els.map_or(String::new(), |els| { - format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) + format!(" else {}", expr_block(cx, els, ctxt, "..", Some(expr.span), &mut app)) }); let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat); @@ -103,7 +105,7 @@ fn report_single_pattern( // PartialEq for different reference counts may not exist. "&".repeat(ref_count_diff), snippet(cx, arms[0].pat.span, ".."), - expr_block(cx, arms[0].body, None, "..", Some(expr.span)), + expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app), ); (msg, sugg) } else { @@ -112,21 +114,13 @@ fn report_single_pattern( "if let {} = {} {}{els_str}", snippet(cx, arms[0].pat.span, ".."), snippet(cx, ex.span, ".."), - expr_block(cx, arms[0].body, None, "..", Some(expr.span)), + expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app), ); (msg, sugg) } }; - span_lint_and_sugg( - cx, - lint, - expr.span, - msg, - "try this", - sugg, - Applicability::HasPlaceholders, - ); + span_lint_and_sugg(cx, lint, expr.span, msg, "try this", sugg, app); } fn check_opt_like<'a>( diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index 8e1130cf8dfa4..00853348840ed 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -1,6 +1,6 @@ use super::{contains_return, BIND_INSTEAD_OF_MAP}; use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::{snippet, snippet_with_macro_callsite}; +use clippy_utils::source::{snippet, snippet_with_context}; use clippy_utils::{peel_blocks, visitors::find_all_ret_expressions}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -76,11 +76,8 @@ pub(crate) trait BindInsteadOfMap { if !contains_return(inner_expr); if let Some(msg) = Self::lint_msg(cx); then { - let some_inner_snip = if inner_expr.span.from_expansion() { - snippet_with_macro_callsite(cx, inner_expr.span, "_") - } else { - snippet(cx, inner_expr.span, "_") - }; + let mut app = Applicability::MachineApplicable; + let some_inner_snip = snippet_with_context(cx, inner_expr.span, closure_expr.span.ctxt(), "_", &mut app).0; let closure_args_snip = snippet(cx, closure_args_span, ".."); let option_snip = snippet(cx, recv.span, ".."); @@ -92,7 +89,7 @@ pub(crate) trait BindInsteadOfMap { &msg, "try this", note, - Applicability::MachineApplicable, + app, ); true } else { diff --git a/clippy_lints/src/methods/clone_on_ref_ptr.rs b/clippy_lints/src/methods/clone_on_ref_ptr.rs index 355f53532e268..5e8ad0861f33d 100644 --- a/clippy_lints/src/methods/clone_on_ref_ptr.rs +++ b/clippy_lints/src/methods/clone_on_ref_ptr.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::paths; -use clippy_utils::source::snippet_with_macro_callsite; +use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{is_type_diagnostic_item, match_type}; use rustc_errors::Applicability; use rustc_hir as hir; @@ -33,7 +33,9 @@ pub(super) fn check( return; }; - let snippet = snippet_with_macro_callsite(cx, receiver.span, ".."); + // Sometimes unnecessary ::<_> after Rc/Arc/Weak + let mut app = Applicability::Unspecified; + let snippet = snippet_with_context(cx, receiver.span, expr.span.ctxt(), "..", &mut app).0; span_lint_and_sugg( cx, @@ -42,7 +44,7 @@ pub(super) fn check( "using `.clone()` on a ref-counted pointer", "try this", format!("{caller_type}::<{}>::clone(&{snippet})", subst.type_at(0)), - Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak + app, ); } } diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 4460f38fcc18f..7ce28ea93e01a 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_lazy_eval; -use clippy_utils::source::{snippet, snippet_with_macro_callsite}; +use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{contains_return, is_trait_item, last_path_segment}; use if_chain::if_chain; @@ -9,7 +9,6 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::source_map::Span; use rustc_span::symbol::{kw, sym, Symbol}; -use std::borrow::Cow; use super::OR_FUN_CALL; @@ -111,37 +110,24 @@ pub(super) fn check<'tcx>( if poss.contains(&name); then { + let ctxt = span.ctxt(); + let mut app = Applicability::HasPlaceholders; let sugg = { let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { (false, Some(fun_span)) => (fun_span, false), _ => (arg.span, true), }; - let format_span = |span: Span| { - let not_macro_argument_snippet = snippet_with_macro_callsite(cx, span, ".."); - let snip = if not_macro_argument_snippet == "vec![]" { - let macro_expanded_snipped = snippet(cx, snippet_span, ".."); - match macro_expanded_snipped.strip_prefix("$crate::vec::") { - Some(stripped) => Cow::Owned(stripped.to_owned()), - None => macro_expanded_snipped, - } - } else { - not_macro_argument_snippet - }; - - snip.to_string() - }; - - let snip = format_span(snippet_span); + let snip = snippet_with_context(cx, snippet_span, ctxt, "..", &mut app).0; let snip = if use_lambda { let l_arg = if fn_has_arguments { "_" } else { "" }; format!("|{l_arg}| {snip}") } else { - snip + snip.into_owned() }; if let Some(f) = second_arg { - let f = format_span(f.span); + let f = snippet_with_context(cx, f.span, ctxt, "..", &mut app).0; format!("{snip}, {f}") } else { snip @@ -155,7 +141,7 @@ pub(super) fn check<'tcx>( &format!("use of `{name}` followed by a function call"), "try this", format!("{name}_{suffix}({sugg})"), - Applicability::HasPlaceholders, + app, ); } } diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 0705029a613bb..3752b9a946f8e 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then}; -use clippy_utils::source::{snippet, snippet_opt}; +use clippy_utils::source::{snippet, snippet_opt, snippet_with_context}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; @@ -181,20 +181,17 @@ impl<'tcx> LateLintPass<'tcx> for LintPass { if let PatKind::Binding(BindingAnnotation(ByRef::Yes, mutabl), .., name, None) = local.pat.kind; if let Some(init) = local.init; then { - // use the macro callsite when the init span (but not the whole local span) - // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];` - let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() { - Sugg::hir_with_macro_callsite(cx, init, "..") - } else { - Sugg::hir(cx, init, "..") - }; + let ctxt = local.span.ctxt(); + let mut app = Applicability::MachineApplicable; + let sugg_init = Sugg::hir_with_context(cx, init, ctxt, "..", &mut app); let (mutopt, initref) = if mutabl == Mutability::Mut { ("mut ", sugg_init.mut_addr()) } else { ("", sugg_init.addr()) }; let tyopt = if let Some(ty) = local.ty { - format!(": &{mutopt}{ty}", ty=snippet(cx, ty.span, "..")) + let ty_snip = snippet_with_context(cx, ty.span, ctxt, "_", &mut app).0; + format!(": &{mutopt}{ty_snip}") } else { String::new() }; @@ -212,7 +209,7 @@ impl<'tcx> LateLintPass<'tcx> for LintPass { "let {name}{tyopt} = {initref};", name=snippet(cx, name.span, ".."), ), - Applicability::MachineApplicable, + app, ); } ); diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 8aa814b740536..309f67521a3b4 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -1,10 +1,11 @@ use clippy_utils::diagnostics::span_lint; +use clippy_utils::ty::is_interior_mut_ty; use clippy_utils::{def_path_def_ids, trait_ref_of_method}; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TypeVisitableExt; -use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty}; +use rustc_middle::query::Key; +use rustc_middle::ty::{Adt, Ty}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; @@ -153,53 +154,18 @@ impl MutableKeyType { let is_keyed_type = [sym::HashMap, sym::BTreeMap, sym::HashSet, sym::BTreeSet] .iter() .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did())); - if is_keyed_type && self.is_interior_mutable_type(cx, substs.type_at(0)) { - span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type"); + if !is_keyed_type { + return; } - } - } - /// Determines if a type contains interior mutability which would affect its implementation of - /// [`Hash`] or [`Ord`]. - fn is_interior_mutable_type<'tcx>(&self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - match *ty.kind() { - Ref(_, inner_ty, mutbl) => mutbl == hir::Mutability::Mut || self.is_interior_mutable_type(cx, inner_ty), - Slice(inner_ty) => self.is_interior_mutable_type(cx, inner_ty), - Array(inner_ty, size) => { - size.try_eval_target_usize(cx.tcx, cx.param_env) - .map_or(true, |u| u != 0) - && self.is_interior_mutable_type(cx, inner_ty) - }, - Tuple(fields) => fields.iter().any(|ty| self.is_interior_mutable_type(cx, ty)), - Adt(def, substs) => { - // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to - // that of their type parameters. Note: we don't include `HashSet` and `HashMap` - // because they have no impl for `Hash` or `Ord`. - let def_id = def.did(); - let is_std_collection = [ - sym::Option, - sym::Result, - sym::LinkedList, - sym::Vec, - sym::VecDeque, - sym::BTreeMap, - sym::BTreeSet, - sym::Rc, - sym::Arc, - ] - .iter() - .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def_id)); - let is_box = Some(def_id) == cx.tcx.lang_items().owned_box(); - if is_std_collection || is_box || self.ignore_mut_def_ids.contains(&def_id) { - // The type is mutable if any of its type parameters are - substs.types().any(|ty| self.is_interior_mutable_type(cx, ty)) - } else { - !ty.has_escaping_bound_vars() - && cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() - && !ty.is_freeze(cx.tcx, cx.param_env) - } - }, - _ => false, + let subst_ty = substs.type_at(0); + // Determines if a type contains interior mutability which would affect its implementation of + // [`Hash`] or [`Ord`]. + if is_interior_mut_ty(cx, subst_ty) + && !matches!(subst_ty.ty_adt_id(), Some(adt_id) if self.ignore_mut_def_ids.contains(&adt_id)) + { + span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type"); + } } } } diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index a4eec95b37159..c87059bf61de3 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -340,18 +340,11 @@ fn suggest_bool_comparison<'a, 'tcx>( cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, expr: &Expr<'_>, - mut applicability: Applicability, + mut app: Applicability, message: &str, conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, ) { - let hint = if expr.span.from_expansion() { - if applicability != Applicability::Unspecified { - applicability = Applicability::MaybeIncorrect; - } - Sugg::hir_with_macro_callsite(cx, expr, "..") - } else { - Sugg::hir_with_applicability(cx, expr, "..", &mut applicability) - }; + let hint = Sugg::hir_with_context(cx, expr, e.span.ctxt(), "..", &mut app); span_lint_and_sugg( cx, BOOL_COMPARISON, @@ -359,7 +352,7 @@ fn suggest_bool_comparison<'a, 'tcx>( message, "try simplifying it as shown", conv_hint(hint).to_string(), - applicability, + app, ); } diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs index bc64ccb295cb6..8fd9ae351a0d7 100644 --- a/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -1,9 +1,10 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{BytePos, Pos}; use rustc_target::spec::abi::Abi; declare_clippy_lint! { @@ -38,25 +39,28 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { if let ItemKind::Fn(fn_sig, _, _) = &item.kind { let attrs = cx.tcx.hir().attrs(item.hir_id()); - let mut applicability = Applicability::MachineApplicable; - let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut applicability); + let mut app = Applicability::MaybeIncorrect; + let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut app); for attr in attrs { if let Some(ident) = attr.ident() && ident.name == rustc_span::sym::no_mangle && fn_sig.header.abi == Abi::Rust - && !snippet.contains("extern") { + && let Some((fn_attrs, _)) = snippet.split_once("fn") + && !fn_attrs.contains("extern") + { + let sugg_span = fn_sig.span + .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len())) + .shrink_to_lo(); - let suggestion = snippet.split_once("fn") - .map_or(String::new(), |(first, second)| format!(r#"{first}extern "C" fn{second}"#)); - - span_lint_and_sugg( + span_lint_and_then( cx, NO_MANGLE_WITH_RUST_ABI, fn_sig.span, - "attribute #[no_mangle] set on a Rust ABI function", - "try", - suggestion, - applicability + "`#[no_mangle]` set on a function with the default (`Rust`) ABI", + |diag| { + diag.span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", app) + .span_suggestion(sugg_span, "or explicitly set the default", "extern \"Rust\" ", app); + }, ); } } diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index c5ea09590d3df..bbbcda069c551 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -12,6 +12,7 @@ use rustc_hir::{ }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::SyntaxContext; declare_clippy_lint! { /// ### What it does @@ -95,10 +96,10 @@ struct OptionOccurrence { none_expr: String, } -fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: bool, as_mut: bool) -> String { +fn format_option_in_sugg(cond_sugg: Sugg<'_>, as_ref: bool, as_mut: bool) -> String { format!( "{}{}", - Sugg::hir_with_macro_callsite(cx, cond_expr, "..").maybe_par(), + cond_sugg.maybe_par(), if as_mut { ".as_mut()" } else if as_ref { @@ -111,6 +112,7 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo fn try_get_option_occurrence<'tcx>( cx: &LateContext<'tcx>, + ctxt: SyntaxContext, pat: &Pat<'tcx>, expr: &Expr<'_>, if_then: &'tcx Expr<'_>, @@ -160,11 +162,23 @@ fn try_get_option_occurrence<'tcx>( } } + let mut app = Applicability::Unspecified; return Some(OptionOccurrence { - option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut), + option: format_option_in_sugg( + Sugg::hir_with_context(cx, cond_expr, ctxt, "..", &mut app), + as_ref, + as_mut, + ), method_sugg: method_sugg.to_string(), - some_expr: format!("|{capture_mut}{capture_name}| {}", Sugg::hir_with_macro_callsite(cx, some_body, "..")), - none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir_with_macro_callsite(cx, none_body, "..")), + some_expr: format!( + "|{capture_mut}{capture_name}| {}", + Sugg::hir_with_context(cx, some_body, ctxt, "..", &mut app), + ), + none_expr: format!( + "{}{}", + if method_sugg == "map_or" { "" } else { "|| " }, + Sugg::hir_with_context(cx, none_body, ctxt, "..", &mut app), + ), }); } } @@ -194,7 +208,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> }) = higher::IfLet::hir(cx, expr) { if !is_else_clause(cx.tcx, expr) { - return try_get_option_occurrence(cx, let_pat, let_expr, if_then, if_else); + return try_get_option_occurrence(cx, expr.span.ctxt(), let_pat, let_expr, if_then, if_else); } } None @@ -203,7 +217,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> fn detect_option_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option { if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { if let Some((let_pat, if_then, if_else)) = try_convert_match(cx, arms) { - return try_get_option_occurrence(cx, let_pat, ex, if_then, if_else); + return try_get_option_occurrence(cx, expr.span.ctxt(), let_pat, ex, if_then, if_else); } } None diff --git a/clippy_lints/src/redundant_async_block.rs b/clippy_lints/src/redundant_async_block.rs index 27ad430863743..2d30e77d55ded 100644 --- a/clippy_lints/src/redundant_async_block.rs +++ b/clippy_lints/src/redundant_async_block.rs @@ -1,5 +1,5 @@ use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet}; -use rustc_ast::ast::*; +use rustc_ast::ast::{Expr, ExprKind, Stmt, StmtKind}; use rustc_ast::visit::Visitor as AstVisitor; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; @@ -32,7 +32,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.69.0"] pub REDUNDANT_ASYNC_BLOCK, - complexity, + nursery, "`async { future.await }` can be replaced by `future`" } declare_lint_pass!(RedundantAsyncBlock => [REDUNDANT_ASYNC_BLOCK]); @@ -48,6 +48,11 @@ impl EarlyLintPass for RedundantAsyncBlock { !future.span.from_expansion() && !await_in_expr(future) { + if captures_value(last) { + // If the async block captures variables then there is no equivalence. + return; + } + span_lint_and_sugg( cx, REDUNDANT_ASYNC_BLOCK, @@ -82,3 +87,33 @@ impl<'ast> AstVisitor<'ast> for AwaitDetector { } } } + +/// Check whether an expression may have captured a local variable. +/// This is done by looking for paths with only one segment, except as +/// a prefix of `.await` since this would be captured by value. +/// +/// This function will sometimes return `true` even tough there are no +/// captures happening: at the AST level, it is impossible to +/// dinstinguish a function call from a call to a closure which comes +/// from the local environment. +fn captures_value(expr: &Expr) -> bool { + let mut detector = CaptureDetector::default(); + detector.visit_expr(expr); + detector.capture_found +} + +#[derive(Default)] +struct CaptureDetector { + capture_found: bool, +} + +impl<'ast> AstVisitor<'ast> for CaptureDetector { + fn visit_expr(&mut self, ex: &'ast Expr) { + match (&ex.kind, self.capture_found) { + (ExprKind::Await(fut), _) if matches!(fut.kind, ExprKind::Path(..)) => (), + (ExprKind::Path(_, path), _) if path.segments.len() == 1 => self.capture_found = true, + (_, false) => rustc_ast::visit::walk_expr(self, ex), + _ => (), + } + } +} diff --git a/clippy_lints/src/semicolon_if_nothing_returned.rs b/clippy_lints/src/semicolon_if_nothing_returned.rs index 66638eed99837..355f907e2577b 100644 --- a/clippy_lints/src/semicolon_if_nothing_returned.rs +++ b/clippy_lints/src/semicolon_if_nothing_returned.rs @@ -1,7 +1,6 @@ use crate::rustc_lint::LintContext; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_macro_callsite; -use clippy_utils::sugg; +use clippy_utils::source::snippet_with_context; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Block, ExprKind}; @@ -44,7 +43,8 @@ impl<'tcx> LateLintPass<'tcx> for SemicolonIfNothingReturned { if let Some(expr) = block.expr; let t_expr = cx.typeck_results().expr_ty(expr); if t_expr.is_unit(); - if let snippet = snippet_with_macro_callsite(cx, expr.span, "}"); + let mut app = Applicability::MaybeIncorrect; + if let snippet = snippet_with_context(cx, expr.span, block.span.ctxt(), "}", &mut app).0; if !snippet.ends_with('}') && !snippet.ends_with(';'); if cx.sess().source_map().is_multiline(block.span); then { @@ -52,17 +52,14 @@ impl<'tcx> LateLintPass<'tcx> for SemicolonIfNothingReturned { if let ExprKind::DropTemps(..) = &expr.kind { return; } - - let sugg = sugg::Sugg::hir_with_macro_callsite(cx, expr, ".."); - let suggestion = format!("{sugg};"); span_lint_and_sugg( cx, SEMICOLON_IF_NOTHING_RETURNED, expr.span.source_callsite(), "consider adding a `;` to the last statement for consistent formatting", "add a `;` here", - suggestion, - Applicability::MaybeIncorrect, + format!("{snippet};"), + app, ); } } diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index e12681c0a0ca6..869358fb1ba70 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -1,9 +1,9 @@ -use crate::FxHashSet; use clippy_utils::{ diagnostics::span_lint_and_then, get_attr, source::{indent_of, snippet}, }; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::{Applicability, Diagnostic}; use rustc_hir::{ self as hir, @@ -58,6 +58,7 @@ impl_lint_pass!(SignificantDropTightening<'_> => [SIGNIFICANT_DROP_TIGHTENING]); pub struct SignificantDropTightening<'tcx> { /// Auxiliary structure used to avoid having to verify the same type multiple times. seen_types: FxHashSet>, + type_cache: FxHashMap, bool>, } impl<'tcx> SignificantDropTightening<'tcx> { @@ -118,7 +119,7 @@ impl<'tcx> SignificantDropTightening<'tcx> { stmt: &hir::Stmt<'_>, cb: impl Fn(&mut SigDropAuxParams), ) { - let mut sig_drop_finder = SigDropFinder::new(cx, &mut self.seen_types); + let mut sig_drop_finder = SigDropFinder::new(cx, &mut self.seen_types, &mut self.type_cache); sig_drop_finder.visit_expr(expr); if sig_drop_finder.has_sig_drop { cb(sdap); @@ -296,15 +297,24 @@ impl Default for SigDropAuxParams { struct SigDropChecker<'cx, 'sdt, 'tcx> { cx: &'cx LateContext<'tcx>, seen_types: &'sdt mut FxHashSet>, + type_cache: &'sdt mut FxHashMap, bool>, } impl<'cx, 'sdt, 'tcx> SigDropChecker<'cx, 'sdt, 'tcx> { - pub(crate) fn new(cx: &'cx LateContext<'tcx>, seen_types: &'sdt mut FxHashSet>) -> Self { + pub(crate) fn new( + cx: &'cx LateContext<'tcx>, + seen_types: &'sdt mut FxHashSet>, + type_cache: &'sdt mut FxHashMap, bool>, + ) -> Self { seen_types.clear(); - Self { cx, seen_types } + Self { + cx, + seen_types, + type_cache, + } } - pub(crate) fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool { + pub(crate) fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>) -> bool { if let Some(adt) = ty.ty_adt_def() { let mut iter = get_attr( self.cx.sess(), @@ -340,6 +350,16 @@ impl<'cx, 'sdt, 'tcx> SigDropChecker<'cx, 'sdt, 'tcx> { } } + pub(crate) fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool { + // The borrow checker prevents us from using something fancier like or_insert_with. + if let Some(ty) = self.type_cache.get(&ty) { + return *ty; + } + let value = self.has_sig_drop_attr_uncached(ty); + self.type_cache.insert(ty, value); + value + } + fn has_seen_ty(&mut self, ty: Ty<'tcx>) -> bool { !self.seen_types.insert(ty) } @@ -353,11 +373,15 @@ struct SigDropFinder<'cx, 'sdt, 'tcx> { } impl<'cx, 'sdt, 'tcx> SigDropFinder<'cx, 'sdt, 'tcx> { - fn new(cx: &'cx LateContext<'tcx>, seen_types: &'sdt mut FxHashSet>) -> Self { + fn new( + cx: &'cx LateContext<'tcx>, + seen_types: &'sdt mut FxHashSet>, + type_cache: &'sdt mut FxHashMap, bool>, + ) -> Self { Self { cx, has_sig_drop: false, - sig_drop_checker: SigDropChecker::new(cx, seen_types), + sig_drop_checker: SigDropChecker::new(cx, seen_types, type_cache), } } } diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 1aeac724ab138..f7eef03d1d473 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -6,7 +6,8 @@ use clippy_utils::{can_mut_borrow_both, eq_expr_value, in_constant, std_or_core} use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Spanned; @@ -188,8 +189,10 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) { if let Some((lhs0, rhs0)) = parse(first) && let Some((lhs1, rhs1)) = parse(second) && first.span.eq_ctxt(second.span) + && !in_external_macro(cx.sess(), first.span) && is_same(cx, lhs0, rhs1) && is_same(cx, lhs1, rhs0) + && !is_same(cx, lhs1, rhs1) // Ignore a = b; a = a (#10421) && let Some(lhs_sugg) = match &lhs0 { ExprOrIdent::Expr(expr) => Sugg::hir_opt(cx, expr), ExprOrIdent::Ident(ident) => Some(Sugg::NonParen(ident.as_str().into())), @@ -257,8 +260,8 @@ fn parse<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(ExprOrIdent<'hir>, &'a Expr< /// Implementation of the xor case for `MANUAL_SWAP` lint. fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) { for [s1, s2, s3] in block.stmts.array_windows::<3>() { + let ctxt = s1.span.ctxt(); if_chain! { - let ctxt = s1.span.ctxt(); if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(s1, ctxt); if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(s2, ctxt); if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(s3, ctxt); diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 3430b6e373418..cc7c2b039f2d7 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::get_parent_node; -use clippy_utils::source::snippet_with_macro_callsite; +use clippy_utils::source::snippet_with_context; use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source}; use core::ops::ControlFlow; use rustc_errors::Applicability; @@ -52,12 +52,13 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { "this let-binding has unit value", |diag| { if let Some(expr) = &local.init { - let snip = snippet_with_macro_callsite(cx, expr.span, "()"); + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, expr.span, local.span.ctxt(), "()", &mut app).0; diag.span_suggestion( local.span, "omit the `let` binding", format!("{snip};"), - Applicability::MachineApplicable, // snippet + app, ); } }, diff --git a/clippy_lints/src/unnecessary_struct_initialization.rs b/clippy_lints/src/unnecessary_struct_initialization.rs new file mode 100644 index 0000000000000..af0b4b1592f4c --- /dev/null +++ b/clippy_lints/src/unnecessary_struct_initialization.rs @@ -0,0 +1,84 @@ +use clippy_utils::{diagnostics::span_lint_and_sugg, get_parent_expr, path_to_local, source::snippet, ty::is_copy}; +use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for initialization of a `struct` by copying a base without setting + /// any field. + /// + /// ### Why is this bad? + /// Readibility suffers from unnecessary struct building. + /// + /// ### Example + /// ```rust + /// struct S { s: String } + /// + /// let a = S { s: String::from("Hello, world!") }; + /// let b = S { ..a }; + /// ``` + /// Use instead: + /// ```rust + /// struct S { s: String } + /// + /// let a = S { s: String::from("Hello, world!") }; + /// let b = a; + /// ``` + #[clippy::version = "1.70.0"] + pub UNNECESSARY_STRUCT_INITIALIZATION, + complexity, + "struct built from a base that can be written mode concisely" +} +declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]); + +impl LateLintPass<'_> for UnnecessaryStruct { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Struct(_, &[], Some(base)) = expr.kind { + if let Some(parent) = get_parent_expr(cx, expr) && + let parent_ty = cx.typeck_results().expr_ty_adjusted(parent) && + parent_ty.is_any_ptr() + { + if is_copy(cx, cx.typeck_results().expr_ty(expr)) && path_to_local(base).is_some() { + // When the type implements `Copy`, a reference to the new struct works on the + // copy. Using the original would borrow it. + return; + } + + if parent_ty.is_mutable_ptr() && !is_mutable(cx, base) { + // The original can be used in a mutable reference context only if it is mutable. + return; + } + } + + // TODO: do not propose to replace *XX if XX is not Copy + if let ExprKind::Unary(UnOp::Deref, target) = base.kind && + matches!(target.kind, ExprKind::Path(..)) && + !is_copy(cx, cx.typeck_results().expr_ty(expr)) + { + // `*base` cannot be used instead of the struct in the general case if it is not Copy. + return; + } + + span_lint_and_sugg( + cx, + UNNECESSARY_STRUCT_INITIALIZATION, + expr.span, + "unnecessary struct building", + "replace with", + snippet(cx, base.span, "..").into_owned(), + rustc_errors::Applicability::MachineApplicable, + ); + } + } +} + +fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let Some(hir_id) = path_to_local(expr) && + let Node::Pat(pat) = cx.tcx.hir().get(hir_id) + { + matches!(pat.kind, PatKind::Binding(BindingAnnotation::MUT, ..)) + } else { + true + } +} diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index fede625f72a89..ddbe6b2c7904d 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; -use clippy_utils::source::{snippet, snippet_with_macro_callsite}; +use clippy_utils::source::{snippet, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts}; use clippy_utils::{get_parent_expr, is_trait_method, match_def_path, path_to_local, paths}; @@ -68,15 +68,16 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { let a = cx.typeck_results().expr_ty(e); let b = cx.typeck_results().expr_ty(recv); if same_type_and_consts(a, b) { - let sugg = snippet_with_macro_callsite(cx, recv.span, "").to_string(); + let mut app = Applicability::MachineApplicable; + let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "", &mut app).0; span_lint_and_sugg( cx, USELESS_CONVERSION, e.span, &format!("useless conversion to the same type: `{b}`"), "consider removing `.into()`", - sugg, - Applicability::MachineApplicable, // snippet + sugg.into_owned(), + app, ); } } @@ -165,7 +166,8 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { if same_type_and_consts(a, b); then { - let sugg = Sugg::hir_with_macro_callsite(cx, arg, "").maybe_par(); + let mut app = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "", &mut app).maybe_par(); let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); span_lint_and_sugg( @@ -175,7 +177,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { &format!("useless conversion to the same type: `{b}`"), &sugg_msg, sugg.to_string(), - Applicability::MachineApplicable, // snippet + app, ); } } diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 1c7f3e96db894..8ba252425a3d0 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -437,7 +437,7 @@ define_Conf! { /// /// The maximum size of the `Err`-variant in a `Result` returned from a function (large_error_threshold: u64 = 128), - /// Lint: MUTABLE_KEY_TYPE. + /// Lint: MUTABLE_KEY_TYPE, IFS_SAME_COND. /// /// A list of paths to types that should be treated like `Arc`, i.e. ignored but /// for the generic parameters for determining interior mutability diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index e105452e1c5f3..36f910c983f64 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -158,12 +158,10 @@ impl LateLintPass<'_> for WildcardImports { let mut imports = used_imports.items().map(ToString::to_string).into_sorted_stable_ord(false); let imports_string = if imports.len() == 1 { imports.pop().unwrap() + } else if braced_glob { + imports.join(", ") } else { - if braced_glob { - imports.join(", ") - } else { - format!("{{{}}}", imports.join(", ")) - } + format!("{{{}}}", imports.join(", ")) }; let sugg = if braced_glob { diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index bc3d774540a51..b4ad42a50279f 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -146,7 +146,7 @@ pub fn get_unique_attr<'a>( /// Return true if the attributes contain any of `proc_macro`, /// `proc_macro_derive` or `proc_macro_attribute`, false otherwise pub fn is_proc_macro(attrs: &[ast::Attribute]) -> bool { - attrs.iter().any(|attr| attr.is_proc_macro_attr()) + attrs.iter().any(rustc_ast::Attribute::is_proc_macro_attr) } /// Return true if the attributes contain `#[doc(hidden)]` diff --git a/clippy_utils/src/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs index babbc7294a173..28c8571706135 100644 --- a/clippy_utils/src/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -199,10 +199,9 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS }, // Memory allocation, custom operator, loop, or call to an unknown function - ExprKind::Unary(..) - | ExprKind::Binary(..) - | ExprKind::Loop(..) - | ExprKind::Call(..) => self.eagerness = Lazy, + ExprKind::Unary(..) | ExprKind::Binary(..) | ExprKind::Loop(..) | ExprKind::Call(..) => { + self.eagerness = Lazy; + }, ExprKind::ConstBlock(_) | ExprKind::Array(_) diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index dbf9f3b621d7a..e05de2dc99c05 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -19,6 +19,7 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { + 1,68,0 { PATH_MAIN_SEPARATOR_STR } 1,65,0 { LET_ELSE } 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE } 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 4aae0f7284e4d..c919575bfe906 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -67,6 +67,7 @@ pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"]; pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"]; pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"]; +pub const PATH_MAIN_SEPARATOR: [&str; 3] = ["std", "path", "MAIN_SEPARATOR"]; pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"]; pub const PEEKABLE: [&str; 5] = ["core", "iter", "adapters", "peekable", "Peekable"]; pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"]; diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 9f6adf3e3fab7..d66640ba0b7a7 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -176,9 +176,10 @@ fn check_rvalue<'tcx>( // FIXME(dyn-star) unimplemented!() }, - Rvalue::Cast(CastKind::Transmute, _, _) => { - Err((span, "transmute can attempt to turn pointers into integers, so is unstable in const fn".into())) - }, + Rvalue::Cast(CastKind::Transmute, _, _) => Err(( + span, + "transmute can attempt to turn pointers into integers, so is unstable in const fn".into(), + )), // binops are fine on integers Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => { check_operand(tcx, lhs, span, body)?; diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index cd5dcfdaca34b..62fa37660fad5 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -12,24 +12,21 @@ use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext, DUMMY_SP}; use std::borrow::Cow; /// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`. -/// Also takes an `Option` which can be put inside the braces. -pub fn expr_block<'a, T: LintContext>( +pub fn expr_block( cx: &T, expr: &Expr<'_>, - option: Option, - default: &'a str, + outer: SyntaxContext, + default: &str, indent_relative_to: Option, -) -> Cow<'a, str> { - let code = snippet_block(cx, expr.span, default, indent_relative_to); - let string = option.unwrap_or_default(); - if expr.span.from_expansion() { - Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default))) + app: &mut Applicability, +) -> String { + let (code, from_macro) = snippet_block_with_context(cx, expr.span, outer, default, indent_relative_to, app); + if from_macro { + format!("{{ {code} }}") } else if let ExprKind::Block(_, _) = expr.kind { - Cow::Owned(format!("{code}{string}")) - } else if string.is_empty() { - Cow::Owned(format!("{{ {code} }}")) + format!("{code}") } else { - Cow::Owned(format!("{{\n{code};\n{string}\n}}")) + format!("{{ {code} }}") } } @@ -229,12 +226,6 @@ fn snippet_with_applicability_sess<'a>( ) } -/// Same as `snippet`, but should only be used when it's clear that the input span is -/// not a macro argument. -pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { - snippet(cx, span.source_callsite(), default) -} - /// Converts a span to a code snippet. Returns `None` if not available. pub fn snippet_opt(cx: &impl LintContext, span: Span) -> Option { snippet_opt_sess(cx.sess(), span) @@ -303,6 +294,19 @@ pub fn snippet_block_with_applicability<'a>( reindent_multiline(snip, true, indent) } +pub fn snippet_block_with_context<'a>( + cx: &impl LintContext, + span: Span, + outer: SyntaxContext, + default: &'a str, + indent_relative_to: Option, + app: &mut Applicability, +) -> (Cow<'a, str>, bool) { + let (snip, from_macro) = snippet_with_context(cx, span, outer, default, app); + let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); + (reindent_multiline(snip, true, indent), from_macro) +} + /// Same as `snippet_with_applicability`, but first walks the span up to the given context. This /// will result in the macro call, rather then the expansion, if the span is from a child context. /// If the span is not from a child context, it will be used directly instead. diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 44cb5d5756ada..a5a4a921d94ec 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -1,9 +1,7 @@ //! Contains utility functions to generate suggestions. #![deny(clippy::missing_docs_in_private_items)] -use crate::source::{ - snippet, snippet_opt, snippet_with_applicability, snippet_with_context, snippet_with_macro_callsite, -}; +use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_context}; use crate::ty::expr_sig; use crate::{get_parent_expr_for_hir, higher}; use rustc_ast::util::parser::AssocOp; @@ -89,12 +87,6 @@ impl<'a> Sugg<'a> { }) } - /// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro. - pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self { - let get_snippet = |span| snippet_with_macro_callsite(cx, span, default); - Self::hir_from_snippet(expr, get_snippet) - } - /// Same as `hir`, but first walks the span up to the given context. This will result in the /// macro call, rather then the expansion, if the span is from a child context. If the span is /// not from a child context, it will be used directly instead. diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index e0ea3952785bb..0b47234647fb6 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -16,9 +16,9 @@ use rustc_infer::infer::{ use rustc_lint::LateContext; use rustc_middle::mir::interpret::{ConstValue, Scalar}; use rustc_middle::ty::{ - self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, IntTy, List, ParamEnv, Predicate, PredicateKind, - Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, - UintTy, VariantDef, VariantDiscr, + self, layout::ValidityRequirement, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, IntTy, List, ParamEnv, + Predicate, PredicateKind, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr, }; use rustc_middle::ty::{GenericArg, GenericArgKind}; use rustc_span::symbol::Ident; @@ -538,13 +538,12 @@ pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { } /// Checks if a given type looks safe to be uninitialized. -pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - match *ty.kind() { - ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component), - ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)), - ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did()), - _ => false, - } +pub fn is_uninit_value_valid_for_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + cx.tcx + .check_validity_requirement((ValidityRequirement::Uninit, cx.param_env.and(ty))) + // For types containing generic parameters we cannot get a layout to check. + // Therefore, we are conservative and assume that they don't allow uninit. + .unwrap_or(false) } /// Gets an iterator over all predicates which apply to the given item. @@ -1121,3 +1120,47 @@ pub fn make_normalized_projection<'tcx>( } helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, substs)?) } + +/// Check if given type has inner mutability such as [`std::cell::Cell`] or [`std::cell::RefCell`] +/// etc. +pub fn is_interior_mut_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + match *ty.kind() { + ty::Ref(_, inner_ty, mutbl) => mutbl == Mutability::Mut || is_interior_mut_ty(cx, inner_ty), + ty::Slice(inner_ty) => is_interior_mut_ty(cx, inner_ty), + ty::Array(inner_ty, size) => { + size.try_eval_target_usize(cx.tcx, cx.param_env) + .map_or(true, |u| u != 0) + && is_interior_mut_ty(cx, inner_ty) + }, + ty::Tuple(fields) => fields.iter().any(|ty| is_interior_mut_ty(cx, ty)), + ty::Adt(def, substs) => { + // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to + // that of their type parameters. Note: we don't include `HashSet` and `HashMap` + // because they have no impl for `Hash` or `Ord`. + let def_id = def.did(); + let is_std_collection = [ + sym::Option, + sym::Result, + sym::LinkedList, + sym::Vec, + sym::VecDeque, + sym::BTreeMap, + sym::BTreeSet, + sym::Rc, + sym::Arc, + ] + .iter() + .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def_id)); + let is_box = Some(def_id) == cx.tcx.lang_items().owned_box(); + if is_std_collection || is_box { + // The type is mutable if any of its type parameters are + substs.types().any(|ty| is_interior_mut_ty(cx, ty)) + } else { + !ty.has_escaping_bound_vars() + && cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() + && !ty.is_freeze(cx.tcx, cx.param_env) + } + }, + _ => false, + } +} diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 86a93f64fb71e..1dc19bac98444 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -599,9 +599,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>( | ExprKind::Let(&Let { init: e, .. }) => { helper(typeck, false, e, f)?; }, - ExprKind::Block(&Block { expr: Some(e), .. }, _) - | ExprKind::Cast(e, _) - | ExprKind::Unary(_, e) => { + ExprKind::Block(&Block { expr: Some(e), .. }, _) | ExprKind::Cast(e, _) | ExprKind::Unary(_, e) => { helper(typeck, true, e, f)?; }, ExprKind::Call(callee, args) => { diff --git a/rust-toolchain b/rust-toolchain index d788c6359d7a4..0b2458ea00708 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-03-10" +channel = "nightly-2023-03-24" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 9643c2c970704..3a5d478fa3140 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -37,10 +37,10 @@ fn dogfood_clippy() { } assert!( - !failed_packages.is_empty(), + failed_packages.is_empty(), "Dogfood failed for packages `{}`", failed_packages.iter().format(", "), - ) + ); } #[test] diff --git a/tests/ui-internal/custom_ice_message.stderr b/tests/ui-internal/custom_ice_message.stderr index 7ed0ef0274fa3..b4619e980f303 100644 --- a/tests/ui-internal/custom_ice_message.stderr +++ b/tests/ui-internal/custom_ice_message.stderr @@ -9,3 +9,4 @@ note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy note: Clippy version: foo +thread panicked while panicking. aborting. diff --git a/tests/ui-toml/ifs_same_cond/clippy.toml b/tests/ui-toml/ifs_same_cond/clippy.toml new file mode 100644 index 0000000000000..90a36ecd92022 --- /dev/null +++ b/tests/ui-toml/ifs_same_cond/clippy.toml @@ -0,0 +1 @@ +ignore-interior-mutability = ["std::cell::Cell"] diff --git a/tests/ui-toml/ifs_same_cond/ifs_same_cond.rs b/tests/ui-toml/ifs_same_cond/ifs_same_cond.rs new file mode 100644 index 0000000000000..d623ac7e02008 --- /dev/null +++ b/tests/ui-toml/ifs_same_cond/ifs_same_cond.rs @@ -0,0 +1,18 @@ +#![warn(clippy::ifs_same_cond)] +#![allow(clippy::if_same_then_else, clippy::comparison_chain)] + +fn main() {} + +fn issue10272() { + use std::cell::Cell; + + // Because the `ignore-interior-mutability` configuration + // is set to ignore for `std::cell::Cell`, the following `get()` calls + // should trigger warning + let x = Cell::new(true); + if x.get() { + } else if !x.take() { + } else if x.get() { + } else { + } +} diff --git a/tests/ui-toml/ifs_same_cond/ifs_same_cond.stderr b/tests/ui-toml/ifs_same_cond/ifs_same_cond.stderr new file mode 100644 index 0000000000000..2841f62bc94ab --- /dev/null +++ b/tests/ui-toml/ifs_same_cond/ifs_same_cond.stderr @@ -0,0 +1,15 @@ +error: this `if` has the same condition as a previous `if` + --> $DIR/ifs_same_cond.rs:15:15 + | +LL | } else if x.get() { + | ^^^^^^^ + | +note: same as this + --> $DIR/ifs_same_cond.rs:13:8 + | +LL | if x.get() { + | ^^^^^^^ + = note: `-D clippy::ifs-same-cond` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui/allow_attributes.fixed b/tests/ui/allow_attributes.fixed new file mode 100644 index 0000000000000..b8dd0619e6d00 --- /dev/null +++ b/tests/ui/allow_attributes.fixed @@ -0,0 +1,25 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::allow_attributes)] +#![feature(lint_reasons)] + +fn main() {} + +// Using clippy::needless_borrow just as a placeholder, it isn't relevant. + +// Should lint +#[expect(dead_code)] +struct T1; + +struct T2; // Should not lint +#[deny(clippy::needless_borrow)] // Should not lint +struct T3; +#[warn(clippy::needless_borrow)] // Should not lint +struct T4; +// `panic = "unwind"` should always be true +#[cfg_attr(panic = "unwind", expect(dead_code))] +struct CfgT; + +fn ignore_inner_attr() { + #![allow(unused)] // Should not lint +} diff --git a/tests/ui/allow_attributes.rs b/tests/ui/allow_attributes.rs new file mode 100644 index 0000000000000..295f560906a78 --- /dev/null +++ b/tests/ui/allow_attributes.rs @@ -0,0 +1,25 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::allow_attributes)] +#![feature(lint_reasons)] + +fn main() {} + +// Using clippy::needless_borrow just as a placeholder, it isn't relevant. + +// Should lint +#[allow(dead_code)] +struct T1; + +struct T2; // Should not lint +#[deny(clippy::needless_borrow)] // Should not lint +struct T3; +#[warn(clippy::needless_borrow)] // Should not lint +struct T4; +// `panic = "unwind"` should always be true +#[cfg_attr(panic = "unwind", allow(dead_code))] +struct CfgT; + +fn ignore_inner_attr() { + #![allow(unused)] // Should not lint +} diff --git a/tests/ui/allow_attributes.stderr b/tests/ui/allow_attributes.stderr new file mode 100644 index 0000000000000..681837e9ed770 --- /dev/null +++ b/tests/ui/allow_attributes.stderr @@ -0,0 +1,16 @@ +error: #[allow] attribute found + --> $DIR/allow_attributes.rs:11:3 + | +LL | #[allow(dead_code)] + | ^^^^^ help: replace it with: `expect` + | + = note: `-D clippy::allow-attributes` implied by `-D warnings` + +error: #[allow] attribute found + --> $DIR/allow_attributes.rs:20:30 + | +LL | #[cfg_attr(panic = "unwind", allow(dead_code))] + | ^^^^^ help: replace it with: `expect` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/almost_complete_range.fixed b/tests/ui/almost_complete_range.fixed index 6046addf71964..a4bf7fe18d5f4 100644 --- a/tests/ui/almost_complete_range.fixed +++ b/tests/ui/almost_complete_range.fixed @@ -1,6 +1,6 @@ // run-rustfix // edition:2018 -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![feature(exclusive_range_pattern)] #![feature(stmt_expr_attributes)] @@ -9,33 +9,10 @@ #![allow(clippy::needless_parens_on_range_literals)] #![allow(clippy::double_parens)] -#[macro_use] -extern crate macro_rules; - -macro_rules! a { - () => { - 'a' - }; -} -macro_rules! A { - () => { - 'A' - }; -} -macro_rules! zero { - () => { - '0' - }; -} - -macro_rules! b { - () => { - let _ = 'a'..='z'; - let _ = 'A'..='Z'; - let _ = '0'..='9'; - }; -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { #[rustfmt::skip] { @@ -56,9 +33,9 @@ fn main() { let _ = b'B'..b'Z'; let _ = b'1'..b'9'; - let _ = a!()..='z'; - let _ = A!()..='Z'; - let _ = zero!()..='9'; + let _ = inline!('a')..='z'; + let _ = inline!('A')..='Z'; + let _ = inline!('0')..='9'; let _ = match 0u8 { b'a'..=b'z' if true => 1, @@ -80,8 +57,16 @@ fn main() { _ => 7, }; - almost_complete_range!(); - b!(); + external!( + let _ = 'a'..'z'; + let _ = 'A'..'Z'; + let _ = '0'..'9'; + ); + inline!( + let _ = 'a'..='z'; + let _ = 'A'..='Z'; + let _ = '0'..='9'; + ); } #[clippy::msrv = "1.25"] diff --git a/tests/ui/almost_complete_range.rs b/tests/ui/almost_complete_range.rs index ae7e07ab872b7..8237c3a13611a 100644 --- a/tests/ui/almost_complete_range.rs +++ b/tests/ui/almost_complete_range.rs @@ -1,6 +1,6 @@ // run-rustfix // edition:2018 -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![feature(exclusive_range_pattern)] #![feature(stmt_expr_attributes)] @@ -9,33 +9,10 @@ #![allow(clippy::needless_parens_on_range_literals)] #![allow(clippy::double_parens)] -#[macro_use] -extern crate macro_rules; - -macro_rules! a { - () => { - 'a' - }; -} -macro_rules! A { - () => { - 'A' - }; -} -macro_rules! zero { - () => { - '0' - }; -} - -macro_rules! b { - () => { - let _ = 'a'..'z'; - let _ = 'A'..'Z'; - let _ = '0'..'9'; - }; -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { #[rustfmt::skip] { @@ -56,9 +33,9 @@ fn main() { let _ = b'B'..b'Z'; let _ = b'1'..b'9'; - let _ = a!()..'z'; - let _ = A!()..'Z'; - let _ = zero!()..'9'; + let _ = inline!('a')..'z'; + let _ = inline!('A')..'Z'; + let _ = inline!('0')..'9'; let _ = match 0u8 { b'a'..b'z' if true => 1, @@ -80,8 +57,16 @@ fn main() { _ => 7, }; - almost_complete_range!(); - b!(); + external!( + let _ = 'a'..'z'; + let _ = 'A'..'Z'; + let _ = '0'..'9'; + ); + inline!( + let _ = 'a'..'z'; + let _ = 'A'..'Z'; + let _ = '0'..'9'; + ); } #[clippy::msrv = "1.25"] diff --git a/tests/ui/almost_complete_range.stderr b/tests/ui/almost_complete_range.stderr index a7a5328785025..34521c13ab3ed 100644 --- a/tests/ui/almost_complete_range.stderr +++ b/tests/ui/almost_complete_range.stderr @@ -1,5 +1,5 @@ error: almost complete ascii range - --> $DIR/almost_complete_range.rs:42:17 + --> $DIR/almost_complete_range.rs:19:17 | LL | let _ = ('a') ..'z'; | ^^^^^^--^^^ @@ -9,7 +9,7 @@ LL | let _ = ('a') ..'z'; = note: `-D clippy::almost-complete-range` implied by `-D warnings` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:43:17 + --> $DIR/almost_complete_range.rs:20:17 | LL | let _ = 'A' .. ('Z'); | ^^^^--^^^^^^ @@ -17,7 +17,7 @@ LL | let _ = 'A' .. ('Z'); | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:44:17 + --> $DIR/almost_complete_range.rs:21:17 | LL | let _ = ((('0'))) .. ('9'); | ^^^^^^^^^^--^^^^^^ @@ -25,7 +25,7 @@ LL | let _ = ((('0'))) .. ('9'); | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:51:13 + --> $DIR/almost_complete_range.rs:28:13 | LL | let _ = (b'a')..(b'z'); | ^^^^^^--^^^^^^ @@ -33,7 +33,7 @@ LL | let _ = (b'a')..(b'z'); | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:52:13 + --> $DIR/almost_complete_range.rs:29:13 | LL | let _ = b'A'..b'Z'; | ^^^^--^^^^ @@ -41,7 +41,7 @@ LL | let _ = b'A'..b'Z'; | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:53:13 + --> $DIR/almost_complete_range.rs:30:13 | LL | let _ = b'0'..b'9'; | ^^^^--^^^^ @@ -49,31 +49,31 @@ LL | let _ = b'0'..b'9'; | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:59:13 + --> $DIR/almost_complete_range.rs:36:13 | -LL | let _ = a!()..'z'; - | ^^^^--^^^ - | | - | help: use an inclusive range: `..=` +LL | let _ = inline!('a')..'z'; + | ^^^^^^^^^^^^--^^^ + | | + | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:60:13 + --> $DIR/almost_complete_range.rs:37:13 | -LL | let _ = A!()..'Z'; - | ^^^^--^^^ - | | - | help: use an inclusive range: `..=` +LL | let _ = inline!('A')..'Z'; + | ^^^^^^^^^^^^--^^^ + | | + | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:61:13 + --> $DIR/almost_complete_range.rs:38:13 | -LL | let _ = zero!()..'9'; - | ^^^^^^^--^^^ - | | - | help: use an inclusive range: `..=` +LL | let _ = inline!('0')..'9'; + | ^^^^^^^^^^^^--^^^ + | | + | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:64:9 + --> $DIR/almost_complete_range.rs:41:9 | LL | b'a'..b'z' if true => 1, | ^^^^--^^^^ @@ -81,7 +81,7 @@ LL | b'a'..b'z' if true => 1, | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:65:9 + --> $DIR/almost_complete_range.rs:42:9 | LL | b'A'..b'Z' if true => 2, | ^^^^--^^^^ @@ -89,7 +89,7 @@ LL | b'A'..b'Z' if true => 2, | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:66:9 + --> $DIR/almost_complete_range.rs:43:9 | LL | b'0'..b'9' if true => 3, | ^^^^--^^^^ @@ -97,7 +97,7 @@ LL | b'0'..b'9' if true => 3, | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:74:9 + --> $DIR/almost_complete_range.rs:51:9 | LL | 'a'..'z' if true => 1, | ^^^--^^^ @@ -105,7 +105,7 @@ LL | 'a'..'z' if true => 1, | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:75:9 + --> $DIR/almost_complete_range.rs:52:9 | LL | 'A'..'Z' if true => 2, | ^^^--^^^ @@ -113,7 +113,7 @@ LL | 'A'..'Z' if true => 2, | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:76:9 + --> $DIR/almost_complete_range.rs:53:9 | LL | '0'..'9' if true => 3, | ^^^--^^^ @@ -121,46 +121,37 @@ LL | '0'..'9' if true => 3, | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:33:17 + --> $DIR/almost_complete_range.rs:66:17 | LL | let _ = 'a'..'z'; | ^^^--^^^ | | | help: use an inclusive range: `..=` -... -LL | b!(); - | ---- in this macro invocation | - = note: this error originates in the macro `b` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: almost complete ascii range - --> $DIR/almost_complete_range.rs:34:17 + --> $DIR/almost_complete_range.rs:67:17 | LL | let _ = 'A'..'Z'; | ^^^--^^^ | | | help: use an inclusive range: `..=` -... -LL | b!(); - | ---- in this macro invocation | - = note: this error originates in the macro `b` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: almost complete ascii range - --> $DIR/almost_complete_range.rs:35:17 + --> $DIR/almost_complete_range.rs:68:17 | LL | let _ = '0'..'9'; | ^^^--^^^ | | | help: use an inclusive range: `..=` -... -LL | b!(); - | ---- in this macro invocation | - = note: this error originates in the macro `b` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: almost complete ascii range - --> $DIR/almost_complete_range.rs:90:9 + --> $DIR/almost_complete_range.rs:75:9 | LL | 'a'..'z' => 1, | ^^^--^^^ @@ -168,7 +159,7 @@ LL | 'a'..'z' => 1, | help: use an inclusive range: `...` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:91:9 + --> $DIR/almost_complete_range.rs:76:9 | LL | 'A'..'Z' => 2, | ^^^--^^^ @@ -176,7 +167,7 @@ LL | 'A'..'Z' => 2, | help: use an inclusive range: `...` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:92:9 + --> $DIR/almost_complete_range.rs:77:9 | LL | '0'..'9' => 3, | ^^^--^^^ @@ -184,7 +175,7 @@ LL | '0'..'9' => 3, | help: use an inclusive range: `...` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:99:13 + --> $DIR/almost_complete_range.rs:84:13 | LL | let _ = 'a'..'z'; | ^^^--^^^ @@ -192,7 +183,7 @@ LL | let _ = 'a'..'z'; | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:100:13 + --> $DIR/almost_complete_range.rs:85:13 | LL | let _ = 'A'..'Z'; | ^^^--^^^ @@ -200,7 +191,7 @@ LL | let _ = 'A'..'Z'; | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:101:13 + --> $DIR/almost_complete_range.rs:86:13 | LL | let _ = '0'..'9'; | ^^^--^^^ @@ -208,7 +199,7 @@ LL | let _ = '0'..'9'; | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:103:9 + --> $DIR/almost_complete_range.rs:88:9 | LL | 'a'..'z' => 1, | ^^^--^^^ @@ -216,7 +207,7 @@ LL | 'a'..'z' => 1, | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:104:9 + --> $DIR/almost_complete_range.rs:89:9 | LL | 'A'..'Z' => 1, | ^^^--^^^ @@ -224,7 +215,7 @@ LL | 'A'..'Z' => 1, | help: use an inclusive range: `..=` error: almost complete ascii range - --> $DIR/almost_complete_range.rs:105:9 + --> $DIR/almost_complete_range.rs:90:9 | LL | '0'..'9' => 3, | ^^^--^^^ diff --git a/tests/ui/as_conversions.rs b/tests/ui/as_conversions.rs index ba4394defbf20..c50d4088b5ebb 100644 --- a/tests/ui/as_conversions.rs +++ b/tests/ui/as_conversions.rs @@ -1,20 +1,15 @@ -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::as_conversions)] #![allow(clippy::borrow_as_ptr)] -#[macro_use] -extern crate macro_rules; - -fn with_external_macro() { - as_conv_with_arg!(0u32 as u64); - as_conv!(); -} +extern crate proc_macros; +use proc_macros::external; fn main() { let i = 0u32 as u64; let j = &i as *const u64 as *mut u64; - with_external_macro(); + external!(0u32 as u64); } diff --git a/tests/ui/as_conversions.stderr b/tests/ui/as_conversions.stderr index f5d59e1e5d836..54037a6499787 100644 --- a/tests/ui/as_conversions.stderr +++ b/tests/ui/as_conversions.stderr @@ -1,5 +1,5 @@ error: using a potentially dangerous silent `as` conversion - --> $DIR/as_conversions.rs:15:13 + --> $DIR/as_conversions.rs:10:13 | LL | let i = 0u32 as u64; | ^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | let i = 0u32 as u64; = note: `-D clippy::as-conversions` implied by `-D warnings` error: using a potentially dangerous silent `as` conversion - --> $DIR/as_conversions.rs:17:13 + --> $DIR/as_conversions.rs:12:13 | LL | let j = &i as *const u64 as *mut u64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ LL | let j = &i as *const u64 as *mut u64; = help: consider using a safe wrapper for this conversion error: using a potentially dangerous silent `as` conversion - --> $DIR/as_conversions.rs:17:13 + --> $DIR/as_conversions.rs:12:13 | LL | let j = &i as *const u64 as *mut u64; | ^^^^^^^^^^^^^^^^ diff --git a/tests/ui/auxiliary/doc_unsafe_macros.rs b/tests/ui/auxiliary/doc_unsafe_macros.rs deleted file mode 100644 index 3d917e3dc75e9..0000000000000 --- a/tests/ui/auxiliary/doc_unsafe_macros.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[macro_export] -macro_rules! undocd_unsafe { - () => { - pub unsafe fn oy_vey() { - unimplemented!(); - } - }; -} -#[macro_export] -macro_rules! undocd_safe { - () => { - pub fn vey_oy() { - unimplemented!(); - } - }; -} diff --git a/tests/ui/auxiliary/implicit_hasher_macros.rs b/tests/ui/auxiliary/implicit_hasher_macros.rs deleted file mode 100644 index 1eb77c531835a..0000000000000 --- a/tests/ui/auxiliary/implicit_hasher_macros.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[macro_export] -macro_rules! implicit_hasher_fn { - () => { - pub fn f(input: &HashMap) {} - }; -} diff --git a/tests/ui/auxiliary/macro_rules.rs b/tests/ui/auxiliary/macro_rules.rs index a13af56520389..a9bb61451dca6 100644 --- a/tests/ui/auxiliary/macro_rules.rs +++ b/tests/ui/auxiliary/macro_rules.rs @@ -2,21 +2,6 @@ //! Used to test that certain lints don't trigger in imported external macros -#[macro_export] -macro_rules! foofoo { - () => { - loop {} - }; -} - -#[macro_export] -macro_rules! must_use_unit { - () => { - #[must_use] - fn foo() {} - }; -} - #[macro_export] macro_rules! try_err { () => { @@ -36,84 +21,6 @@ macro_rules! string_add { }; } -#[macro_export] -macro_rules! take_external { - ($s:expr) => { - std::mem::replace($s, Default::default()) - }; -} - -#[macro_export] -macro_rules! option_env_unwrap_external { - ($env: expr) => { - option_env!($env).unwrap() - }; - ($env: expr, $message: expr) => { - option_env!($env).expect($message) - }; -} - -#[macro_export] -macro_rules! ref_arg_binding { - () => { - let ref _y = 42; - }; -} - -#[macro_export] -macro_rules! ref_arg_function { - () => { - fn fun_example(ref _x: usize) {} - }; -} - -#[macro_export] -macro_rules! as_conv_with_arg { - (0u32 as u64) => { - () - }; -} - -#[macro_export] -macro_rules! as_conv { - () => { - 0u32 as u64 - }; -} - -#[macro_export] -macro_rules! large_enum_variant { - () => { - enum LargeEnumInMacro { - A(i32), - B([i32; 8000]), - } - }; -} - -#[macro_export] -macro_rules! field_reassign_with_default { - () => { - #[derive(Default)] - struct A { - pub i: i32, - pub j: i64, - } - fn lint() { - let mut a: A = Default::default(); - a.i = 42; - a; - } - }; -} - -#[macro_export] -macro_rules! default_numeric_fallback { - () => { - let x = 22; - }; -} - #[macro_export] macro_rules! mut_mut { () => { @@ -122,49 +29,11 @@ macro_rules! mut_mut { } #[macro_export] -macro_rules! ptr_as_ptr_cast { - ($ptr: ident) => { - $ptr as *const i32 - }; -} - -#[macro_export] -macro_rules! manual_rem_euclid { +macro_rules! issue_10421 { () => { - let value: i32 = 5; - let _: i32 = ((value % 4) + 4) % 4; - }; -} - -#[macro_export] -macro_rules! equatable_if_let { - ($a:ident) => {{ if let 2 = $a {} }}; -} - -#[macro_export] -macro_rules! almost_complete_range { - () => { - let _ = 'a'..'z'; - let _ = 'A'..'Z'; - let _ = '0'..'9'; - }; -} - -#[macro_export] -macro_rules! unsafe_macro { - () => { - unsafe { - *core::ptr::null::<()>(); - *core::ptr::null::<()>(); - } - }; -} - -#[macro_export] -macro_rules! needless_lifetime { - () => { - fn needless_lifetime<'a>(x: &'a u8) -> &'a u8 { - unimplemented!() - } + let mut a = 1; + let mut b = 2; + a = b; + b = a; }; } diff --git a/tests/ui/auxiliary/macro_use_helper.rs b/tests/ui/auxiliary/macro_use_helper.rs index ecb55d8cb48d5..7ed8a28dbd935 100644 --- a/tests/ui/auxiliary/macro_use_helper.rs +++ b/tests/ui/auxiliary/macro_use_helper.rs @@ -13,7 +13,7 @@ pub mod inner { // RE-EXPORT // this will stick in `inner` module - pub use macro_rules::foofoo; + pub use macro_rules::mut_mut; pub use macro_rules::try_err; pub mod nested { diff --git a/tests/ui/auxiliary/proc_macro_with_span.rs b/tests/ui/auxiliary/proc_macro_with_span.rs deleted file mode 100644 index 8ea631f2bbd42..0000000000000 --- a/tests/ui/auxiliary/proc_macro_with_span.rs +++ /dev/null @@ -1,32 +0,0 @@ -// compile-flags: --emit=link -// no-prefer-dynamic - -#![crate_type = "proc-macro"] - -extern crate proc_macro; - -use proc_macro::{token_stream::IntoIter, Group, Span, TokenStream, TokenTree}; - -#[proc_macro] -pub fn with_span(input: TokenStream) -> TokenStream { - let mut iter = input.into_iter(); - let span = iter.next().unwrap().span(); - let mut res = TokenStream::new(); - write_with_span(span, iter, &mut res); - res -} - -fn write_with_span(s: Span, input: IntoIter, out: &mut TokenStream) { - for mut tt in input { - if let TokenTree::Group(g) = tt { - let mut stream = TokenStream::new(); - write_with_span(s, g.stream().into_iter(), &mut stream); - let mut group = Group::new(g.delimiter(), stream); - group.set_span(s); - out.extend([TokenTree::Group(group)]); - } else { - tt.set_span(s); - out.extend([tt]); - } - } -} diff --git a/tests/ui/auxiliary/proc_macros.rs b/tests/ui/auxiliary/proc_macros.rs new file mode 100644 index 0000000000000..325be83a0d73d --- /dev/null +++ b/tests/ui/auxiliary/proc_macros.rs @@ -0,0 +1,474 @@ +// compile-flags: --emit=link +// no-prefer-dynamic + +#![crate_type = "proc-macro"] +#![feature(let_chains)] +#![feature(proc_macro_span)] +#![allow(dead_code)] + +extern crate proc_macro; + +use core::mem; +use proc_macro::{ + token_stream::IntoIter, + Delimiter::{self, Brace, Parenthesis}, + Group, Ident, Literal, Punct, + Spacing::{self, Alone, Joint}, + Span, TokenStream, TokenTree as TT, +}; + +type Result = core::result::Result; + +/// Make a `compile_error!` pointing to the given span. +fn make_error(msg: &str, span: Span) -> TokenStream { + TokenStream::from_iter([ + TT::Ident(Ident::new("compile_error", span)), + TT::Punct(punct_with_span('!', Alone, span)), + TT::Group({ + let mut msg = Literal::string(msg); + msg.set_span(span); + group_with_span(Parenthesis, TokenStream::from_iter([TT::Literal(msg)]), span) + }), + ]) +} + +fn expect_tt(tt: Option, f: impl FnOnce(TT) -> Option, expected: &str, span: Span) -> Result { + match tt { + None => Err(make_error( + &format!("unexpected end of input, expected {expected}"), + span, + )), + Some(tt) => { + let span = tt.span(); + match f(tt) { + Some(x) => Ok(x), + None => Err(make_error(&format!("unexpected token, expected {expected}"), span)), + } + }, + } +} + +fn punct_with_span(c: char, spacing: Spacing, span: Span) -> Punct { + let mut p = Punct::new(c, spacing); + p.set_span(span); + p +} + +fn group_with_span(delimiter: Delimiter, stream: TokenStream, span: Span) -> Group { + let mut g = Group::new(delimiter, stream); + g.set_span(span); + g +} + +/// Token used to escape the following token from the macro's span rules. +const ESCAPE_CHAR: char = '$'; + +/// Takes a single token followed by a sequence tokens. Returns the sequence of tokens with their +/// span set to that of the first token. Tokens may be escaped with either `#ident` or `#(tokens)`. +#[proc_macro] +pub fn with_span(input: TokenStream) -> TokenStream { + let mut iter = input.into_iter(); + let span = iter.next().unwrap().span(); + let mut res = TokenStream::new(); + if let Err(e) = write_with_span(span, iter, &mut res) { + e + } else { + res + } +} + +/// Takes a sequence of tokens and return the tokens with the span set such that they appear to be +/// from an external macro. Tokens may be escaped with either `#ident` or `#(tokens)`. +#[proc_macro] +pub fn external(input: TokenStream) -> TokenStream { + let mut res = TokenStream::new(); + if let Err(e) = write_with_span(Span::mixed_site(), input.into_iter(), &mut res) { + e + } else { + res + } +} + +/// Copies all the tokens, replacing all their spans with the given span. Tokens can be escaped +/// either by `#ident` or `#(tokens)`. +fn write_with_span(s: Span, mut input: IntoIter, out: &mut TokenStream) -> Result<()> { + while let Some(tt) = input.next() { + match tt { + TT::Punct(p) if p.as_char() == ESCAPE_CHAR => { + expect_tt( + input.next(), + |tt| match tt { + tt @ (TT::Ident(_) | TT::Literal(_)) => { + out.extend([tt]); + Some(()) + }, + TT::Punct(mut p) if p.as_char() == ESCAPE_CHAR => { + p.set_span(s); + out.extend([TT::Punct(p)]); + Some(()) + }, + TT::Group(g) if g.delimiter() == Parenthesis => { + out.extend([TT::Group(group_with_span(Delimiter::None, g.stream(), g.span()))]); + Some(()) + }, + _ => None, + }, + "an ident, a literal, or parenthesized tokens", + p.span(), + )?; + }, + TT::Group(g) => { + let mut stream = TokenStream::new(); + write_with_span(s, g.stream().into_iter(), &mut stream)?; + out.extend([TT::Group(group_with_span(g.delimiter(), stream, s))]); + }, + mut tt => { + tt.set_span(s); + out.extend([tt]); + }, + } + } + Ok(()) +} + +/// Within the item this attribute is attached to, an `inline!` macro is available which expands the +/// contained tokens as though they came from a macro expansion. +/// +/// Within the `inline!` macro, any token preceded by `$` is passed as though it were an argument +/// with an automatically chosen fragment specifier. `$ident` will be passed as `ident`, `$1` or +/// `$"literal"` will be passed as `literal`, `$'lt` will be passed as `lifetime`, and `$(...)` will +/// pass the contained tokens as a `tt` sequence (the wrapping parenthesis are removed). If another +/// specifier is required it can be specified within parenthesis like `$(@expr ...)`. This will +/// expand the remaining tokens as a single argument. +/// +/// Multiple `inline!` macros may be nested within each other. This will expand as nested macro +/// calls. However, any arguments will be passed as though they came from the outermost context. +#[proc_macro_attribute] +pub fn inline_macros(args: TokenStream, input: TokenStream) -> TokenStream { + let mut args = args.into_iter(); + let mac_name = match args.next() { + Some(TT::Ident(name)) => Some(name), + Some(tt) => { + return make_error( + "unexpected argument, expected either an ident or no arguments", + tt.span(), + ); + }, + None => None, + }; + if let Some(tt) = args.next() { + return make_error( + "unexpected argument, expected either an ident or no arguments", + tt.span(), + ); + }; + + let mac_name = if let Some(mac_name) = mac_name { + Ident::new(&format!("__inline_mac_{mac_name}"), Span::call_site()) + } else { + let mut input = match LookaheadIter::new(input.clone().into_iter()) { + Some(x) => x, + None => return input, + }; + loop { + match input.next() { + None => break Ident::new("__inline_mac", Span::call_site()), + Some(TT::Ident(kind)) => match &*kind.to_string() { + "impl" => break Ident::new("__inline_mac_impl", Span::call_site()), + kind @ ("struct" | "enum" | "union" | "fn" | "mod" | "trait" | "type" | "const" | "static") => { + if let TT::Ident(name) = &input.tt { + break Ident::new(&format!("__inline_mac_{kind}_{name}"), Span::call_site()); + } else { + break Ident::new(&format!("__inline_mac_{kind}"), Span::call_site()); + } + }, + _ => {}, + }, + _ => {}, + } + } + }; + + let mut expander = Expander::default(); + let mut mac = MacWriter::new(mac_name); + if let Err(e) = expander.expand(input.into_iter(), &mut mac) { + return e; + } + let mut out = TokenStream::new(); + mac.finish(&mut out); + out.extend(expander.expn); + out +} + +/// Wraps a `TokenStream` iterator with a single token lookahead. +struct LookaheadIter { + tt: TT, + iter: IntoIter, +} +impl LookaheadIter { + fn new(mut iter: IntoIter) -> Option { + iter.next().map(|tt| Self { tt, iter }) + } + + /// Get's the lookahead token, replacing it with the next token in the stream. + /// Note: If there isn't a next token, this will not return the lookahead token. + fn next(&mut self) -> Option { + self.iter.next().map(|tt| mem::replace(&mut self.tt, tt)) + } +} + +/// Builds the macro used to implement all the `inline!` macro calls. +struct MacWriter { + name: Ident, + macros: TokenStream, + next_idx: usize, +} +impl MacWriter { + fn new(name: Ident) -> Self { + Self { + name, + macros: TokenStream::new(), + next_idx: 0, + } + } + + /// Inserts a new `inline!` call. + fn insert(&mut self, name_span: Span, bang_span: Span, body: Group, expander: &mut Expander) -> Result<()> { + let idx = self.next_idx; + self.next_idx += 1; + + let mut inner = Expander::for_arm(idx); + inner.expand(body.stream().into_iter(), self)?; + let new_arm = inner.arm.unwrap(); + + self.macros.extend([ + TT::Group(Group::new(Parenthesis, new_arm.args_def)), + TT::Punct(Punct::new('=', Joint)), + TT::Punct(Punct::new('>', Alone)), + TT::Group(Group::new(Parenthesis, inner.expn)), + TT::Punct(Punct::new(';', Alone)), + ]); + + expander.expn.extend([ + TT::Ident({ + let mut name = self.name.clone(); + name.set_span(name_span); + name + }), + TT::Punct(punct_with_span('!', Alone, bang_span)), + ]); + let mut call_body = TokenStream::from_iter([TT::Literal(Literal::usize_unsuffixed(idx))]); + if let Some(arm) = expander.arm.as_mut() { + if !new_arm.args.is_empty() { + arm.add_sub_args(new_arm.args, &mut call_body); + } + } else { + call_body.extend(new_arm.args); + } + let mut g = Group::new(body.delimiter(), call_body); + g.set_span(body.span()); + expander.expn.extend([TT::Group(g)]); + Ok(()) + } + + /// Creates the macro definition. + fn finish(self, out: &mut TokenStream) { + if self.next_idx != 0 { + out.extend([ + TT::Ident(Ident::new("macro_rules", Span::call_site())), + TT::Punct(Punct::new('!', Alone)), + TT::Ident(self.name), + TT::Group(Group::new(Brace, self.macros)), + ]) + } + } +} + +struct MacroArm { + args_def: TokenStream, + args: Vec, +} +impl MacroArm { + fn add_single_arg_def(&mut self, kind: &str, dollar_span: Span, arg_span: Span, out: &mut TokenStream) { + let mut name = Ident::new(&format!("_{}", self.args.len()), Span::call_site()); + self.args_def.extend([ + TT::Punct(Punct::new('$', Alone)), + TT::Ident(name.clone()), + TT::Punct(Punct::new(':', Alone)), + TT::Ident(Ident::new(kind, Span::call_site())), + ]); + name.set_span(arg_span); + out.extend([TT::Punct(punct_with_span('$', Alone, dollar_span)), TT::Ident(name)]); + } + + fn add_parenthesized_arg_def(&mut self, kind: Ident, dollar_span: Span, arg_span: Span, out: &mut TokenStream) { + let mut name = Ident::new(&format!("_{}", self.args.len()), Span::call_site()); + self.args_def.extend([TT::Group(Group::new( + Parenthesis, + TokenStream::from_iter([ + TT::Punct(Punct::new('$', Alone)), + TT::Ident(name.clone()), + TT::Punct(Punct::new(':', Alone)), + TT::Ident(kind), + ]), + ))]); + name.set_span(arg_span); + out.extend([TT::Punct(punct_with_span('$', Alone, dollar_span)), TT::Ident(name)]); + } + + fn add_multi_arg_def(&mut self, dollar_span: Span, arg_span: Span, out: &mut TokenStream) { + let mut name = Ident::new(&format!("_{}", self.args.len()), Span::call_site()); + self.args_def.extend([TT::Group(Group::new( + Parenthesis, + TokenStream::from_iter([ + TT::Punct(Punct::new('$', Alone)), + TT::Group(Group::new( + Parenthesis, + TokenStream::from_iter([ + TT::Punct(Punct::new('$', Alone)), + TT::Ident(name.clone()), + TT::Punct(Punct::new(':', Alone)), + TT::Ident(Ident::new("tt", Span::call_site())), + ]), + )), + TT::Punct(Punct::new('*', Alone)), + ]), + ))]); + name.set_span(arg_span); + out.extend([ + TT::Punct(punct_with_span('$', Alone, dollar_span)), + TT::Group(group_with_span( + Parenthesis, + TokenStream::from_iter([TT::Punct(punct_with_span('$', Alone, dollar_span)), TT::Ident(name)]), + dollar_span, + )), + TT::Punct(punct_with_span('*', Alone, dollar_span)), + ]); + } + + fn add_arg(&mut self, dollar_span: Span, tt: TT, input: &mut IntoIter, out: &mut TokenStream) -> Result<()> { + match tt { + TT::Punct(p) if p.as_char() == ESCAPE_CHAR => out.extend([TT::Punct(p)]), + TT::Punct(p) if p.as_char() == '\'' && p.spacing() == Joint => { + let lt_name = expect_tt( + input.next(), + |tt| match tt { + TT::Ident(x) => Some(x), + _ => None, + }, + "lifetime name", + p.span(), + )?; + let arg_span = p.span().join(lt_name.span()).unwrap_or(p.span()); + self.add_single_arg_def("lifetime", dollar_span, arg_span, out); + self.args.extend([TT::Punct(p), TT::Ident(lt_name)]); + }, + TT::Ident(x) => { + self.add_single_arg_def("ident", dollar_span, x.span(), out); + self.args.push(TT::Ident(x)); + }, + TT::Literal(x) => { + self.add_single_arg_def("literal", dollar_span, x.span(), out); + self.args.push(TT::Literal(x)); + }, + TT::Group(g) if g.delimiter() == Parenthesis => { + let mut inner = g.stream().into_iter(); + if let Some(TT::Punct(p)) = inner.next() + && p.as_char() == '@' + { + let kind = expect_tt( + inner.next(), + |tt| match tt { + TT::Ident(kind) => Some(kind), + _ => None, + }, + "a macro fragment specifier", + p.span(), + )?; + self.add_parenthesized_arg_def(kind, dollar_span, g.span(), out); + self.args.push(TT::Group(group_with_span(Parenthesis, inner.collect(), g.span()))) + } else { + self.add_multi_arg_def(dollar_span, g.span(), out); + self.args.push(TT::Group(g)); + } + }, + tt => return Err(make_error("unsupported escape", tt.span())), + }; + Ok(()) + } + + fn add_sub_args(&mut self, args: Vec, out: &mut TokenStream) { + self.add_multi_arg_def(Span::call_site(), Span::call_site(), out); + self.args + .extend([TT::Group(Group::new(Parenthesis, TokenStream::from_iter(args)))]); + } +} + +#[derive(Default)] +struct Expander { + arm: Option, + expn: TokenStream, +} +impl Expander { + fn for_arm(idx: usize) -> Self { + Self { + arm: Some(MacroArm { + args_def: TokenStream::from_iter([TT::Literal(Literal::usize_unsuffixed(idx))]), + args: Vec::new(), + }), + expn: TokenStream::new(), + } + } + + fn write_tt(&mut self, tt: TT, mac: &mut MacWriter) -> Result<()> { + match tt { + TT::Group(g) => { + let outer = mem::take(&mut self.expn); + self.expand(g.stream().into_iter(), mac)?; + let inner = mem::replace(&mut self.expn, outer); + self.expn + .extend([TT::Group(group_with_span(g.delimiter(), inner, g.span()))]); + }, + tt => self.expn.extend([tt]), + } + Ok(()) + } + + fn expand(&mut self, input: IntoIter, mac: &mut MacWriter) -> Result<()> { + let Some(mut input) = LookaheadIter::new(input) else { + return Ok(()); + }; + while let Some(tt) = input.next() { + if let TT::Punct(p) = &tt + && p.as_char() == ESCAPE_CHAR + && let Some(arm) = self.arm.as_mut() + { + arm.add_arg(p.span(), mem::replace(&mut input.tt, tt), &mut input.iter, &mut self.expn)?; + if input.next().is_none() { + return Ok(()); + } + } else if let TT::Punct(p) = &input.tt + && p.as_char() == '!' + && let TT::Ident(name) = &tt + && name.to_string() == "inline" + { + let g = expect_tt( + input.iter.next(), + |tt| match tt { + TT::Group(g) => Some(g), + _ => None, + }, + "macro arguments", + p.span(), + )?; + mac.insert(name.span(), p.span(), g, self)?; + if input.next().is_none() { + return Ok(()); + } + } else { + self.write_tt(tt, mac)?; + } + } + self.write_tt(input.tt, mac) + } +} diff --git a/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs b/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs index f13733af3d0d1..b03c21262c3bd 100644 --- a/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs +++ b/tests/ui/borrow_interior_mutable_const/auxiliary/helper.rs @@ -1,5 +1,5 @@ // this file solely exists to test constants defined in foreign crates. -// As the most common case is the `http` crate, it replicates `http::HeadewrName`'s structure. +// As the most common case is the `http` crate, it replicates `http::HeaderName`'s structure. #![allow(clippy::declare_interior_mutable_const)] #![allow(unused_tuple_struct_fields)] diff --git a/tests/ui/crashes/ice-10148.rs b/tests/ui/crashes/ice-10148.rs index af33b10c69388..1ab3570c907ce 100644 --- a/tests/ui/crashes/ice-10148.rs +++ b/tests/ui/crashes/ice-10148.rs @@ -1,8 +1,8 @@ -// aux-build:../../auxiliary/proc_macro_with_span.rs +// aux-build:../../auxiliary/proc_macros.rs -extern crate proc_macro_with_span; +extern crate proc_macros; -use proc_macro_with_span::with_span; +use proc_macros::with_span; fn main() { println!(with_span!(""something "")); diff --git a/tests/ui/default_numeric_fallback_f64.fixed b/tests/ui/default_numeric_fallback_f64.fixed index a9e5fd159afca..42c15d6a70b83 100644 --- a/tests/ui/default_numeric_fallback_f64.fixed +++ b/tests/ui/default_numeric_fallback_f64.fixed @@ -1,5 +1,5 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::default_numeric_fallback)] #![allow( @@ -13,8 +13,8 @@ clippy::let_with_type_underscore )] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; mod basic_expr { fn test() { @@ -167,20 +167,17 @@ mod method_calls { } mod in_macro { - macro_rules! internal_macro { - () => { - let x = 22.0_f64; - }; - } + use super::*; // Should lint in internal macro. + #[inline_macros] fn internal() { - internal_macro!(); + inline!(let x = 22.0_f64;); } // Should NOT lint in external macro. fn external() { - default_numeric_fallback!(); + external!(let x = 22.;); } } diff --git a/tests/ui/default_numeric_fallback_f64.rs b/tests/ui/default_numeric_fallback_f64.rs index 085f8f452b2e6..7da7ea254e98b 100644 --- a/tests/ui/default_numeric_fallback_f64.rs +++ b/tests/ui/default_numeric_fallback_f64.rs @@ -1,5 +1,5 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::default_numeric_fallback)] #![allow( @@ -13,8 +13,8 @@ clippy::let_with_type_underscore )] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; mod basic_expr { fn test() { @@ -167,20 +167,17 @@ mod method_calls { } mod in_macro { - macro_rules! internal_macro { - () => { - let x = 22.; - }; - } + use super::*; // Should lint in internal macro. + #[inline_macros] fn internal() { - internal_macro!(); + inline!(let x = 22.;); } // Should NOT lint in external macro. fn external() { - default_numeric_fallback!(); + external!(let x = 22.;); } } diff --git a/tests/ui/default_numeric_fallback_f64.stderr b/tests/ui/default_numeric_fallback_f64.stderr index 44c6f1a9bea9b..b949cd1d50b18 100644 --- a/tests/ui/default_numeric_fallback_f64.stderr +++ b/tests/ui/default_numeric_fallback_f64.stderr @@ -139,15 +139,12 @@ LL | s.generic_arg(1.); | ^^ help: consider adding suffix: `1.0_f64` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_f64.rs:172:21 + --> $DIR/default_numeric_fallback_f64.rs:175:25 | -LL | let x = 22.; - | ^^^ help: consider adding suffix: `22.0_f64` -... -LL | internal_macro!(); - | ----------------- in this macro invocation +LL | inline!(let x = 22.;); + | ^^^ help: consider adding suffix: `22.0_f64` | - = note: this error originates in the macro `internal_macro` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_internal` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 24 previous errors diff --git a/tests/ui/default_numeric_fallback_i32.fixed b/tests/ui/default_numeric_fallback_i32.fixed index 63ac4d5aeb6f3..b7485b73dcddd 100644 --- a/tests/ui/default_numeric_fallback_i32.fixed +++ b/tests/ui/default_numeric_fallback_i32.fixed @@ -1,5 +1,5 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![feature(lint_reasons)] #![warn(clippy::default_numeric_fallback)] @@ -13,8 +13,8 @@ clippy::let_with_type_underscore )] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; mod basic_expr { fn test() { @@ -168,20 +168,17 @@ mod method_calls { } mod in_macro { - macro_rules! internal_macro { - () => { - let x = 22_i32; - }; - } + use super::*; // Should lint in internal macro. + #[inline_macros] fn internal() { - internal_macro!(); + inline!(let x = 22_i32;); } // Should NOT lint in external macro. fn external() { - default_numeric_fallback!(); + external!(let x = 22;); } } diff --git a/tests/ui/default_numeric_fallback_i32.rs b/tests/ui/default_numeric_fallback_i32.rs index 28e6eceb80e2e..7307d31354ebd 100644 --- a/tests/ui/default_numeric_fallback_i32.rs +++ b/tests/ui/default_numeric_fallback_i32.rs @@ -1,5 +1,5 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![feature(lint_reasons)] #![warn(clippy::default_numeric_fallback)] @@ -13,8 +13,8 @@ clippy::let_with_type_underscore )] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; mod basic_expr { fn test() { @@ -168,20 +168,17 @@ mod method_calls { } mod in_macro { - macro_rules! internal_macro { - () => { - let x = 22; - }; - } + use super::*; // Should lint in internal macro. + #[inline_macros] fn internal() { - internal_macro!(); + inline!(let x = 22;); } // Should NOT lint in external macro. fn external() { - default_numeric_fallback!(); + external!(let x = 22;); } } diff --git a/tests/ui/default_numeric_fallback_i32.stderr b/tests/ui/default_numeric_fallback_i32.stderr index dd91574d5b347..48cd28102ce4b 100644 --- a/tests/ui/default_numeric_fallback_i32.stderr +++ b/tests/ui/default_numeric_fallback_i32.stderr @@ -151,15 +151,12 @@ LL | s.generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:173:21 + --> $DIR/default_numeric_fallback_i32.rs:176:25 | -LL | let x = 22; - | ^^ help: consider adding suffix: `22_i32` -... -LL | internal_macro!(); - | ----------------- in this macro invocation +LL | inline!(let x = 22;); + | ^^ help: consider adding suffix: `22_i32` | - = note: this error originates in the macro `internal_macro` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_internal` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 26 previous errors diff --git a/tests/ui/default_trait_access.fixed b/tests/ui/default_trait_access.fixed index 5640599d48ae8..7842ef3ec40c2 100644 --- a/tests/ui/default_trait_access.fixed +++ b/tests/ui/default_trait_access.fixed @@ -1,12 +1,12 @@ // run-rustfix -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![deny(clippy::default_trait_access)] #![allow(dead_code, unused_imports)] #![allow(clippy::uninlined_format_args)] -extern crate proc_macro_with_span; +extern crate proc_macros; -use proc_macro_with_span::with_span; +use proc_macros::with_span; use std::default; use std::default::Default as D2; use std::string; diff --git a/tests/ui/default_trait_access.rs b/tests/ui/default_trait_access.rs index 11d4bc5c5f022..cbb3e59c970eb 100644 --- a/tests/ui/default_trait_access.rs +++ b/tests/ui/default_trait_access.rs @@ -1,12 +1,12 @@ // run-rustfix -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![deny(clippy::default_trait_access)] #![allow(dead_code, unused_imports)] #![allow(clippy::uninlined_format_args)] -extern crate proc_macro_with_span; +extern crate proc_macros; -use proc_macro_with_span::with_span; +use proc_macros::with_span; use std::default; use std::default::Default as D2; use std::string; diff --git a/tests/ui/deref_addrof.fixed b/tests/ui/deref_addrof.fixed index 2f489deb1ee1f..ca5c03304c7f2 100644 --- a/tests/ui/deref_addrof.fixed +++ b/tests/ui/deref_addrof.fixed @@ -1,7 +1,12 @@ // run-rustfix +// aux-build:proc_macros.rs + #![allow(clippy::return_self_not_must_use)] #![warn(clippy::deref_addrof)] +extern crate proc_macros; +use proc_macros::inline_macros; + fn get_number() -> usize { 10 } @@ -41,28 +46,15 @@ fn main() { let _ = unsafe { *core::ptr::addr_of!(a) }; } -#[rustfmt::skip] -macro_rules! m { - ($visitor: expr) => { - $visitor - }; -} - -#[rustfmt::skip] -macro_rules! m_mut { - ($visitor: expr) => { - $visitor - }; -} - #[derive(Copy, Clone)] pub struct S; +#[inline_macros] impl S { pub fn f(&self) -> &Self { - m!(self) + inline!($(@expr self)) } #[allow(unused_mut)] // mut will be unused, once the macro is fixed pub fn f_mut(mut self) -> Self { - m_mut!(self) + inline!($(@expr self)) } } diff --git a/tests/ui/deref_addrof.rs b/tests/ui/deref_addrof.rs index 49f360b9a7f9e..3db5fafe94499 100644 --- a/tests/ui/deref_addrof.rs +++ b/tests/ui/deref_addrof.rs @@ -1,7 +1,12 @@ // run-rustfix +// aux-build:proc_macros.rs + #![allow(clippy::return_self_not_must_use)] #![warn(clippy::deref_addrof)] +extern crate proc_macros; +use proc_macros::inline_macros; + fn get_number() -> usize { 10 } @@ -41,28 +46,15 @@ fn main() { let _ = unsafe { *core::ptr::addr_of!(a) }; } -#[rustfmt::skip] -macro_rules! m { - ($visitor: expr) => { - *& $visitor - }; -} - -#[rustfmt::skip] -macro_rules! m_mut { - ($visitor: expr) => { - *& mut $visitor - }; -} - #[derive(Copy, Clone)] pub struct S; +#[inline_macros] impl S { pub fn f(&self) -> &Self { - m!(self) + inline!(*& $(@expr self)) } #[allow(unused_mut)] // mut will be unused, once the macro is fixed pub fn f_mut(mut self) -> Self { - m_mut!(self) + inline!(*&mut $(@expr self)) } } diff --git a/tests/ui/deref_addrof.stderr b/tests/ui/deref_addrof.stderr index 75371fcdb9677..e0287522fc56f 100644 --- a/tests/ui/deref_addrof.stderr +++ b/tests/ui/deref_addrof.stderr @@ -1,5 +1,5 @@ error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:19:13 + --> $DIR/deref_addrof.rs:24:13 | LL | let b = *&a; | ^^^ help: try this: `a` @@ -7,68 +7,62 @@ LL | let b = *&a; = note: `-D clippy::deref-addrof` implied by `-D warnings` error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:21:13 + --> $DIR/deref_addrof.rs:26:13 | LL | let b = *&get_number(); | ^^^^^^^^^^^^^^ help: try this: `get_number()` error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:26:13 + --> $DIR/deref_addrof.rs:31:13 | LL | let b = *&bytes[1..2][0]; | ^^^^^^^^^^^^^^^^ help: try this: `bytes[1..2][0]` error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:30:13 + --> $DIR/deref_addrof.rs:35:13 | LL | let b = *&(a); | ^^^^^ help: try this: `(a)` error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:32:13 + --> $DIR/deref_addrof.rs:37:13 | LL | let b = *(&a); | ^^^^^ help: try this: `a` error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:35:13 + --> $DIR/deref_addrof.rs:40:13 | LL | let b = *((&a)); | ^^^^^^^ help: try this: `a` error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:37:13 + --> $DIR/deref_addrof.rs:42:13 | LL | let b = *&&a; | ^^^^ help: try this: `&a` error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:39:14 + --> $DIR/deref_addrof.rs:44:14 | LL | let b = **&aref; | ^^^^^^ help: try this: `aref` error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:47:9 + --> $DIR/deref_addrof.rs:54:17 | -LL | *& $visitor - | ^^^^^^^^^^^ help: try this: `$visitor` -... -LL | m!(self) - | -------- in this macro invocation +LL | inline!(*& $(@expr self)) + | ^^^^^^^^^^^^^^^^ help: try this: `$(@expr self)` | - = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_impl` (in Nightly builds, run with -Z macro-backtrace for more info) error: immediately dereferencing a reference - --> $DIR/deref_addrof.rs:54:9 + --> $DIR/deref_addrof.rs:58:17 | -LL | *& mut $visitor - | ^^^^^^^^^^^^^^^ help: try this: `$visitor` -... -LL | m_mut!(self) - | ------------ in this macro invocation +LL | inline!(*&mut $(@expr self)) + | ^^^^^^^^^^^^^^^^^^^ help: try this: `$(@expr self)` | - = note: this error originates in the macro `m_mut` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_impl` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 10 previous errors diff --git a/tests/ui/deref_addrof_macro.rs b/tests/ui/deref_addrof_macro.rs index dcebd6c6e29c8..57c0be3f51e1b 100644 --- a/tests/ui/deref_addrof_macro.rs +++ b/tests/ui/deref_addrof_macro.rs @@ -1,10 +1,13 @@ -macro_rules! m { - ($($x:tt),*) => { &[$(($x, stringify!(x)),)*] }; -} +// aux-build:proc_macros.rs + +#![warn(clippy::deref_addrof)] + +extern crate proc_macros; -#[warn(clippy::deref_addrof)] -fn f() -> [(i32, &'static str); 3] { - *m![1, 2, 3] // should be fine +#[proc_macros::inline_macros] +fn f() -> i32 { + // should be fine + *inline!(&$1) } fn main() {} diff --git a/tests/ui/doc_unsafe.rs b/tests/ui/doc_unsafe.rs index b91f7aa0dd8d2..30674ce3708e7 100644 --- a/tests/ui/doc_unsafe.rs +++ b/tests/ui/doc_unsafe.rs @@ -1,9 +1,9 @@ -// aux-build:doc_unsafe_macros.rs +// aux-build:proc_macros.rs #![allow(clippy::let_unit_value)] -#[macro_use] -extern crate doc_unsafe_macros; +extern crate proc_macros; +use proc_macros::external; /// This is not sufficiently documented pub unsafe fn destroy_the_planet() { @@ -105,7 +105,11 @@ macro_rules! very_unsafe { very_unsafe!(); // we don't lint code from external macros -undocd_unsafe!(); +external! { + pub unsafe fn oy_vey() { + unimplemented!(); + } +} fn main() { unsafe { diff --git a/tests/ui/empty_loop.rs b/tests/ui/empty_loop.rs index 8fd7697eb3b29..6a8e6b550c13f 100644 --- a/tests/ui/empty_loop.rs +++ b/tests/ui/empty_loop.rs @@ -1,9 +1,9 @@ -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::empty_loop)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; fn should_trigger() { loop {} @@ -16,6 +16,7 @@ fn should_trigger() { } } +#[inline_macros] fn should_not_trigger() { loop { panic!("This is fine") @@ -38,14 +39,10 @@ fn should_not_trigger() { loop {} // We don't lint loops inside macros - macro_rules! foo { - () => { - loop {} - }; - } + inline!(loop {}); // We don't lint external macros - foofoo!() + external!(loop {}); } fn main() {} diff --git a/tests/ui/equatable_if_let.fixed b/tests/ui/equatable_if_let.fixed index 9af2ba9627200..007702ab550bf 100644 --- a/tests/ui/equatable_if_let.fixed +++ b/tests/ui/equatable_if_let.fixed @@ -1,11 +1,11 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)] #![warn(clippy::equatable_if_let)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; use std::cmp::Ordering; @@ -44,6 +44,7 @@ impl PartialEq for NotStructuralEq { } } +#[inline_macros] fn main() { let a = 2; let b = 3; @@ -78,14 +79,9 @@ fn main() { if Some(g) == Some(NotStructuralEq::A) {} if matches!(h, NoPartialEqStruct { a: 2, b: false }) {} - macro_rules! m1 { - (x) => { - "abc" - }; - } - if "abc" == m1!(x) { + if "abc" == inline!("abc") { println!("OK"); } - equatable_if_let!(a); + external!({ if let 2 = $a {} }); } diff --git a/tests/ui/equatable_if_let.rs b/tests/ui/equatable_if_let.rs index c3626c081dd5e..3bda7977645b3 100644 --- a/tests/ui/equatable_if_let.rs +++ b/tests/ui/equatable_if_let.rs @@ -1,11 +1,11 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)] #![warn(clippy::equatable_if_let)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; use std::cmp::Ordering; @@ -44,6 +44,7 @@ impl PartialEq for NotStructuralEq { } } +#[inline_macros] fn main() { let a = 2; let b = 3; @@ -78,14 +79,9 @@ fn main() { if let Some(NotStructuralEq::A) = Some(g) {} if let NoPartialEqStruct { a: 2, b: false } = h {} - macro_rules! m1 { - (x) => { - "abc" - }; - } - if let m1!(x) = "abc" { + if let inline!("abc") = "abc" { println!("OK"); } - equatable_if_let!(a); + external!({ if let 2 = $a {} }); } diff --git a/tests/ui/equatable_if_let.stderr b/tests/ui/equatable_if_let.stderr index 40ca75b8da22c..a72d87bb7ba96 100644 --- a/tests/ui/equatable_if_let.stderr +++ b/tests/ui/equatable_if_let.stderr @@ -1,5 +1,5 @@ error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:59:8 + --> $DIR/equatable_if_let.rs:60:8 | LL | if let 2 = a {} | ^^^^^^^^^ help: try: `a == 2` @@ -7,82 +7,82 @@ LL | if let 2 = a {} = note: `-D clippy::equatable-if-let` implied by `-D warnings` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:60:8 + --> $DIR/equatable_if_let.rs:61:8 | LL | if let Ordering::Greater = a.cmp(&b) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.cmp(&b) == Ordering::Greater` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:61:8 + --> $DIR/equatable_if_let.rs:62:8 | LL | if let Some(2) = c {} | ^^^^^^^^^^^^^^^ help: try: `c == Some(2)` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:62:8 + --> $DIR/equatable_if_let.rs:63:8 | LL | if let Struct { a: 2, b: false } = d {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `d == (Struct { a: 2, b: false })` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:63:8 + --> $DIR/equatable_if_let.rs:64:8 | LL | if let Enum::TupleVariant(32, 64) = e {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == Enum::TupleVariant(32, 64)` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:64:8 + --> $DIR/equatable_if_let.rs:65:8 | LL | if let Enum::RecordVariant { a: 64, b: 32 } = e {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == (Enum::RecordVariant { a: 64, b: 32 })` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:65:8 + --> $DIR/equatable_if_let.rs:66:8 | LL | if let Enum::UnitVariant = e {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == Enum::UnitVariant` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:66:8 + --> $DIR/equatable_if_let.rs:67:8 | LL | if let (Enum::UnitVariant, &Struct { a: 2, b: false }) = (e, &d) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(e, &d) == (Enum::UnitVariant, &Struct { a: 2, b: false })` error: this pattern matching can be expressed using `matches!` - --> $DIR/equatable_if_let.rs:75:8 + --> $DIR/equatable_if_let.rs:76:8 | LL | if let NotPartialEq::A = f {} | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(f, NotPartialEq::A)` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:76:8 + --> $DIR/equatable_if_let.rs:77:8 | LL | if let NotStructuralEq::A = g {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `g == NotStructuralEq::A` error: this pattern matching can be expressed using `matches!` - --> $DIR/equatable_if_let.rs:77:8 + --> $DIR/equatable_if_let.rs:78:8 | LL | if let Some(NotPartialEq::A) = Some(f) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(Some(f), Some(NotPartialEq::A))` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:78:8 + --> $DIR/equatable_if_let.rs:79:8 | LL | if let Some(NotStructuralEq::A) = Some(g) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(g) == Some(NotStructuralEq::A)` error: this pattern matching can be expressed using `matches!` - --> $DIR/equatable_if_let.rs:79:8 + --> $DIR/equatable_if_let.rs:80:8 | LL | if let NoPartialEqStruct { a: 2, b: false } = h {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(h, NoPartialEqStruct { a: 2, b: false })` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:86:8 + --> $DIR/equatable_if_let.rs:82:8 | -LL | if let m1!(x) = "abc" { - | ^^^^^^^^^^^^^^^^^^ help: try: `"abc" == m1!(x)` +LL | if let inline!("abc") = "abc" { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"abc" == inline!("abc")` error: aborting due to 14 previous errors diff --git a/tests/ui/field_reassign_with_default.rs b/tests/ui/field_reassign_with_default.rs index 1f989bb122052..0e208b3ed0e15 100644 --- a/tests/ui/field_reassign_with_default.rs +++ b/tests/ui/field_reassign_with_default.rs @@ -1,12 +1,12 @@ // aux-build:proc_macro_derive.rs -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::field_reassign_with_default)] #[macro_use] extern crate proc_macro_derive; -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; // Don't lint on derives that derive `Default` // See https://github.com/rust-lang/rust-clippy/issues/6545 @@ -36,14 +36,6 @@ struct D { b: Option, } -macro_rules! m { - ($key:ident: $value:tt) => {{ - let mut data = $crate::D::default(); - data.$key = Some($value); - data - }}; -} - /// Implements .next() that returns a different number each time. struct SideEffect(i32); @@ -57,6 +49,7 @@ impl SideEffect { } } +#[inline_macros] fn main() { // wrong, produces first error in stderr let mut a: A = Default::default(); @@ -150,7 +143,18 @@ fn main() { a.i = vec![1]; // Don't lint in external macros - field_reassign_with_default!(); + external! { + #[derive(Default)] + struct A { + pub i: i32, + pub j: i64, + } + fn lint() { + let mut a: A = Default::default(); + a.i = 42; + a; + } + } // be sure suggestion is correct with generics let mut a: Wrapper = Default::default(); @@ -160,9 +164,11 @@ fn main() { a.i = 42; // Don't lint in macros - m! { - a: 42 - }; + inline!( + let mut data = $crate::D::default(); + data.$a = Some($42); + data + ); } mod m { diff --git a/tests/ui/field_reassign_with_default.stderr b/tests/ui/field_reassign_with_default.stderr index 710bb66a48a40..da74f9ef9f7ee 100644 --- a/tests/ui/field_reassign_with_default.stderr +++ b/tests/ui/field_reassign_with_default.stderr @@ -1,132 +1,132 @@ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:63:5 + --> $DIR/field_reassign_with_default.rs:56:5 | LL | a.i = 42; | ^^^^^^^^^ | note: consider initializing the variable with `main::A { i: 42, ..Default::default() }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:62:5 + --> $DIR/field_reassign_with_default.rs:55:5 | LL | let mut a: A = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: `-D clippy::field-reassign-with-default` implied by `-D warnings` error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:103:5 + --> $DIR/field_reassign_with_default.rs:96:5 | LL | a.j = 43; | ^^^^^^^^^ | note: consider initializing the variable with `main::A { j: 43, i: 42 }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:102:5 + --> $DIR/field_reassign_with_default.rs:95:5 | LL | let mut a: A = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:108:5 + --> $DIR/field_reassign_with_default.rs:101:5 | LL | a.i = 42; | ^^^^^^^^^ | note: consider initializing the variable with `main::A { i: 42, j: 44 }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:107:5 + --> $DIR/field_reassign_with_default.rs:100:5 | LL | let mut a: A = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:114:5 + --> $DIR/field_reassign_with_default.rs:107:5 | LL | a.i = 42; | ^^^^^^^^^ | note: consider initializing the variable with `main::A { i: 42, ..Default::default() }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:113:5 + --> $DIR/field_reassign_with_default.rs:106:5 | LL | let mut a = A::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:124:5 + --> $DIR/field_reassign_with_default.rs:117:5 | LL | a.i = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: consider initializing the variable with `main::A { i: Default::default(), ..Default::default() }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:123:5 + --> $DIR/field_reassign_with_default.rs:116:5 | LL | let mut a: A = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:128:5 + --> $DIR/field_reassign_with_default.rs:121:5 | LL | a.i = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: consider initializing the variable with `main::A { i: Default::default(), j: 45 }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:127:5 + --> $DIR/field_reassign_with_default.rs:120:5 | LL | let mut a: A = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:150:5 + --> $DIR/field_reassign_with_default.rs:143:5 | LL | a.i = vec![1]; | ^^^^^^^^^^^^^^ | note: consider initializing the variable with `C { i: vec![1], ..Default::default() }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:149:5 + --> $DIR/field_reassign_with_default.rs:142:5 | LL | let mut a: C = C::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:157:5 + --> $DIR/field_reassign_with_default.rs:161:5 | LL | a.i = true; | ^^^^^^^^^^^ | note: consider initializing the variable with `Wrapper:: { i: true }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:156:5 + --> $DIR/field_reassign_with_default.rs:160:5 | LL | let mut a: Wrapper = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:160:5 + --> $DIR/field_reassign_with_default.rs:164:5 | LL | a.i = 42; | ^^^^^^^^^ | note: consider initializing the variable with `WrapperMulti:: { i: 42, ..Default::default() }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:159:5 + --> $DIR/field_reassign_with_default.rs:163:5 | LL | let mut a: WrapperMulti = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:229:13 + --> $DIR/field_reassign_with_default.rs:235:13 | LL | f.name = name.len(); | ^^^^^^^^^^^^^^^^^^^^ | note: consider initializing the variable with `issue6312::ImplDropAllCopy { name: name.len(), ..Default::default() }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:228:13 + --> $DIR/field_reassign_with_default.rs:234:13 | LL | let mut f = ImplDropAllCopy::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: field assignment outside of initializer for an instance created with Default::default() - --> $DIR/field_reassign_with_default.rs:245:13 + --> $DIR/field_reassign_with_default.rs:251:13 | LL | f.name = name.len(); | ^^^^^^^^^^^^^^^^^^^^ | note: consider initializing the variable with `issue6312::NoDropAllCopy { name: name.len(), ..Default::default() }` and removing relevant reassignments - --> $DIR/field_reassign_with_default.rs:244:13 + --> $DIR/field_reassign_with_default.rs:250:13 | LL | let mut f = NoDropAllCopy::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/ifs_same_cond.rs b/tests/ui/ifs_same_cond.rs index 9850fc0919e12..9ce9a87626a79 100644 --- a/tests/ui/ifs_same_cond.rs +++ b/tests/ui/ifs_same_cond.rs @@ -43,4 +43,30 @@ fn ifs_same_cond() { } } +fn issue10272() { + let a = String::from("ha"); + if a.contains("ah") { + } else if a.contains("ah") { + // Trigger this lint + } else if a.contains("ha") { + } else if a == "wow" { + } + + let p: *mut i8 = std::ptr::null_mut(); + if p.is_null() { + } else if p.align_offset(0) == 0 { + } else if p.is_null() { + // ok, p is mutable pointer + } else { + } + + let x = std::cell::Cell::new(true); + if x.get() { + } else if !x.take() { + } else if x.get() { + // ok, x is interior mutable type + } else { + } +} + fn main() {} diff --git a/tests/ui/ifs_same_cond.stderr b/tests/ui/ifs_same_cond.stderr index 4113087327a28..9519f6904cb1e 100644 --- a/tests/ui/ifs_same_cond.stderr +++ b/tests/ui/ifs_same_cond.stderr @@ -35,5 +35,17 @@ note: same as this LL | if 2 * a == 1 { | ^^^^^^^^^^ -error: aborting due to 3 previous errors +error: this `if` has the same condition as a previous `if` + --> $DIR/ifs_same_cond.rs:49:15 + | +LL | } else if a.contains("ah") { + | ^^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/ifs_same_cond.rs:48:8 + | +LL | if a.contains("ah") { + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors diff --git a/tests/ui/implicit_hasher.rs b/tests/ui/implicit_hasher.rs index fd96ca3f466ea..35d08a07bc3fe 100644 --- a/tests/ui/implicit_hasher.rs +++ b/tests/ui/implicit_hasher.rs @@ -1,9 +1,11 @@ -// aux-build:implicit_hasher_macros.rs +// aux-build:proc_macros.rs + #![deny(clippy::implicit_hasher)] #![allow(unused)] #[macro_use] -extern crate implicit_hasher_macros; +extern crate proc_macros; +use proc_macros::external; use std::cmp::Eq; use std::collections::{HashMap, HashSet}; @@ -68,22 +70,19 @@ impl Foo for HashSet { pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} -macro_rules! gen { - (impl) => { +#[proc_macros::inline_macros] +pub mod gen { + use super::*; + inline! { impl Foo for HashMap { fn make() -> (Self, Self) { (HashMap::new(), HashMap::with_capacity(10)) } } - }; - (fn $name:ident) => { - pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} - }; + pub fn bar(_map: &mut HashMap, _set: &mut HashSet) {} + } } -#[rustfmt::skip] -gen!(impl); -gen!(fn bar); // When the macro is in a different file, the suggestion spans can't be combined properly // and should not cause an ICE @@ -94,7 +93,9 @@ pub mod test_macro; __implicit_hasher_test_macro!(impl for HashMap where V: test_macro::A); // #4260 -implicit_hasher_fn!(); +external! { + pub fn f(input: &HashMap) {} +} // #7712 pub async fn election_vote(_data: HashMap) {} diff --git a/tests/ui/implicit_hasher.stderr b/tests/ui/implicit_hasher.stderr index 59b0fba2a4cfe..83b46de2eb5e3 100644 --- a/tests/ui/implicit_hasher.stderr +++ b/tests/ui/implicit_hasher.stderr @@ -1,11 +1,11 @@ error: impl for `HashMap` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:16:35 + --> $DIR/implicit_hasher.rs:18:35 | LL | impl Foo for HashMap { | ^^^^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/implicit_hasher.rs:2:9 + --> $DIR/implicit_hasher.rs:3:9 | LL | #![deny(clippy::implicit_hasher)] | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -19,7 +19,7 @@ LL | (HashMap::default(), HashMap::with_capacity_and_hasher(10, Default: | ~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: impl for `HashMap` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:25:36 + --> $DIR/implicit_hasher.rs:27:36 | LL | impl Foo for (HashMap,) { | ^^^^^^^^^^^^^ @@ -34,7 +34,7 @@ LL | ((HashMap::default(),), (HashMap::with_capacity_and_hasher(10, Defa | ~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: impl for `HashMap` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:30:19 + --> $DIR/implicit_hasher.rs:32:19 | LL | impl Foo for HashMap { | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL | (HashMap::default(), HashMap::with_capacity_and_hasher(10, Default: | ~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: impl for `HashSet` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:47:32 + --> $DIR/implicit_hasher.rs:49:32 | LL | impl Foo for HashSet { | ^^^^^^^^^^ @@ -64,7 +64,7 @@ LL | (HashSet::default(), HashSet::with_capacity_and_hasher(10, Default: | ~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: impl for `HashSet` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:52:19 + --> $DIR/implicit_hasher.rs:54:19 | LL | impl Foo for HashSet { | ^^^^^^^^^^^^^^^ @@ -79,7 +79,7 @@ LL | (HashSet::default(), HashSet::with_capacity_and_hasher(10, Default: | ~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: parameter of type `HashMap` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:69:23 + --> $DIR/implicit_hasher.rs:71:23 | LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} | ^^^^^^^^^^^^^^^^^ @@ -90,7 +90,7 @@ LL | pub fn foo(_map: &mut HashMap, _s | +++++++++++++++++++++++++++++ ~~~~~~~~~~~~~~~~~~~~ error: parameter of type `HashSet` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:69:53 + --> $DIR/implicit_hasher.rs:71:53 | LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} | ^^^^^^^^^^^^ @@ -101,15 +101,12 @@ LL | pub fn foo(_map: &mut HashMap, _set: | +++++++++++++++++++++++++++++ ~~~~~~~~~~~~~~~ error: impl for `HashMap` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:73:43 + --> $DIR/implicit_hasher.rs:77:43 | LL | impl Foo for HashMap { | ^^^^^^^^^^^^^ -... -LL | gen!(impl); - | ---------- in this macro invocation | - = note: this error originates in the macro `gen` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_mod_gen` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider adding a type parameter | LL | impl Foo for HashMap { @@ -120,37 +117,31 @@ LL | (HashMap::default(), HashMap::with_capacity_and_hasher(10, | ~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: parameter of type `HashMap` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:81:33 + --> $DIR/implicit_hasher.rs:83:31 | -LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} - | ^^^^^^^^^^^^^^^^^ -... -LL | gen!(fn bar); - | ------------ in this macro invocation +LL | pub fn bar(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^ | - = note: this error originates in the macro `gen` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_mod_gen` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider adding a type parameter | -LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} - | +++++++++++++++++++++++++++++ ~~~~~~~~~~~~~~~~~~~~ +LL | pub fn bar(_map: &mut HashMap, _set: &mut HashSet) {} + | +++++++++++++++++++++++++++++ ~~~~~~~~~~~~~~~~~~~~ error: parameter of type `HashSet` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:81:63 + --> $DIR/implicit_hasher.rs:83:61 | -LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} - | ^^^^^^^^^^^^ -... -LL | gen!(fn bar); - | ------------ in this macro invocation +LL | pub fn bar(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^ | - = note: this error originates in the macro `gen` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_mod_gen` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider adding a type parameter | -LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} - | +++++++++++++++++++++++++++++ ~~~~~~~~~~~~~~~ +LL | pub fn bar(_map: &mut HashMap, _set: &mut HashSet) {} + | +++++++++++++++++++++++++++++ ~~~~~~~~~~~~~~~ error: parameter of type `HashMap` should be generalized over different hashers - --> $DIR/implicit_hasher.rs:100:35 + --> $DIR/implicit_hasher.rs:101:35 | LL | pub async fn election_vote(_data: HashMap) {} | ^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/inconsistent_struct_constructor.fixed b/tests/ui/inconsistent_struct_constructor.fixed index 74ba2f1c5e70b..5aaa00f851724 100644 --- a/tests/ui/inconsistent_struct_constructor.fixed +++ b/tests/ui/inconsistent_struct_constructor.fixed @@ -1,10 +1,14 @@ // run-rustfix +// aux-build:proc_macros.rs + #![warn(clippy::inconsistent_struct_constructor)] #![allow(clippy::redundant_field_names)] #![allow(clippy::unnecessary_operation)] #![allow(clippy::no_effect)] #![allow(dead_code)] +extern crate proc_macros; + #[derive(Default)] struct Foo { x: i32, @@ -12,18 +16,10 @@ struct Foo { z: i32, } -macro_rules! new_foo { - () => { - let x = 1; - let y = 1; - let z = 1; - Foo { y, x, z } - }; -} - mod without_base { use super::Foo; + #[proc_macros::inline_macros] fn test() { let x = 1; let y = 1; @@ -34,7 +30,12 @@ mod without_base { // Should NOT lint. // issue #7069. - new_foo!(); + inline!({ + let x = 1; + let y = 1; + let z = 1; + Foo { y, x, z } + }); // Should NOT lint because the order is the same as in the definition. Foo { x, y, z }; diff --git a/tests/ui/inconsistent_struct_constructor.rs b/tests/ui/inconsistent_struct_constructor.rs index ba96e1e330f5f..2b2dd7f59a4da 100644 --- a/tests/ui/inconsistent_struct_constructor.rs +++ b/tests/ui/inconsistent_struct_constructor.rs @@ -1,10 +1,14 @@ // run-rustfix +// aux-build:proc_macros.rs + #![warn(clippy::inconsistent_struct_constructor)] #![allow(clippy::redundant_field_names)] #![allow(clippy::unnecessary_operation)] #![allow(clippy::no_effect)] #![allow(dead_code)] +extern crate proc_macros; + #[derive(Default)] struct Foo { x: i32, @@ -12,18 +16,10 @@ struct Foo { z: i32, } -macro_rules! new_foo { - () => { - let x = 1; - let y = 1; - let z = 1; - Foo { y, x, z } - }; -} - mod without_base { use super::Foo; + #[proc_macros::inline_macros] fn test() { let x = 1; let y = 1; @@ -34,7 +30,12 @@ mod without_base { // Should NOT lint. // issue #7069. - new_foo!(); + inline!({ + let x = 1; + let y = 1; + let z = 1; + Foo { y, x, z } + }); // Should NOT lint because the order is the same as in the definition. Foo { x, y, z }; diff --git a/tests/ui/inconsistent_struct_constructor.stderr b/tests/ui/inconsistent_struct_constructor.stderr index c90189e964f09..785a6dc9d53ad 100644 --- a/tests/ui/inconsistent_struct_constructor.stderr +++ b/tests/ui/inconsistent_struct_constructor.stderr @@ -1,5 +1,5 @@ error: struct constructor field order is inconsistent with struct definition field order - --> $DIR/inconsistent_struct_constructor.rs:33:9 + --> $DIR/inconsistent_struct_constructor.rs:29:9 | LL | Foo { y, x, z }; | ^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z }` @@ -7,7 +7,7 @@ LL | Foo { y, x, z }; = note: `-D clippy::inconsistent-struct-constructor` implied by `-D warnings` error: struct constructor field order is inconsistent with struct definition field order - --> $DIR/inconsistent_struct_constructor.rs:55:9 + --> $DIR/inconsistent_struct_constructor.rs:56:9 | LL | / Foo { LL | | z, diff --git a/tests/ui/large_enum_variant.rs b/tests/ui/large_enum_variant.rs index 3b96f09d7b1d6..f09f8ae0ccc35 100644 --- a/tests/ui/large_enum_variant.rs +++ b/tests/ui/large_enum_variant.rs @@ -1,11 +1,11 @@ -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![allow(dead_code)] #![allow(unused_variables)] #![warn(clippy::large_enum_variant)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::external; enum LargeEnum { A(i32), @@ -155,5 +155,10 @@ enum LargeEnumOfConst { } fn main() { - large_enum_variant!(); + external!( + enum LargeEnumInMacro { + A(i32), + B([i32; 8000]), + } + ); } diff --git a/tests/ui/macro_use_imports.fixed b/tests/ui/macro_use_imports.fixed index e612480d264bd..15f7a099a7de5 100644 --- a/tests/ui/macro_use_imports.fixed +++ b/tests/ui/macro_use_imports.fixed @@ -20,7 +20,7 @@ mod a { use mac; use mini_mac::ClippyMiniMacroTest; use mini_mac; - use mac::{inner::foofoo, inner::try_err}; + use mac::{inner::mut_mut, inner::try_err}; use mac::inner; use mac::inner::nested::string_add; use mac::inner::nested; @@ -36,7 +36,7 @@ mod a { let v: ty_macro!() = Vec::default(); inner::try_err!(); - inner::foofoo!(); + inner::mut_mut!(); nested::string_add!(); } } diff --git a/tests/ui/macro_use_imports.rs b/tests/ui/macro_use_imports.rs index b34817cc3b269..b1a2873329490 100644 --- a/tests/ui/macro_use_imports.rs +++ b/tests/ui/macro_use_imports.rs @@ -36,7 +36,7 @@ mod a { let v: ty_macro!() = Vec::default(); inner::try_err!(); - inner::foofoo!(); + inner::mut_mut!(); nested::string_add!(); } } diff --git a/tests/ui/macro_use_imports.stderr b/tests/ui/macro_use_imports.stderr index 61843124ccd91..68d558dede054 100644 --- a/tests/ui/macro_use_imports.stderr +++ b/tests/ui/macro_use_imports.stderr @@ -16,7 +16,7 @@ error: `macro_use` attributes are no longer needed in the Rust 2018 edition --> $DIR/macro_use_imports.rs:23:5 | LL | #[macro_use] - | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{inner::foofoo, inner::try_err};` + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{inner::mut_mut, inner::try_err};` error: `macro_use` attributes are no longer needed in the Rust 2018 edition --> $DIR/macro_use_imports.rs:19:5 diff --git a/tests/ui/macro_use_imports_expect.rs b/tests/ui/macro_use_imports_expect.rs index 8a1b05da9efc7..5aac5af26db56 100644 --- a/tests/ui/macro_use_imports_expect.rs +++ b/tests/ui/macro_use_imports_expect.rs @@ -39,7 +39,7 @@ mod a { let v: ty_macro!() = Vec::default(); inner::try_err!(); - inner::foofoo!(); + inner::mut_mut!(); nested::string_add!(); } } diff --git a/tests/ui/manual_async_fn.fixed b/tests/ui/manual_async_fn.fixed index b7e46a4a8ccc2..5cc4a43af7e36 100644 --- a/tests/ui/manual_async_fn.fixed +++ b/tests/ui/manual_async_fn.fixed @@ -107,4 +107,10 @@ mod issue_5765 { } } +pub async fn issue_10450() -> i32 { 42 } + +pub(crate) async fn issue_10450_2() -> i32 { 42 } + +pub(self) async fn issue_10450_3() -> i32 { 42 } + fn main() {} diff --git a/tests/ui/manual_async_fn.rs b/tests/ui/manual_async_fn.rs index b05429da66225..ba504b8a8231f 100644 --- a/tests/ui/manual_async_fn.rs +++ b/tests/ui/manual_async_fn.rs @@ -127,4 +127,16 @@ mod issue_5765 { } } +pub fn issue_10450() -> impl Future { + async { 42 } +} + +pub(crate) fn issue_10450_2() -> impl Future { + async { 42 } +} + +pub(self) fn issue_10450_3() -> impl Future { + async { 42 } +} + fn main() {} diff --git a/tests/ui/manual_async_fn.stderr b/tests/ui/manual_async_fn.stderr index 0a903ed6fd436..f5ee3eb7cccba 100644 --- a/tests/ui/manual_async_fn.stderr +++ b/tests/ui/manual_async_fn.stderr @@ -161,5 +161,50 @@ help: move the body of the async block to the enclosing function LL | fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future + 'a + 'b { 42 } | ~~~~~~ -error: aborting due to 10 previous errors +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:130:1 + | +LL | pub fn issue_10450() -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | pub async fn issue_10450() -> i32 { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: move the body of the async block to the enclosing function + | +LL | pub fn issue_10450() -> impl Future { 42 } + | ~~~~~~ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:134:1 + | +LL | pub(crate) fn issue_10450_2() -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | pub(crate) async fn issue_10450_2() -> i32 { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: move the body of the async block to the enclosing function + | +LL | pub(crate) fn issue_10450_2() -> impl Future { 42 } + | ~~~~~~ + +error: this function can be simplified using the `async fn` syntax + --> $DIR/manual_async_fn.rs:138:1 + | +LL | pub(self) fn issue_10450_3() -> impl Future { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `async` and return the output of the future directly + | +LL | pub(self) async fn issue_10450_3() -> i32 { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: move the body of the async block to the enclosing function + | +LL | pub(self) fn issue_10450_3() -> impl Future { 42 } + | ~~~~~~ + +error: aborting due to 13 previous errors diff --git a/tests/ui/manual_clamp.rs b/tests/ui/manual_clamp.rs index f7902e6fd5389..cdfd8e4c3fe3e 100644 --- a/tests/ui/manual_clamp.rs +++ b/tests/ui/manual_clamp.rs @@ -326,3 +326,22 @@ fn msrv_1_50() { input }; } + +const fn _const() { + let (input, min, max) = (0, -1, 2); + let _ = if input < min { + min + } else if input > max { + max + } else { + input + }; + + let mut x = input; + if max < x { + let x = max; + } + if min > x { + x = min; + } +} diff --git a/tests/ui/manual_main_separator_str.fixed b/tests/ui/manual_main_separator_str.fixed new file mode 100644 index 0000000000000..50f46d6b35500 --- /dev/null +++ b/tests/ui/manual_main_separator_str.fixed @@ -0,0 +1,39 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::manual_main_separator_str)] + +use std::path::MAIN_SEPARATOR; + +fn len(s: &str) -> usize { + s.len() +} + +struct U<'a> { + f: &'a str, + g: &'a String, +} + +struct V { + f: T, +} + +fn main() { + // Should lint + let _: &str = std::path::MAIN_SEPARATOR_STR; + let _ = len(std::path::MAIN_SEPARATOR_STR); + let _: Vec = std::path::MAIN_SEPARATOR_STR.encode_utf16().collect(); + + // Should lint for field `f` only + let _ = U { + f: std::path::MAIN_SEPARATOR_STR, + g: &MAIN_SEPARATOR.to_string(), + }; + + // Should not lint + let _: &String = &MAIN_SEPARATOR.to_string(); + let _ = &MAIN_SEPARATOR.to_string(); + let _ = V { + f: &MAIN_SEPARATOR.to_string(), + }; +} diff --git a/tests/ui/manual_main_separator_str.rs b/tests/ui/manual_main_separator_str.rs new file mode 100644 index 0000000000000..2dbb9e66151d4 --- /dev/null +++ b/tests/ui/manual_main_separator_str.rs @@ -0,0 +1,39 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::manual_main_separator_str)] + +use std::path::MAIN_SEPARATOR; + +fn len(s: &str) -> usize { + s.len() +} + +struct U<'a> { + f: &'a str, + g: &'a String, +} + +struct V { + f: T, +} + +fn main() { + // Should lint + let _: &str = &MAIN_SEPARATOR.to_string(); + let _ = len(&MAIN_SEPARATOR.to_string()); + let _: Vec = MAIN_SEPARATOR.to_string().encode_utf16().collect(); + + // Should lint for field `f` only + let _ = U { + f: &MAIN_SEPARATOR.to_string(), + g: &MAIN_SEPARATOR.to_string(), + }; + + // Should not lint + let _: &String = &MAIN_SEPARATOR.to_string(); + let _ = &MAIN_SEPARATOR.to_string(); + let _ = V { + f: &MAIN_SEPARATOR.to_string(), + }; +} diff --git a/tests/ui/manual_main_separator_str.stderr b/tests/ui/manual_main_separator_str.stderr new file mode 100644 index 0000000000000..e6cefde66a7d2 --- /dev/null +++ b/tests/ui/manual_main_separator_str.stderr @@ -0,0 +1,28 @@ +error: taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String` + --> $DIR/manual_main_separator_str.rs:23:19 + | +LL | let _: &str = &MAIN_SEPARATOR.to_string(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::path::MAIN_SEPARATOR_STR` + | + = note: `-D clippy::manual-main-separator-str` implied by `-D warnings` + +error: taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String` + --> $DIR/manual_main_separator_str.rs:24:17 + | +LL | let _ = len(&MAIN_SEPARATOR.to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::path::MAIN_SEPARATOR_STR` + +error: taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String` + --> $DIR/manual_main_separator_str.rs:25:23 + | +LL | let _: Vec = MAIN_SEPARATOR.to_string().encode_utf16().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::path::MAIN_SEPARATOR_STR` + +error: taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String` + --> $DIR/manual_main_separator_str.rs:29:12 + | +LL | f: &MAIN_SEPARATOR.to_string(), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::path::MAIN_SEPARATOR_STR` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/manual_rem_euclid.fixed b/tests/ui/manual_rem_euclid.fixed index 6916a284a20a1..1f6df1b0a8654 100644 --- a/tests/ui/manual_rem_euclid.fixed +++ b/tests/ui/manual_rem_euclid.fixed @@ -1,19 +1,13 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::manual_rem_euclid)] #![allow(clippy::let_with_type_underscore)] -#[macro_use] -extern crate macro_rules; - -macro_rules! internal_rem_euclid { - () => { - let value: i32 = 5; - let _: i32 = value.rem_euclid(4); - }; -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { let value: i32 = 5; @@ -39,10 +33,16 @@ fn main() { let _: i32 = ((4 % value) + 4) % 4; // Lint in internal macros - internal_rem_euclid!(); + inline!( + let value: i32 = 5; + let _: i32 = value.rem_euclid(4); + ); // Do not lint in external macros - manual_rem_euclid!(); + external!( + let value: i32 = 5; + let _: i32 = ((value % 4) + 4) % 4; + ); } // Should lint for params too diff --git a/tests/ui/manual_rem_euclid.rs b/tests/ui/manual_rem_euclid.rs index 412dbddb4266b..b275e8a38d21e 100644 --- a/tests/ui/manual_rem_euclid.rs +++ b/tests/ui/manual_rem_euclid.rs @@ -1,19 +1,13 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::manual_rem_euclid)] #![allow(clippy::let_with_type_underscore)] -#[macro_use] -extern crate macro_rules; - -macro_rules! internal_rem_euclid { - () => { - let value: i32 = 5; - let _: i32 = ((value % 4) + 4) % 4; - }; -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { let value: i32 = 5; @@ -39,10 +33,16 @@ fn main() { let _: i32 = ((4 % value) + 4) % 4; // Lint in internal macros - internal_rem_euclid!(); + inline!( + let value: i32 = 5; + let _: i32 = ((value % 4) + 4) % 4; + ); // Do not lint in external macros - manual_rem_euclid!(); + external!( + let value: i32 = 5; + let _: i32 = ((value % 4) + 4) % 4; + ); } // Should lint for params too diff --git a/tests/ui/manual_rem_euclid.stderr b/tests/ui/manual_rem_euclid.stderr index 6d06654638b3c..a43707f89c49e 100644 --- a/tests/ui/manual_rem_euclid.stderr +++ b/tests/ui/manual_rem_euclid.stderr @@ -1,5 +1,5 @@ error: manual `rem_euclid` implementation - --> $DIR/manual_rem_euclid.rs:20:18 + --> $DIR/manual_rem_euclid.rs:14:18 | LL | let _: i32 = ((value % 4) + 4) % 4; | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` @@ -7,39 +7,36 @@ LL | let _: i32 = ((value % 4) + 4) % 4; = note: `-D clippy::manual-rem-euclid` implied by `-D warnings` error: manual `rem_euclid` implementation - --> $DIR/manual_rem_euclid.rs:21:18 + --> $DIR/manual_rem_euclid.rs:15:18 | LL | let _: i32 = (4 + (value % 4)) % 4; | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` error: manual `rem_euclid` implementation - --> $DIR/manual_rem_euclid.rs:22:18 + --> $DIR/manual_rem_euclid.rs:16:18 | LL | let _: i32 = (value % 4 + 4) % 4; | ^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` error: manual `rem_euclid` implementation - --> $DIR/manual_rem_euclid.rs:23:18 + --> $DIR/manual_rem_euclid.rs:17:18 | LL | let _: i32 = (4 + value % 4) % 4; | ^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` error: manual `rem_euclid` implementation - --> $DIR/manual_rem_euclid.rs:24:22 + --> $DIR/manual_rem_euclid.rs:18:22 | LL | let _: i32 = 1 + (4 + value % 4) % 4; | ^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` error: manual `rem_euclid` implementation - --> $DIR/manual_rem_euclid.rs:13:22 + --> $DIR/manual_rem_euclid.rs:38:22 | LL | let _: i32 = ((value % 4) + 4) % 4; | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` -... -LL | internal_rem_euclid!(); - | ---------------------- in this macro invocation | - = note: this error originates in the macro `internal_rem_euclid` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: manual `rem_euclid` implementation --> $DIR/manual_rem_euclid.rs:50:5 diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index 6cfb6661a0394..201301cc9b737 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -1,7 +1,12 @@ // run-rustfix #![warn(clippy::match_single_binding)] -#![allow(unused_variables)] -#![allow(clippy::toplevel_ref_arg, clippy::uninlined_format_args)] +#![allow( + unused, + clippy::let_unit_value, + clippy::no_effect, + clippy::toplevel_ref_arg, + clippy::uninlined_format_args +)] struct Point { x: i32, @@ -109,10 +114,9 @@ fn main() { // Lint let x = 1; - println!("Not an array index start"); + println!("Not an array index start") } -#[allow(dead_code)] fn issue_8723() { let (mut val, idx) = ("a b", 1); @@ -125,16 +129,15 @@ fn issue_8723() { let _ = val; } -#[allow(dead_code)] +fn side_effects() {} + fn issue_9575() { - fn side_effects() {} let _ = || { side_effects(); - println!("Needs curlies"); + println!("Needs curlies") }; } -#[allow(dead_code)] fn issue_9725(r: Option) { let x = r; match x { @@ -146,3 +149,25 @@ fn issue_9725(r: Option) { }, }; } + +fn issue_10447() -> usize { + (); + + let a = (); + + side_effects(); + + let b = side_effects(); + + println!("1"); + + let c = println!("1"); + + let in_expr = [ + (), + side_effects(), + println!("1"), + ]; + + 2 +} diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index f188aeb5f2ff5..8b047b19ce968 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -1,7 +1,12 @@ // run-rustfix #![warn(clippy::match_single_binding)] -#![allow(unused_variables)] -#![allow(clippy::toplevel_ref_arg, clippy::uninlined_format_args)] +#![allow( + unused, + clippy::let_unit_value, + clippy::no_effect, + clippy::toplevel_ref_arg, + clippy::uninlined_format_args +)] struct Point { x: i32, @@ -127,7 +132,6 @@ fn main() { } } -#[allow(dead_code)] fn issue_8723() { let (mut val, idx) = ("a b", 1); @@ -141,15 +145,14 @@ fn issue_8723() { let _ = val; } -#[allow(dead_code)] +fn side_effects() {} + fn issue_9575() { - fn side_effects() {} let _ = || match side_effects() { _ => println!("Needs curlies"), }; } -#[allow(dead_code)] fn issue_9725(r: Option) { match r { x => match x { @@ -162,3 +165,43 @@ fn issue_9725(r: Option) { }, }; } + +fn issue_10447() -> usize { + match 1 { + _ => (), + } + + let a = match 1 { + _ => (), + }; + + match 1 { + _ => side_effects(), + } + + let b = match 1 { + _ => side_effects(), + }; + + match 1 { + _ => println!("1"), + } + + let c = match 1 { + _ => println!("1"), + }; + + let in_expr = [ + match 1 { + _ => (), + }, + match 1 { + _ => side_effects(), + }, + match 1 { + _ => println!("1"), + }, + ]; + + 2 +} diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index e960d64ad2b03..9d16af76c6af9 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -1,5 +1,5 @@ error: this match could be written as a `let` statement - --> $DIR/match_single_binding.rs:28:5 + --> $DIR/match_single_binding.rs:33:5 | LL | / match (a, b, c) { LL | | (x, y, z) => { @@ -18,7 +18,7 @@ LL + } | error: this match could be written as a `let` statement - --> $DIR/match_single_binding.rs:34:5 + --> $DIR/match_single_binding.rs:39:5 | LL | / match (a, b, c) { LL | | (x, y, z) => println!("{} {} {}", x, y, z), @@ -32,7 +32,7 @@ LL + println!("{} {} {}", x, y, z); | error: this match could be replaced by its body itself - --> $DIR/match_single_binding.rs:51:5 + --> $DIR/match_single_binding.rs:56:5 | LL | / match a { LL | | _ => println!("whatever"), @@ -40,7 +40,7 @@ LL | | } | |_____^ help: consider using the match body instead: `println!("whatever");` error: this match could be replaced by its body itself - --> $DIR/match_single_binding.rs:55:5 + --> $DIR/match_single_binding.rs:60:5 | LL | / match a { LL | | _ => { @@ -59,7 +59,7 @@ LL + } | error: this match could be replaced by its body itself - --> $DIR/match_single_binding.rs:62:5 + --> $DIR/match_single_binding.rs:67:5 | LL | / match a { LL | | _ => { @@ -81,7 +81,7 @@ LL + } | error: this match could be written as a `let` statement - --> $DIR/match_single_binding.rs:72:5 + --> $DIR/match_single_binding.rs:77:5 | LL | / match p { LL | | Point { x, y } => println!("Coords: ({}, {})", x, y), @@ -95,7 +95,7 @@ LL + println!("Coords: ({}, {})", x, y); | error: this match could be written as a `let` statement - --> $DIR/match_single_binding.rs:76:5 + --> $DIR/match_single_binding.rs:81:5 | LL | / match p { LL | | Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1), @@ -109,7 +109,7 @@ LL + println!("Coords: ({}, {})", x1, y1); | error: this match could be written as a `let` statement - --> $DIR/match_single_binding.rs:81:5 + --> $DIR/match_single_binding.rs:86:5 | LL | / match x { LL | | ref r => println!("Got a reference to {}", r), @@ -123,7 +123,7 @@ LL + println!("Got a reference to {}", r); | error: this match could be written as a `let` statement - --> $DIR/match_single_binding.rs:86:5 + --> $DIR/match_single_binding.rs:91:5 | LL | / match x { LL | | ref mut mr => println!("Got a mutable reference to {}", mr), @@ -137,7 +137,7 @@ LL + println!("Got a mutable reference to {}", mr); | error: this match could be written as a `let` statement - --> $DIR/match_single_binding.rs:90:5 + --> $DIR/match_single_binding.rs:95:5 | LL | / let product = match coords() { LL | | Point { x, y } => x * y, @@ -151,7 +151,7 @@ LL + let product = x * y; | error: this match could be written as a `let` statement - --> $DIR/match_single_binding.rs:98:18 + --> $DIR/match_single_binding.rs:103:18 | LL | .map(|i| match i.unwrap() { | __________________^ @@ -168,16 +168,16 @@ LL ~ }) | error: this match could be replaced by its body itself - --> $DIR/match_single_binding.rs:124:5 + --> $DIR/match_single_binding.rs:129:5 | LL | / match x { LL | | // => LL | | _ => println!("Not an array index start"), LL | | } - | |_____^ help: consider using the match body instead: `println!("Not an array index start");` + | |_____^ help: consider using the match body instead: `println!("Not an array index start")` error: this assignment could be simplified - --> $DIR/match_single_binding.rs:134:5 + --> $DIR/match_single_binding.rs:138:5 | LL | / val = match val.split_at(idx) { LL | | (pre, suf) => { @@ -197,7 +197,7 @@ LL ~ }; | error: this match could be replaced by its scrutinee and body - --> $DIR/match_single_binding.rs:147:16 + --> $DIR/match_single_binding.rs:151:16 | LL | let _ = || match side_effects() { | ________________^ @@ -209,12 +209,12 @@ help: consider using the scrutinee and body instead | LL ~ let _ = || { LL + side_effects(); -LL + println!("Needs curlies"); +LL + println!("Needs curlies") LL ~ }; | error: this match could be written as a `let` statement - --> $DIR/match_single_binding.rs:154:5 + --> $DIR/match_single_binding.rs:157:5 | LL | / match r { LL | | x => match x { @@ -238,5 +238,80 @@ LL + }, LL ~ }; | -error: aborting due to 15 previous errors +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:170:5 + | +LL | / match 1 { +LL | | _ => (), +LL | | } + | |_____^ help: consider using the match body instead: `();` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:174:13 + | +LL | let a = match 1 { + | _____________^ +LL | | _ => (), +LL | | }; + | |_____^ help: consider using the match body instead: `()` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:178:5 + | +LL | / match 1 { +LL | | _ => side_effects(), +LL | | } + | |_____^ help: consider using the match body instead: `side_effects();` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:182:13 + | +LL | let b = match 1 { + | _____________^ +LL | | _ => side_effects(), +LL | | }; + | |_____^ help: consider using the match body instead: `side_effects()` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:186:5 + | +LL | / match 1 { +LL | | _ => println!("1"), +LL | | } + | |_____^ help: consider using the match body instead: `println!("1");` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:190:13 + | +LL | let c = match 1 { + | _____________^ +LL | | _ => println!("1"), +LL | | }; + | |_____^ help: consider using the match body instead: `println!("1")` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:195:9 + | +LL | / match 1 { +LL | | _ => (), +LL | | }, + | |_________^ help: consider using the match body instead: `()` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:198:9 + | +LL | / match 1 { +LL | | _ => side_effects(), +LL | | }, + | |_________^ help: consider using the match body instead: `side_effects()` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:201:9 + | +LL | / match 1 { +LL | | _ => println!("1"), +LL | | }, + | |_________^ help: consider using the match body instead: `println!("1")` + +error: aborting due to 24 previous errors diff --git a/tests/ui/match_single_binding2.fixed b/tests/ui/match_single_binding2.fixed index 6a7db67e311a5..e3cf56a4293cc 100644 --- a/tests/ui/match_single_binding2.fixed +++ b/tests/ui/match_single_binding2.fixed @@ -30,7 +30,7 @@ fn main() { #[rustfmt::skip] Some((first, _second)) => { let (a, b) = get_tup(); - println!("a {:?} and b {:?}", a, b); + println!("a {:?} and b {:?}", a, b) }, None => println!("nothing"), } @@ -49,5 +49,5 @@ fn main() { 0 => 1, _ => 2, }; - println!("Single branch"); + println!("Single branch") } diff --git a/tests/ui/match_single_binding2.stderr b/tests/ui/match_single_binding2.stderr index 22bf7d8be4a29..e180b93e76d7b 100644 --- a/tests/ui/match_single_binding2.stderr +++ b/tests/ui/match_single_binding2.stderr @@ -27,7 +27,7 @@ LL | | } help: consider using a `let` statement | LL ~ let (a, b) = get_tup(); -LL + println!("a {:?} and b {:?}", a, b); +LL + println!("a {:?} and b {:?}", a, b) | error: this match could be replaced by its scrutinee and body @@ -61,7 +61,7 @@ LL ~ match x { LL + 0 => 1, LL + _ => 2, LL + }; -LL + println!("Single branch"); +LL + println!("Single branch") | error: aborting due to 4 previous errors diff --git a/tests/ui/mem_replace_macro.rs b/tests/ui/mem_replace_macro.rs index 0c09344b80d10..3932e7d00c19d 100644 --- a/tests/ui/mem_replace_macro.rs +++ b/tests/ui/mem_replace_macro.rs @@ -1,21 +1,12 @@ -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::mem_replace_with_default)] -#[macro_use] -extern crate macro_rules; - -macro_rules! take { - ($s:expr) => { - std::mem::replace($s, Default::default()) - }; -} - -fn replace_with_default() { - let s = &mut String::from("foo"); - take!(s); - take_external!(s); -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { - replace_with_default(); + let s = &mut String::from("foo"); + inline!(std::mem::replace($s, Default::default())); + external!(std::mem::replace($s, Default::default())); } diff --git a/tests/ui/mem_replace_macro.stderr b/tests/ui/mem_replace_macro.stderr index dd69ab8b5efb8..35dda93da3d03 100644 --- a/tests/ui/mem_replace_macro.stderr +++ b/tests/ui/mem_replace_macro.stderr @@ -1,14 +1,11 @@ error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` - --> $DIR/mem_replace_macro.rs:9:9 + --> $DIR/mem_replace_macro.rs:10:13 | -LL | std::mem::replace($s, Default::default()) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | take!(s); - | -------- in this macro invocation +LL | inline!(std::mem::replace($s, Default::default())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::mem-replace-with-default` implied by `-D warnings` - = note: this error originates in the macro `take` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to previous error diff --git a/tests/ui/missing_const_for_fn/cant_be_const.rs b/tests/ui/missing_const_for_fn/cant_be_const.rs index 75cace1816754..e6f88c6e622cb 100644 --- a/tests/ui/missing_const_for_fn/cant_be_const.rs +++ b/tests/ui/missing_const_for_fn/cant_be_const.rs @@ -3,15 +3,15 @@ //! The .stderr output of this test should be empty. Otherwise it's a bug somewhere. // aux-build:helper.rs -// aux-build:../../auxiliary/proc_macro_with_span.rs +// aux-build:../../auxiliary/proc_macros.rs #![warn(clippy::missing_const_for_fn)] #![feature(start)] extern crate helper; -extern crate proc_macro_with_span; +extern crate proc_macros; -use proc_macro_with_span::with_span; +use proc_macros::with_span; struct Game; diff --git a/tests/ui/missing_doc.rs b/tests/ui/missing_doc.rs index 590ad63c90be3..5752048949ca4 100644 --- a/tests/ui/missing_doc.rs +++ b/tests/ui/missing_doc.rs @@ -1,5 +1,5 @@ // needs-asm-support -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![warn(clippy::missing_docs_in_private_items)] // When denying at the crate level, be sure to not get random warnings from the @@ -8,9 +8,9 @@ //! Some garbage docs for the crate here #![doc = "More garbage"] -extern crate proc_macro_with_span; +extern crate proc_macros; -use proc_macro_with_span::with_span; +use proc_macros::with_span; use std::arch::global_asm; type Typedef = String; diff --git a/tests/ui/missing_doc_impl.rs b/tests/ui/missing_doc_impl.rs index 0396d1193ff5c..e2d49b0907d57 100644 --- a/tests/ui/missing_doc_impl.rs +++ b/tests/ui/missing_doc_impl.rs @@ -1,4 +1,4 @@ -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![warn(clippy::missing_docs_in_private_items)] #![allow(dead_code)] @@ -7,8 +7,8 @@ //! Some garbage docs for the crate here #![doc = "More garbage"] -extern crate proc_macro_with_span; -use proc_macro_with_span::with_span; +extern crate proc_macros; +use proc_macros::with_span; struct Foo { a: isize, diff --git a/tests/ui/mistyped_literal_suffix.fixed b/tests/ui/mistyped_literal_suffix.fixed index becb9562a8497..9a47d7c56ed1e 100644 --- a/tests/ui/mistyped_literal_suffix.fixed +++ b/tests/ui/mistyped_literal_suffix.fixed @@ -1,5 +1,5 @@ // run-rustfix -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![allow( dead_code, @@ -10,8 +10,8 @@ clippy::unusual_byte_groupings )] -extern crate proc_macro_with_span; -use proc_macro_with_span::with_span; +extern crate proc_macros; +use proc_macros::with_span; fn main() { let fail14 = 2_i32; diff --git a/tests/ui/mistyped_literal_suffix.rs b/tests/ui/mistyped_literal_suffix.rs index ee841bcd7e4e9..04261cba55aa4 100644 --- a/tests/ui/mistyped_literal_suffix.rs +++ b/tests/ui/mistyped_literal_suffix.rs @@ -1,5 +1,5 @@ // run-rustfix -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![allow( dead_code, @@ -10,8 +10,8 @@ clippy::unusual_byte_groupings )] -extern crate proc_macro_with_span; -use proc_macro_with_span::with_span; +extern crate proc_macros; +use proc_macros::with_span; fn main() { let fail14 = 2_32; diff --git a/tests/ui/multiple_unsafe_ops_per_block.rs b/tests/ui/multiple_unsafe_ops_per_block.rs index 5073685c9f067..9082f1675a875 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.rs +++ b/tests/ui/multiple_unsafe_ops_per_block.rs @@ -1,12 +1,12 @@ -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![allow(unused)] #![allow(deref_nullptr)] #![allow(clippy::unnecessary_operation)] #![allow(clippy::drop_copy)] #![warn(clippy::multiple_unsafe_ops_per_block)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::external; use core::arch::asm; @@ -113,7 +113,10 @@ unsafe fn read_char_good(ptr: *const u8) -> char { // no lint fn issue10259() { - unsafe_macro!(); + external!(unsafe { + *core::ptr::null::<()>(); + *core::ptr::null::<()>(); + }); } fn _fn_ptr(x: unsafe fn()) { diff --git a/tests/ui/multiple_unsafe_ops_per_block.stderr b/tests/ui/multiple_unsafe_ops_per_block.stderr index e0c1d3801f7cf..badc284ec423e 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.stderr +++ b/tests/ui/multiple_unsafe_ops_per_block.stderr @@ -126,7 +126,7 @@ LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } | ^^^^^^^^^^^^^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> $DIR/multiple_unsafe_ops_per_block.rs:120:5 + --> $DIR/multiple_unsafe_ops_per_block.rs:123:5 | LL | / unsafe { LL | | x(); @@ -135,18 +135,18 @@ LL | | } | |_____^ | note: unsafe function call occurs here - --> $DIR/multiple_unsafe_ops_per_block.rs:121:9 + --> $DIR/multiple_unsafe_ops_per_block.rs:124:9 | LL | x(); | ^^^ note: unsafe function call occurs here - --> $DIR/multiple_unsafe_ops_per_block.rs:122:9 + --> $DIR/multiple_unsafe_ops_per_block.rs:125:9 | LL | x(); | ^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> $DIR/multiple_unsafe_ops_per_block.rs:131:9 + --> $DIR/multiple_unsafe_ops_per_block.rs:134:9 | LL | / unsafe { LL | | T::X(); @@ -155,18 +155,18 @@ LL | | } | |_________^ | note: unsafe function call occurs here - --> $DIR/multiple_unsafe_ops_per_block.rs:132:13 + --> $DIR/multiple_unsafe_ops_per_block.rs:135:13 | LL | T::X(); | ^^^^^^ note: unsafe function call occurs here - --> $DIR/multiple_unsafe_ops_per_block.rs:133:13 + --> $DIR/multiple_unsafe_ops_per_block.rs:136:13 | LL | T::X(); | ^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> $DIR/multiple_unsafe_ops_per_block.rs:141:5 + --> $DIR/multiple_unsafe_ops_per_block.rs:144:5 | LL | / unsafe { LL | | x.0(); @@ -175,12 +175,12 @@ LL | | } | |_____^ | note: unsafe function call occurs here - --> $DIR/multiple_unsafe_ops_per_block.rs:142:9 + --> $DIR/multiple_unsafe_ops_per_block.rs:145:9 | LL | x.0(); | ^^^^^ note: unsafe function call occurs here - --> $DIR/multiple_unsafe_ops_per_block.rs:143:9 + --> $DIR/multiple_unsafe_ops_per_block.rs:146:9 | LL | x.0(); | ^^^^^ diff --git a/tests/ui/must_use_unit.fixed b/tests/ui/must_use_unit.fixed index 6c9aa434ac016..b7d375ff80e11 100644 --- a/tests/ui/must_use_unit.fixed +++ b/tests/ui/must_use_unit.fixed @@ -1,11 +1,11 @@ //run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::must_use_unit)] #![allow(clippy::unused_unit)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::external; pub fn must_use_default() {} @@ -22,5 +22,8 @@ fn main() { must_use_with_note(); // We should not lint in external macros - must_use_unit!(); + external!( + #[must_use] + fn foo() {} + ); } diff --git a/tests/ui/must_use_unit.rs b/tests/ui/must_use_unit.rs index 8a395dc284db4..74d6b4ca865cd 100644 --- a/tests/ui/must_use_unit.rs +++ b/tests/ui/must_use_unit.rs @@ -1,11 +1,11 @@ //run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::must_use_unit)] #![allow(clippy::unused_unit)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::external; #[must_use] pub fn must_use_default() {} @@ -22,5 +22,8 @@ fn main() { must_use_with_note(); // We should not lint in external macros - must_use_unit!(); + external!( + #[must_use] + fn foo() {} + ); } diff --git a/tests/ui/mut_mut.rs b/tests/ui/mut_mut.rs index ee3a856566cc1..06bb085442a79 100644 --- a/tests/ui/mut_mut.rs +++ b/tests/ui/mut_mut.rs @@ -1,10 +1,10 @@ -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::mut_mut)] #![allow(unused)] #![allow(clippy::no_effect, clippy::uninlined_format_args, clippy::unnecessary_operation)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; fn fun(x: &mut &mut u32) -> bool { **x > 0 @@ -21,6 +21,7 @@ macro_rules! mut_ptr { } #[allow(unused_mut, unused_variables)] +#[inline_macros] fn main() { let mut x = &mut &mut 1u32; { @@ -37,7 +38,7 @@ fn main() { ***y + **x; } - let mut z = mut_ptr!(&mut 3u32); + let mut z = inline!(&mut $(&mut 3u32)); } fn issue939() { @@ -55,7 +56,7 @@ fn issue939() { fn issue6922() { // do not lint from an external macro - mut_mut!(); + external!(let mut_mut_ty: &mut &mut u32 = &mut &mut 1u32;); } mod issue9035 { diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index 6820a85aa5433..93b857eb20748 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -7,54 +7,51 @@ LL | fn fun(x: &mut &mut u32) -> bool { = note: `-D clippy::mut-mut` implied by `-D warnings` error: generally you want to avoid `&mut &mut _` if possible - --> $DIR/mut_mut.rs:25:17 + --> $DIR/mut_mut.rs:26:17 | LL | let mut x = &mut &mut 1u32; | ^^^^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> $DIR/mut_mut.rs:19:9 + --> $DIR/mut_mut.rs:41:25 | -LL | &mut $p - | ^^^^^^^ -... -LL | let mut z = mut_ptr!(&mut 3u32); - | ------------------- in this macro invocation +LL | let mut z = inline!(&mut $(&mut 3u32)); + | ^ | - = note: this error originates in the macro `mut_ptr` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: this expression mutably borrows a mutable reference. Consider reborrowing - --> $DIR/mut_mut.rs:27:21 + --> $DIR/mut_mut.rs:28:21 | LL | let mut y = &mut x; | ^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> $DIR/mut_mut.rs:31:32 + --> $DIR/mut_mut.rs:32:32 | LL | let y: &mut &mut u32 = &mut &mut 2; | ^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> $DIR/mut_mut.rs:31:16 + --> $DIR/mut_mut.rs:32:16 | LL | let y: &mut &mut u32 = &mut &mut 2; | ^^^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> $DIR/mut_mut.rs:36:37 + --> $DIR/mut_mut.rs:37:37 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; | ^^^^^^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> $DIR/mut_mut.rs:36:16 + --> $DIR/mut_mut.rs:37:16 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; | ^^^^^^^^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> $DIR/mut_mut.rs:36:21 + --> $DIR/mut_mut.rs:37:21 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; | ^^^^^^^^^^^^^ diff --git a/tests/ui/needless_late_init.fixed b/tests/ui/needless_late_init.fixed index 17f2227ba91c4..86d899bb46c36 100644 --- a/tests/ui/needless_late_init.fixed +++ b/tests/ui/needless_late_init.fixed @@ -1,4 +1,5 @@ // run-rustfix +// aux-build:proc_macros.rs #![feature(let_chains)] #![allow(unused)] #![allow( @@ -10,6 +11,8 @@ clippy::uninlined_format_args )] +extern crate proc_macros; + use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::rc::Rc; @@ -138,6 +141,7 @@ const fn in_const() -> &'static str { a } +#[proc_macros::inline_macros] fn does_not_lint() { let z; if false { @@ -195,35 +199,27 @@ fn does_not_lint() { } y = 3; - macro_rules! assign { - ($i:ident) => { - $i = 1; - }; - } let x; - assign!(x); + inline!($x = 1;); let x; if true { - assign!(x); + inline!($x = 1;); } else { x = 2; } - macro_rules! in_macro { - () => { - let x; - x = 1; + inline!({ + let x; + x = 1; - let x; - if true { - x = 1; - } else { - x = 2; - } - }; - } - in_macro!(); + let x; + if true { + x = 1; + } else { + x = 2; + } + }); // ignore if-lets - https://github.com/rust-lang/rust-clippy/issues/8613 let x; diff --git a/tests/ui/needless_late_init.rs b/tests/ui/needless_late_init.rs index d84457a298753..969afb38edf2e 100644 --- a/tests/ui/needless_late_init.rs +++ b/tests/ui/needless_late_init.rs @@ -1,4 +1,5 @@ // run-rustfix +// aux-build:proc_macros.rs #![feature(let_chains)] #![allow(unused)] #![allow( @@ -10,6 +11,8 @@ clippy::uninlined_format_args )] +extern crate proc_macros; + use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::rc::Rc; @@ -138,6 +141,7 @@ const fn in_const() -> &'static str { a } +#[proc_macros::inline_macros] fn does_not_lint() { let z; if false { @@ -195,35 +199,27 @@ fn does_not_lint() { } y = 3; - macro_rules! assign { - ($i:ident) => { - $i = 1; - }; - } let x; - assign!(x); + inline!($x = 1;); let x; if true { - assign!(x); + inline!($x = 1;); } else { x = 2; } - macro_rules! in_macro { - () => { - let x; - x = 1; + inline!({ + let x; + x = 1; - let x; - if true { - x = 1; - } else { - x = 2; - } - }; - } - in_macro!(); + let x; + if true { + x = 1; + } else { + x = 2; + } + }); // ignore if-lets - https://github.com/rust-lang/rust-clippy/issues/8613 let x; diff --git a/tests/ui/needless_late_init.stderr b/tests/ui/needless_late_init.stderr index 0a256fb4a131c..eff782f8bf104 100644 --- a/tests/ui/needless_late_init.stderr +++ b/tests/ui/needless_late_init.stderr @@ -1,5 +1,5 @@ error: unneeded late initialization - --> $DIR/needless_late_init.rs:24:5 + --> $DIR/needless_late_init.rs:27:5 | LL | let a; | ^^^^^^ created here @@ -13,7 +13,7 @@ LL | let a = "zero"; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:27:5 + --> $DIR/needless_late_init.rs:30:5 | LL | let b; | ^^^^^^ created here @@ -27,7 +27,7 @@ LL | let b = 1; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:28:5 + --> $DIR/needless_late_init.rs:31:5 | LL | let c; | ^^^^^^ created here @@ -41,7 +41,7 @@ LL | let c = 2; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:32:5 + --> $DIR/needless_late_init.rs:35:5 | LL | let d: usize; | ^^^^^^^^^^^^^ created here @@ -54,7 +54,7 @@ LL | let d: usize = 1; | ~~~~~~~~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:35:5 + --> $DIR/needless_late_init.rs:38:5 | LL | let e; | ^^^^^^ created here @@ -67,7 +67,7 @@ LL | let e = format!("{}", d); | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:40:5 + --> $DIR/needless_late_init.rs:43:5 | LL | let a; | ^^^^^^ @@ -88,7 +88,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:49:5 + --> $DIR/needless_late_init.rs:52:5 | LL | let b; | ^^^^^^ @@ -109,7 +109,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:56:5 + --> $DIR/needless_late_init.rs:59:5 | LL | let d; | ^^^^^^ @@ -130,7 +130,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:64:5 + --> $DIR/needless_late_init.rs:67:5 | LL | let e; | ^^^^^^ @@ -151,7 +151,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:71:5 + --> $DIR/needless_late_init.rs:74:5 | LL | let f; | ^^^^^^ @@ -167,7 +167,7 @@ LL + 1 => "three", | error: unneeded late initialization - --> $DIR/needless_late_init.rs:77:5 + --> $DIR/needless_late_init.rs:80:5 | LL | let g: usize; | ^^^^^^^^^^^^^ @@ -187,7 +187,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:85:5 + --> $DIR/needless_late_init.rs:88:5 | LL | let x; | ^^^^^^ created here @@ -201,7 +201,7 @@ LL | let x = 1; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:89:5 + --> $DIR/needless_late_init.rs:92:5 | LL | let x; | ^^^^^^ created here @@ -215,7 +215,7 @@ LL | let x = SignificantDrop; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:93:5 + --> $DIR/needless_late_init.rs:96:5 | LL | let x; | ^^^^^^ created here @@ -229,7 +229,7 @@ LL | let x = SignificantDrop; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:112:5 + --> $DIR/needless_late_init.rs:115:5 | LL | let a; | ^^^^^^ @@ -250,7 +250,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:129:5 + --> $DIR/needless_late_init.rs:132:5 | LL | let a; | ^^^^^^ diff --git a/tests/ui/needless_lifetimes.fixed b/tests/ui/needless_lifetimes.fixed index f0f1f9298ac65..e6ead69d148e8 100644 --- a/tests/ui/needless_lifetimes.fixed +++ b/tests/ui/needless_lifetimes.fixed @@ -1,5 +1,5 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::needless_lifetimes)] #![allow( @@ -12,8 +12,8 @@ clippy::get_first )] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::inline_macros; fn distinct_lifetimes(_x: &u8, _y: &u8, _z: u8) {} @@ -502,30 +502,29 @@ mod pr_9743_output_lifetime_checks { } } +#[inline_macros] mod in_macro { - macro_rules! local_one_input_macro { - () => { - fn one_input(x: &u8) -> &u8 { - unimplemented!() - } - }; - } + use proc_macros::external; // lint local macro expands to function with needless lifetimes - local_one_input_macro!(); + inline! { + fn one_input(x: &u8) -> &u8 { + unimplemented!() + } + } // no lint on external macro - macro_rules::needless_lifetime!(); - - macro_rules! expanded_lifetime { - ($l:lifetime) => { - fn f<$l>(arg: &$l str) -> &$l str { - arg - } + external! { + fn needless_lifetime<'a>(x: &'a u8) -> &'a u8 { + unimplemented!() } } - expanded_lifetime!('a); + inline! { + fn f<$'a>(arg: &$'a str) -> &$'a str { + arg + } + } } mod issue5787 { diff --git a/tests/ui/needless_lifetimes.rs b/tests/ui/needless_lifetimes.rs index ddfd10430038e..06eb430506f9f 100644 --- a/tests/ui/needless_lifetimes.rs +++ b/tests/ui/needless_lifetimes.rs @@ -1,5 +1,5 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::needless_lifetimes)] #![allow( @@ -12,8 +12,8 @@ clippy::get_first )] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::inline_macros; fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} @@ -502,30 +502,29 @@ mod pr_9743_output_lifetime_checks { } } +#[inline_macros] mod in_macro { - macro_rules! local_one_input_macro { - () => { - fn one_input<'a>(x: &'a u8) -> &'a u8 { - unimplemented!() - } - }; - } + use proc_macros::external; // lint local macro expands to function with needless lifetimes - local_one_input_macro!(); + inline! { + fn one_input<'a>(x: &'a u8) -> &'a u8 { + unimplemented!() + } + } // no lint on external macro - macro_rules::needless_lifetime!(); - - macro_rules! expanded_lifetime { - ($l:lifetime) => { - fn f<$l>(arg: &$l str) -> &$l str { - arg - } + external! { + fn needless_lifetime<'a>(x: &'a u8) -> &'a u8 { + unimplemented!() } } - expanded_lifetime!('a); + inline! { + fn f<$'a>(arg: &$'a str) -> &$'a str { + arg + } + } } mod issue5787 { diff --git a/tests/ui/needless_lifetimes.stderr b/tests/ui/needless_lifetimes.stderr index 4e3c8f20d8c52..86acc4e00469f 100644 --- a/tests/ui/needless_lifetimes.stderr +++ b/tests/ui/needless_lifetimes.stderr @@ -540,19 +540,16 @@ LL + fn multiple_inputs_output_not_elided<'b>(x: &u8, y: &'b u8, z: &'b u8) | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:508:13 + --> $DIR/needless_lifetimes.rs:511:9 | -LL | fn one_input<'a>(x: &'a u8) -> &'a u8 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | local_one_input_macro!(); - | ------------------------ in this macro invocation +LL | fn one_input<'a>(x: &'a u8) -> &'a u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in the macro `local_one_input_macro` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_mod_in_macro` (in Nightly builds, run with -Z macro-backtrace for more info) help: elide the lifetimes | -LL - fn one_input<'a>(x: &'a u8) -> &'a u8 { -LL + fn one_input(x: &u8) -> &u8 { +LL - fn one_input<'a>(x: &'a u8) -> &'a u8 { +LL + fn one_input(x: &u8) -> &u8 { | error: aborting due to 46 previous errors diff --git a/tests/ui/needless_update.rs b/tests/ui/needless_update.rs index b93ff048a62f2..4e8517cad1006 100644 --- a/tests/ui/needless_update.rs +++ b/tests/ui/needless_update.rs @@ -1,5 +1,5 @@ #![warn(clippy::needless_update)] -#![allow(clippy::no_effect)] +#![allow(clippy::no_effect, clippy::unnecessary_struct_initialization)] struct S { pub a: i32, diff --git a/tests/ui/no_effect.rs b/tests/ui/no_effect.rs index ec8a5aa28c593..1e42e1fbabfeb 100644 --- a/tests/ui/no_effect.rs +++ b/tests/ui/no_effect.rs @@ -1,7 +1,12 @@ #![feature(fn_traits, unboxed_closures)] #![warn(clippy::no_effect_underscore_binding)] #![allow(dead_code, path_statements)] -#![allow(clippy::deref_addrof, clippy::redundant_field_names, clippy::uninlined_format_args)] +#![allow( + clippy::deref_addrof, + clippy::redundant_field_names, + clippy::uninlined_format_args, + clippy::unnecessary_struct_initialization +)] struct Unit; struct Tuple(i32); diff --git a/tests/ui/no_effect.stderr b/tests/ui/no_effect.stderr index 92f6dbfbdba16..f10f2bcf2a8a0 100644 --- a/tests/ui/no_effect.stderr +++ b/tests/ui/no_effect.stderr @@ -1,5 +1,5 @@ error: statement with no effect - --> $DIR/no_effect.rs:92:5 + --> $DIR/no_effect.rs:97:5 | LL | 0; | ^^ @@ -7,151 +7,151 @@ LL | 0; = note: `-D clippy::no-effect` implied by `-D warnings` error: statement with no effect - --> $DIR/no_effect.rs:93:5 + --> $DIR/no_effect.rs:98:5 | LL | s2; | ^^^ error: statement with no effect - --> $DIR/no_effect.rs:94:5 + --> $DIR/no_effect.rs:99:5 | LL | Unit; | ^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:95:5 + --> $DIR/no_effect.rs:100:5 | LL | Tuple(0); | ^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:96:5 + --> $DIR/no_effect.rs:101:5 | LL | Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:97:5 + --> $DIR/no_effect.rs:102:5 | LL | Struct { ..s }; | ^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:98:5 + --> $DIR/no_effect.rs:103:5 | LL | Union { a: 0 }; | ^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:99:5 + --> $DIR/no_effect.rs:104:5 | LL | Enum::Tuple(0); | ^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:100:5 + --> $DIR/no_effect.rs:105:5 | LL | Enum::Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:101:5 + --> $DIR/no_effect.rs:106:5 | LL | 5 + 6; | ^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:102:5 + --> $DIR/no_effect.rs:107:5 | LL | *&42; | ^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:103:5 + --> $DIR/no_effect.rs:108:5 | LL | &6; | ^^^ error: statement with no effect - --> $DIR/no_effect.rs:104:5 + --> $DIR/no_effect.rs:109:5 | LL | (5, 6, 7); | ^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:105:5 + --> $DIR/no_effect.rs:110:5 | LL | ..; | ^^^ error: statement with no effect - --> $DIR/no_effect.rs:106:5 + --> $DIR/no_effect.rs:111:5 | LL | 5..; | ^^^^ error: statement with no effect - --> $DIR/no_effect.rs:107:5 + --> $DIR/no_effect.rs:112:5 | LL | ..5; | ^^^^ error: statement with no effect - --> $DIR/no_effect.rs:108:5 + --> $DIR/no_effect.rs:113:5 | LL | 5..6; | ^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:109:5 + --> $DIR/no_effect.rs:114:5 | LL | 5..=6; | ^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:110:5 + --> $DIR/no_effect.rs:115:5 | LL | [42, 55]; | ^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:111:5 + --> $DIR/no_effect.rs:116:5 | LL | [42, 55][1]; | ^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:112:5 + --> $DIR/no_effect.rs:117:5 | LL | (42, 55).1; | ^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:113:5 + --> $DIR/no_effect.rs:118:5 | LL | [42; 55]; | ^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:114:5 + --> $DIR/no_effect.rs:119:5 | LL | [42; 55][13]; | ^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:116:5 + --> $DIR/no_effect.rs:121:5 | LL | || x += 5; | ^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:118:5 + --> $DIR/no_effect.rs:123:5 | LL | FooString { s: s }; | ^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:119:5 + --> $DIR/no_effect.rs:124:5 | LL | let _unused = 1; | ^^^^^^^^^^^^^^^^ @@ -159,19 +159,19 @@ LL | let _unused = 1; = note: `-D clippy::no-effect-underscore-binding` implied by `-D warnings` error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:120:5 + --> $DIR/no_effect.rs:125:5 | LL | let _penguin = || println!("Some helpful closure"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:121:5 + --> $DIR/no_effect.rs:126:5 | LL | let _duck = Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:122:5 + --> $DIR/no_effect.rs:127:5 | LL | let _cat = [2, 4, 6, 8][2]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/no_mangle_with_rust_abi.fixed b/tests/ui/no_mangle_with_rust_abi.fixed deleted file mode 100644 index d18dec22a8bbe..0000000000000 --- a/tests/ui/no_mangle_with_rust_abi.fixed +++ /dev/null @@ -1,48 +0,0 @@ -// run-rustfix - -#![allow(unused)] -#![warn(clippy::no_mangle_with_rust_abi)] - -#[no_mangle] -extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} - -#[no_mangle] -pub extern "C" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} - -/// # Safety -/// This function shouldn't be called unless the horsemen are ready -#[no_mangle] -pub unsafe extern "C" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} - -/// # Safety -/// This function shouldn't be called unless the horsemen are ready -#[no_mangle] -unsafe extern "C" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} - -#[no_mangle] -extern "C" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( - arg_one: u32, - arg_two: usize, -) -> u32 { - 0 -} - -// Must not run on functions that explicitly opt in to Rust ABI with `extern "Rust"` -#[no_mangle] -#[rustfmt::skip] -extern "Rust" fn rust_abi_fn_explicit_opt_in(arg_one: u32, arg_two: usize) {} - -fn rust_abi_fn_again(arg_one: u32, arg_two: usize) {} - -#[no_mangle] -extern "C" fn c_abi_fn(arg_one: u32, arg_two: usize) {} - -extern "C" fn c_abi_fn_again(arg_one: u32, arg_two: usize) {} - -extern "C" { - fn c_abi_in_block(arg_one: u32, arg_two: usize); -} - -fn main() { - // test code goes here -} diff --git a/tests/ui/no_mangle_with_rust_abi.rs b/tests/ui/no_mangle_with_rust_abi.rs index 481e1b6d96197..b32e721110e0b 100644 --- a/tests/ui/no_mangle_with_rust_abi.rs +++ b/tests/ui/no_mangle_with_rust_abi.rs @@ -1,5 +1,3 @@ -// run-rustfix - #![allow(unused)] #![warn(clippy::no_mangle_with_rust_abi)] diff --git a/tests/ui/no_mangle_with_rust_abi.stderr b/tests/ui/no_mangle_with_rust_abi.stderr index 71517d318095e..da5d31d8f2d4c 100644 --- a/tests/ui/no_mangle_with_rust_abi.stderr +++ b/tests/ui/no_mangle_with_rust_abi.stderr @@ -1,31 +1,66 @@ -error: attribute #[no_mangle] set on a Rust ABI function - --> $DIR/no_mangle_with_rust_abi.rs:7:1 +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> $DIR/no_mangle_with_rust_abi.rs:5:1 | LL | fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` +help: set an ABI + | +LL | extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | extern "Rust" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | +++++++++++++ -error: attribute #[no_mangle] set on a Rust ABI function - --> $DIR/no_mangle_with_rust_abi.rs:10:1 +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> $DIR/no_mangle_with_rust_abi.rs:8:1 | LL | pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `pub extern "C" fn rust_abi_fn_two(arg_one: u32, arg_two: usize)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | pub extern "C" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | pub extern "Rust" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | +++++++++++++ -error: attribute #[no_mangle] set on a Rust ABI function - --> $DIR/no_mangle_with_rust_abi.rs:15:1 +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> $DIR/no_mangle_with_rust_abi.rs:13:1 | LL | pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `pub unsafe extern "C" fn rust_abi_fn_three(arg_one: u32, arg_two: usize)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | pub unsafe extern "C" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | pub unsafe extern "Rust" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | +++++++++++++ -error: attribute #[no_mangle] set on a Rust ABI function - --> $DIR/no_mangle_with_rust_abi.rs:20:1 +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> $DIR/no_mangle_with_rust_abi.rs:18:1 | LL | unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unsafe extern "C" fn rust_abi_fn_four(arg_one: u32, arg_two: usize)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | unsafe extern "C" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | unsafe extern "Rust" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | +++++++++++++ -error: attribute #[no_mangle] set on a Rust ABI function - --> $DIR/no_mangle_with_rust_abi.rs:23:1 +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> $DIR/no_mangle_with_rust_abi.rs:21:1 | LL | / fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( LL | | arg_one: u32, @@ -33,13 +68,14 @@ LL | | arg_two: usize, LL | | ) -> u32 { | |________^ | -help: try +help: set an ABI | -LL + extern "C" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( -LL + arg_one: u32, -LL + arg_two: usize, -LL ~ ) -> u32 { +LL | extern "C" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + | ++++++++++ +help: or explicitly set the default | +LL | extern "Rust" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + | +++++++++++++ error: aborting due to 5 previous errors diff --git a/tests/ui/nonminimal_bool.rs b/tests/ui/nonminimal_bool.rs index e9b4367ca653c..3b5a374b4a7b4 100644 --- a/tests/ui/nonminimal_bool.rs +++ b/tests/ui/nonminimal_bool.rs @@ -63,3 +63,32 @@ fn issue9428() { println!("foo"); } } + +fn issue_10523() { + macro_rules! a { + ($v:expr) => { + $v.is_some() + }; + } + let x: Option = None; + if !a!(x) {} +} + +fn issue_10523_1() { + macro_rules! a { + ($v:expr) => { + !$v.is_some() + }; + } + let x: Option = None; + if a!(x) {} +} + +fn issue_10523_2() { + macro_rules! a { + () => { + !None::.is_some() + }; + } + if a!() {} +} diff --git a/tests/ui/option_env_unwrap.rs b/tests/ui/option_env_unwrap.rs index 0141fb7856d06..9a56cf40d8a77 100644 --- a/tests/ui/option_env_unwrap.rs +++ b/tests/ui/option_env_unwrap.rs @@ -1,24 +1,16 @@ -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::option_env_unwrap)] #![allow(clippy::map_flatten)] -#[macro_use] -extern crate macro_rules; - -macro_rules! option_env_unwrap { - ($env: expr) => { - option_env!($env).unwrap() - }; - ($env: expr, $message: expr) => { - option_env!($env).expect($message) - }; -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { let _ = option_env!("PATH").unwrap(); let _ = option_env!("PATH").expect("environment variable PATH isn't set"); - let _ = option_env_unwrap!("PATH"); - let _ = option_env_unwrap!("PATH", "environment variable PATH isn't set"); - let _ = option_env_unwrap_external!("PATH"); - let _ = option_env_unwrap_external!("PATH", "environment variable PATH isn't set"); + let _ = inline!(option_env!($"PATH").unwrap()); + let _ = inline!(option_env!($"PATH").expect($"environment variable PATH isn't set")); + let _ = external!(option_env!($"PATH").unwrap()); + let _ = external!(option_env!($"PATH").expect($"environment variable PATH isn't set")); } diff --git a/tests/ui/option_env_unwrap.stderr b/tests/ui/option_env_unwrap.stderr index bc188a07e9e0a..7bba62686eecf 100644 --- a/tests/ui/option_env_unwrap.stderr +++ b/tests/ui/option_env_unwrap.stderr @@ -1,5 +1,5 @@ error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:18:13 + --> $DIR/option_env_unwrap.rs:10:13 | LL | let _ = option_env!("PATH").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | let _ = option_env!("PATH").unwrap(); = note: `-D clippy::option-env-unwrap` implied by `-D warnings` error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:19:13 + --> $DIR/option_env_unwrap.rs:11:13 | LL | let _ = option_env!("PATH").expect("environment variable PATH isn't set"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,46 +16,40 @@ LL | let _ = option_env!("PATH").expect("environment variable PATH isn't set = help: consider using the `env!` macro instead error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:10:9 + --> $DIR/option_env_unwrap.rs:12:21 | -LL | option_env!($env).unwrap() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | let _ = option_env_unwrap!("PATH"); - | -------------------------- in this macro invocation +LL | let _ = inline!(option_env!($"PATH").unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider using the `env!` macro instead - = note: this error originates in the macro `option_env_unwrap` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:13:9 + --> $DIR/option_env_unwrap.rs:13:21 | -LL | option_env!($env).expect($message) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | let _ = option_env_unwrap!("PATH", "environment variable PATH isn't set"); - | ----------------------------------------------------------------- in this macro invocation +LL | let _ = inline!(option_env!($"PATH").expect($"environment variable PATH isn't set")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider using the `env!` macro instead - = note: this error originates in the macro `option_env_unwrap` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:22:13 + --> $DIR/option_env_unwrap.rs:14:13 | -LL | let _ = option_env_unwrap_external!("PATH"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _ = external!(option_env!($"PATH").unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider using the `env!` macro instead - = note: this error originates in the macro `option_env_unwrap_external` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `external` (in Nightly builds, run with -Z macro-backtrace for more info) error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:23:13 + --> $DIR/option_env_unwrap.rs:15:13 | -LL | let _ = option_env_unwrap_external!("PATH", "environment variable PATH isn't set"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _ = external!(option_env!($"PATH").expect($"environment variable PATH isn't set")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider using the `env!` macro instead - = note: this error originates in the macro `option_env_unwrap_external` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `external` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 6 previous errors diff --git a/tests/ui/ptr_as_ptr.fixed b/tests/ui/ptr_as_ptr.fixed index df36a9b842bf7..ee7b998a0b2f3 100644 --- a/tests/ui/ptr_as_ptr.fixed +++ b/tests/ui/ptr_as_ptr.fixed @@ -1,16 +1,12 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::ptr_as_ptr)] -extern crate macro_rules; - -macro_rules! cast_it { - ($ptr: ident) => { - $ptr.cast::() - }; -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { let ptr: *const u32 = &42_u32; let mut_ptr: *mut u32 = &mut 42_u32; @@ -38,10 +34,10 @@ fn main() { let _: *mut i32 = mut_ptr.cast(); // Make sure the lint is triggered inside a macro - let _ = cast_it!(ptr); + let _ = inline!($ptr.cast::()); // Do not lint inside macros from external crates - let _ = macro_rules::ptr_as_ptr_cast!(ptr); + let _ = external!($ptr as *const i32); } #[clippy::msrv = "1.37"] diff --git a/tests/ui/ptr_as_ptr.rs b/tests/ui/ptr_as_ptr.rs index 302c66462d9b9..c88329ce4eccd 100644 --- a/tests/ui/ptr_as_ptr.rs +++ b/tests/ui/ptr_as_ptr.rs @@ -1,16 +1,12 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::ptr_as_ptr)] -extern crate macro_rules; - -macro_rules! cast_it { - ($ptr: ident) => { - $ptr as *const i32 - }; -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { let ptr: *const u32 = &42_u32; let mut_ptr: *mut u32 = &mut 42_u32; @@ -38,10 +34,10 @@ fn main() { let _: *mut i32 = mut_ptr as _; // Make sure the lint is triggered inside a macro - let _ = cast_it!(ptr); + let _ = inline!($ptr as *const i32); // Do not lint inside macros from external crates - let _ = macro_rules::ptr_as_ptr_cast!(ptr); + let _ = external!($ptr as *const i32); } #[clippy::msrv = "1.37"] diff --git a/tests/ui/ptr_as_ptr.stderr b/tests/ui/ptr_as_ptr.stderr index a68e1cab6d357..78d733994ac69 100644 --- a/tests/ui/ptr_as_ptr.stderr +++ b/tests/ui/ptr_as_ptr.stderr @@ -1,5 +1,5 @@ error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:18:13 + --> $DIR/ptr_as_ptr.rs:14:13 | LL | let _ = ptr as *const i32; | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` @@ -7,48 +7,45 @@ LL | let _ = ptr as *const i32; = note: `-D clippy::ptr-as-ptr` implied by `-D warnings` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:19:13 + --> $DIR/ptr_as_ptr.rs:15:13 | LL | let _ = mut_ptr as *mut i32; | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:24:17 + --> $DIR/ptr_as_ptr.rs:20:17 | LL | let _ = *ptr_ptr as *const i32; | ^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `(*ptr_ptr).cast::()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:37:25 + --> $DIR/ptr_as_ptr.rs:33:25 | LL | let _: *const i32 = ptr as *const _; | ^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:38:23 + --> $DIR/ptr_as_ptr.rs:34:23 | LL | let _: *mut i32 = mut_ptr as _; | ^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:10:9 + --> $DIR/ptr_as_ptr.rs:37:21 | -LL | $ptr as *const i32 - | ^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `$ptr.cast::()` -... -LL | let _ = cast_it!(ptr); - | ------------- in this macro invocation +LL | let _ = inline!($ptr as *const i32); + | ^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `$ptr.cast::()` | - = note: this error originates in the macro `cast_it` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:62:13 + --> $DIR/ptr_as_ptr.rs:58:13 | LL | let _ = ptr as *const i32; | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:63:13 + --> $DIR/ptr_as_ptr.rs:59:13 | LL | let _ = mut_ptr as *mut i32; | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` diff --git a/tests/ui/redundant_async_block.fixed b/tests/ui/redundant_async_block.fixed index 5f9931df45e92..d26b7a332cbbf 100644 --- a/tests/ui/redundant_async_block.fixed +++ b/tests/ui/redundant_async_block.fixed @@ -3,6 +3,8 @@ #![allow(unused)] #![warn(clippy::redundant_async_block)] +use std::future::Future; + async fn func1(n: usize) -> usize { n + 1 } @@ -62,3 +64,48 @@ fn main() { let fut = async_await_parameter_in_macro!(func2()); let fut = async_await_in_macro!(std::convert::identity); } + +#[allow(clippy::let_and_return)] +fn capture_local() -> impl Future { + // Lint + let fut = async { 17 }; + fut +} + +fn capture_local_closure(s: &str) -> impl Future { + let f = move || std::future::ready(s); + // Do not lint: `f` would not live long enough + async move { f().await } +} + +#[allow(clippy::let_and_return)] +fn capture_arg(s: &str) -> impl Future { + // Lint + let fut = async move { s }; + fut +} + +#[derive(Debug, Clone)] +struct F {} + +impl F { + async fn run(&self) {} +} + +pub async fn run() { + let f = F {}; + let c = f.clone(); + // Do not lint: `c` would not live long enough + spawn(async move { c.run().await }); + let _f = f; +} + +fn spawn(_: F) {} + +async fn work(_: &str) {} + +fn capture() { + let val = "Hello World".to_owned(); + // Do not lint: `val` would not live long enough + spawn(async { work(&{ val }).await }); +} diff --git a/tests/ui/redundant_async_block.rs b/tests/ui/redundant_async_block.rs index de3c9970c65f2..04726e6280508 100644 --- a/tests/ui/redundant_async_block.rs +++ b/tests/ui/redundant_async_block.rs @@ -3,6 +3,8 @@ #![allow(unused)] #![warn(clippy::redundant_async_block)] +use std::future::Future; + async fn func1(n: usize) -> usize { n + 1 } @@ -62,3 +64,48 @@ fn main() { let fut = async_await_parameter_in_macro!(func2()); let fut = async_await_in_macro!(std::convert::identity); } + +#[allow(clippy::let_and_return)] +fn capture_local() -> impl Future { + // Lint + let fut = async { 17 }; + async move { fut.await } +} + +fn capture_local_closure(s: &str) -> impl Future { + let f = move || std::future::ready(s); + // Do not lint: `f` would not live long enough + async move { f().await } +} + +#[allow(clippy::let_and_return)] +fn capture_arg(s: &str) -> impl Future { + // Lint + let fut = async move { s }; + async move { fut.await } +} + +#[derive(Debug, Clone)] +struct F {} + +impl F { + async fn run(&self) {} +} + +pub async fn run() { + let f = F {}; + let c = f.clone(); + // Do not lint: `c` would not live long enough + spawn(async move { c.run().await }); + let _f = f; +} + +fn spawn(_: F) {} + +async fn work(_: &str) {} + +fn capture() { + let val = "Hello World".to_owned(); + // Do not lint: `val` would not live long enough + spawn(async { work(&{ val }).await }); +} diff --git a/tests/ui/redundant_async_block.stderr b/tests/ui/redundant_async_block.stderr index b16d96dce84eb..1a1c1603e0809 100644 --- a/tests/ui/redundant_async_block.stderr +++ b/tests/ui/redundant_async_block.stderr @@ -1,5 +1,5 @@ error: this async expression only awaits a single future - --> $DIR/redundant_async_block.rs:13:13 + --> $DIR/redundant_async_block.rs:15:13 | LL | let x = async { f.await }; | ^^^^^^^^^^^^^^^^^ help: you can reduce it to: `f` @@ -7,22 +7,34 @@ LL | let x = async { f.await }; = note: `-D clippy::redundant-async-block` implied by `-D warnings` error: this async expression only awaits a single future - --> $DIR/redundant_async_block.rs:46:16 + --> $DIR/redundant_async_block.rs:48:16 | LL | let fut2 = async { fut1.await }; | ^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut1` error: this async expression only awaits a single future - --> $DIR/redundant_async_block.rs:49:16 + --> $DIR/redundant_async_block.rs:51:16 | LL | let fut2 = async move { fut1.await }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut1` error: this async expression only awaits a single future - --> $DIR/redundant_async_block.rs:51:15 + --> $DIR/redundant_async_block.rs:53:15 | LL | let fut = async { async { 42 }.await }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `async { 42 }` -error: aborting due to 4 previous errors +error: this async expression only awaits a single future + --> $DIR/redundant_async_block.rs:72:5 + | +LL | async move { fut.await } + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut` + +error: this async expression only awaits a single future + --> $DIR/redundant_async_block.rs:85:5 + | +LL | async move { fut.await } + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut` + +error: aborting due to 6 previous errors diff --git a/tests/ui/redundant_pattern_matching_result.fixed b/tests/ui/redundant_pattern_matching_result.fixed index b88c5d0bec82e..42348df4480b3 100644 --- a/tests/ui/redundant_pattern_matching_result.fixed +++ b/tests/ui/redundant_pattern_matching_result.fixed @@ -69,8 +69,8 @@ fn issue5504() { } fn try_result_opt() -> Result { - while (r#try!(result_opt())).is_some() {} - if (r#try!(result_opt())).is_some() {} + while r#try!(result_opt()).is_some() {} + if r#try!(result_opt()).is_some() {} Ok(42) } diff --git a/tests/ui/redundant_pattern_matching_result.stderr b/tests/ui/redundant_pattern_matching_result.stderr index e6afe9eb78eaa..d6a46babb7795 100644 --- a/tests/ui/redundant_pattern_matching_result.stderr +++ b/tests/ui/redundant_pattern_matching_result.stderr @@ -88,13 +88,13 @@ error: redundant pattern matching, consider using `is_some()` --> $DIR/redundant_pattern_matching_result.rs:84:19 | LL | while let Some(_) = r#try!(result_opt()) {} - | ----------^^^^^^^----------------------- help: try this: `while (r#try!(result_opt())).is_some()` + | ----------^^^^^^^----------------------- help: try this: `while r#try!(result_opt()).is_some()` error: redundant pattern matching, consider using `is_some()` --> $DIR/redundant_pattern_matching_result.rs:85:16 | LL | if let Some(_) = r#try!(result_opt()) {} - | -------^^^^^^^----------------------- help: try this: `if (r#try!(result_opt())).is_some()` + | -------^^^^^^^----------------------- help: try this: `if r#try!(result_opt()).is_some()` error: redundant pattern matching, consider using `is_some()` --> $DIR/redundant_pattern_matching_result.rs:91:12 diff --git a/tests/ui/single_match_else.rs b/tests/ui/single_match_else.rs index 5d03f77e9326e..3c86f41f3a6e7 100644 --- a/tests/ui/single_match_else.rs +++ b/tests/ui/single_match_else.rs @@ -1,9 +1,9 @@ -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![warn(clippy::single_match_else)] #![allow(clippy::needless_return, clippy::no_effect, clippy::uninlined_format_args)] -extern crate proc_macro_with_span; -use proc_macro_with_span::with_span; +extern crate proc_macros; +use proc_macros::with_span; enum ExprNode { ExprAddrOf, diff --git a/tests/ui/string_add.rs b/tests/ui/string_add.rs index 16673c01e6301..20edbe31fa9e7 100644 --- a/tests/ui/string_add.rs +++ b/tests/ui/string_add.rs @@ -1,7 +1,7 @@ -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::external; #[warn(clippy::string_add)] #[allow(clippy::string_add_assign, unused)] @@ -22,5 +22,8 @@ fn main() { x = x + 1; assert_eq!(2, x); - string_add!(); + external!({ + let y = "".to_owned(); + let z = y + "..."; + }); } diff --git a/tests/ui/swap.fixed b/tests/ui/swap.fixed index 04008c0d9b31c..9703674d1a4ed 100644 --- a/tests/ui/swap.fixed +++ b/tests/ui/swap.fixed @@ -1,4 +1,5 @@ // run-rustfix +// aux-build: macro_rules.rs #![warn(clippy::all)] #![allow( @@ -8,7 +9,8 @@ redundant_semicolons, dead_code, unused_assignments, - unused_variables + unused_variables, + clippy::let_and_return )] struct Foo(u32); @@ -186,3 +188,14 @@ const fn issue_9864(mut u: u32) -> u32 { v = temp; u + v } + +#[macro_use] +extern crate macro_rules; + +const fn issue_10421(x: u32) -> u32 { + issue_10421!(); + let a = x; + let a = a; + let a = a; + a +} diff --git a/tests/ui/swap.rs b/tests/ui/swap.rs index ef8a81c8341b1..a0228065e46b5 100644 --- a/tests/ui/swap.rs +++ b/tests/ui/swap.rs @@ -1,4 +1,5 @@ // run-rustfix +// aux-build: macro_rules.rs #![warn(clippy::all)] #![allow( @@ -8,7 +9,8 @@ redundant_semicolons, dead_code, unused_assignments, - unused_variables + unused_variables, + clippy::let_and_return )] struct Foo(u32); @@ -215,3 +217,14 @@ const fn issue_9864(mut u: u32) -> u32 { v = temp; u + v } + +#[macro_use] +extern crate macro_rules; + +const fn issue_10421(x: u32) -> u32 { + issue_10421!(); + let a = x; + let a = a; + let a = a; + a +} diff --git a/tests/ui/swap.stderr b/tests/ui/swap.stderr index 825c9261e1983..0c246268499d9 100644 --- a/tests/ui/swap.stderr +++ b/tests/ui/swap.stderr @@ -1,5 +1,5 @@ error: this looks like you are swapping `bar.a` and `bar.b` manually - --> $DIR/swap.rs:25:5 + --> $DIR/swap.rs:27:5 | LL | / let temp = bar.a; LL | | bar.a = bar.b; @@ -10,7 +10,7 @@ LL | | bar.b = temp; = note: `-D clippy::manual-swap` implied by `-D warnings` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:37:5 + --> $DIR/swap.rs:39:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -18,7 +18,7 @@ LL | | foo[1] = temp; | |__________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:46:5 + --> $DIR/swap.rs:48:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -26,7 +26,7 @@ LL | | foo[1] = temp; | |__________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:65:5 + --> $DIR/swap.rs:67:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -34,7 +34,7 @@ LL | | foo[1] = temp; | |__________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping `a` and `b` manually - --> $DIR/swap.rs:76:5 + --> $DIR/swap.rs:78:5 | LL | / a ^= b; LL | | b ^= a; @@ -42,7 +42,7 @@ LL | | a ^= b; | |___________^ help: try: `std::mem::swap(&mut a, &mut b);` error: this looks like you are swapping `bar.a` and `bar.b` manually - --> $DIR/swap.rs:84:5 + --> $DIR/swap.rs:86:5 | LL | / bar.a ^= bar.b; LL | | bar.b ^= bar.a; @@ -50,7 +50,7 @@ LL | | bar.a ^= bar.b; | |___________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b);` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:92:5 + --> $DIR/swap.rs:94:5 | LL | / foo[0] ^= foo[1]; LL | | foo[1] ^= foo[0]; @@ -58,7 +58,7 @@ LL | | foo[0] ^= foo[1]; | |_____________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping `foo[0][1]` and `bar[1][0]` manually - --> $DIR/swap.rs:121:5 + --> $DIR/swap.rs:123:5 | LL | / let temp = foo[0][1]; LL | | foo[0][1] = bar[1][0]; @@ -68,7 +68,7 @@ LL | | bar[1][0] = temp; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `a` and `b` manually - --> $DIR/swap.rs:135:7 + --> $DIR/swap.rs:137:7 | LL | ; let t = a; | _______^ @@ -79,7 +79,7 @@ LL | | b = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `c.0` and `a` manually - --> $DIR/swap.rs:144:7 + --> $DIR/swap.rs:146:7 | LL | ; let t = c.0; | _______^ @@ -90,7 +90,7 @@ LL | | a = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `b` and `a` manually - --> $DIR/swap.rs:170:5 + --> $DIR/swap.rs:172:5 | LL | / let t = b; LL | | b = a; @@ -100,7 +100,7 @@ LL | | a = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `a` and `b` - --> $DIR/swap.rs:132:5 + --> $DIR/swap.rs:134:5 | LL | / a = b; LL | | b = a; @@ -110,7 +110,7 @@ LL | | b = a; = note: `-D clippy::almost-swapped` implied by `-D warnings` error: this looks like you are trying to swap `c.0` and `a` - --> $DIR/swap.rs:141:5 + --> $DIR/swap.rs:143:5 | LL | / c.0 = a; LL | | a = c.0; @@ -119,7 +119,7 @@ LL | | a = c.0; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `a` and `b` - --> $DIR/swap.rs:148:5 + --> $DIR/swap.rs:150:5 | LL | / let a = b; LL | | let b = a; @@ -128,7 +128,7 @@ LL | | let b = a; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `d` and `c` - --> $DIR/swap.rs:153:5 + --> $DIR/swap.rs:155:5 | LL | / d = c; LL | | c = d; @@ -137,7 +137,7 @@ LL | | c = d; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `a` and `b` - --> $DIR/swap.rs:157:5 + --> $DIR/swap.rs:159:5 | LL | / let a = b; LL | | b = a; @@ -146,7 +146,7 @@ LL | | b = a; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `s.0.x` and `s.0.y` manually - --> $DIR/swap.rs:205:5 + --> $DIR/swap.rs:207:5 | LL | / let t = s.0.x; LL | | s.0.x = s.0.y; diff --git a/tests/ui/toplevel_ref_arg.fixed b/tests/ui/toplevel_ref_arg.fixed index 09fb66ca37e04..174c858a47d1f 100644 --- a/tests/ui/toplevel_ref_arg.fixed +++ b/tests/ui/toplevel_ref_arg.fixed @@ -1,17 +1,12 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::toplevel_ref_arg)] -#![allow(clippy::uninlined_format_args)] +#![allow(clippy::uninlined_format_args, unused)] -#[macro_use] -extern crate macro_rules; - -macro_rules! gen_binding { - () => { - let _y = &42; - }; -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { // Closures should not warn let y = |ref x| println!("{:?}", x); @@ -38,13 +33,8 @@ fn main() { for ref _x in 0..10 {} // lint in macro - #[allow(unused)] - { - gen_binding!(); - } + inline!(let _y = &42;); // do not lint in external macro - { - ref_arg_binding!(); - } + external!(let ref _y = 42;); } diff --git a/tests/ui/toplevel_ref_arg.rs b/tests/ui/toplevel_ref_arg.rs index 9d1f2f810983a..4b81a06112fe2 100644 --- a/tests/ui/toplevel_ref_arg.rs +++ b/tests/ui/toplevel_ref_arg.rs @@ -1,17 +1,12 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::toplevel_ref_arg)] -#![allow(clippy::uninlined_format_args)] +#![allow(clippy::uninlined_format_args, unused)] -#[macro_use] -extern crate macro_rules; - -macro_rules! gen_binding { - () => { - let ref _y = 42; - }; -} +extern crate proc_macros; +use proc_macros::{external, inline_macros}; +#[inline_macros] fn main() { // Closures should not warn let y = |ref x| println!("{:?}", x); @@ -38,13 +33,8 @@ fn main() { for ref _x in 0..10 {} // lint in macro - #[allow(unused)] - { - gen_binding!(); - } + inline!(let ref _y = 42;); // do not lint in external macro - { - ref_arg_binding!(); - } + external!(let ref _y = 42;); } diff --git a/tests/ui/toplevel_ref_arg.stderr b/tests/ui/toplevel_ref_arg.stderr index 9c853020ab019..407c2d9fcd33b 100644 --- a/tests/ui/toplevel_ref_arg.stderr +++ b/tests/ui/toplevel_ref_arg.stderr @@ -1,5 +1,5 @@ error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead - --> $DIR/toplevel_ref_arg.rs:20:9 + --> $DIR/toplevel_ref_arg.rs:15:9 | LL | let ref _x = 1; | ----^^^^^^----- help: try: `let _x = &1;` @@ -7,39 +7,36 @@ LL | let ref _x = 1; = note: `-D clippy::toplevel-ref-arg` implied by `-D warnings` error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead - --> $DIR/toplevel_ref_arg.rs:22:9 + --> $DIR/toplevel_ref_arg.rs:17:9 | LL | let ref _y: (&_, u8) = (&1, 2); | ----^^^^^^--------------------- help: try: `let _y: &(&_, u8) = &(&1, 2);` error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead - --> $DIR/toplevel_ref_arg.rs:24:9 + --> $DIR/toplevel_ref_arg.rs:19:9 | LL | let ref _z = 1 + 2; | ----^^^^^^--------- help: try: `let _z = &(1 + 2);` error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead - --> $DIR/toplevel_ref_arg.rs:26:9 + --> $DIR/toplevel_ref_arg.rs:21:9 | LL | let ref mut _z = 1 + 2; | ----^^^^^^^^^^--------- help: try: `let _z = &mut (1 + 2);` error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead - --> $DIR/toplevel_ref_arg.rs:31:9 + --> $DIR/toplevel_ref_arg.rs:26:9 | LL | let ref _x = vec![1, 2, 3]; | ----^^^^^^----------------- help: try: `let _x = &vec![1, 2, 3];` error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead - --> $DIR/toplevel_ref_arg.rs:11:13 + --> $DIR/toplevel_ref_arg.rs:36:17 | -LL | let ref _y = 42; - | ----^^^^^^------ help: try: `let _y = &42;` -... -LL | gen_binding!(); - | -------------- in this macro invocation +LL | inline!(let ref _y = 42;); + | ----^^^^^^------ help: try: `let _y = &42;` | - = note: this error originates in the macro `gen_binding` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 6 previous errors diff --git a/tests/ui/toplevel_ref_arg_non_rustfix.rs b/tests/ui/toplevel_ref_arg_non_rustfix.rs index 1a493fbce0ef3..2047593e7e4b8 100644 --- a/tests/ui/toplevel_ref_arg_non_rustfix.rs +++ b/tests/ui/toplevel_ref_arg_non_rustfix.rs @@ -1,33 +1,27 @@ -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![warn(clippy::toplevel_ref_arg)] #![allow(unused)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; fn the_answer(ref mut x: u8) { *x = 42; } -macro_rules! gen_function { - () => { - fn fun_example(ref _x: usize) {} - }; -} - +#[inline_macros] fn main() { let mut x = 0; the_answer(x); // lint in macro - #[allow(unused)] - { - gen_function!(); + inline! { + fn fun_example(ref _x: usize) {} } // do not lint in external macro - { - ref_arg_function!(); + external! { + fn fun_example2(ref _x: usize) {} } } diff --git a/tests/ui/toplevel_ref_arg_non_rustfix.stderr b/tests/ui/toplevel_ref_arg_non_rustfix.stderr index e97011c7fd51f..7307bd599d9ba 100644 --- a/tests/ui/toplevel_ref_arg_non_rustfix.stderr +++ b/tests/ui/toplevel_ref_arg_non_rustfix.stderr @@ -7,15 +7,12 @@ LL | fn the_answer(ref mut x: u8) { = note: `-D clippy::toplevel-ref-arg` implied by `-D warnings` error: `ref` directly on a function argument is ignored. Consider using a reference type instead - --> $DIR/toplevel_ref_arg_non_rustfix.rs:15:24 + --> $DIR/toplevel_ref_arg_non_rustfix.rs:20:24 | LL | fn fun_example(ref _x: usize) {} | ^^^^^^ -... -LL | gen_function!(); - | --------------- in this macro invocation | - = note: this error originates in the macro `gen_function` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 2 previous errors diff --git a/tests/ui/try_err.fixed b/tests/ui/try_err.fixed index 264194419c739..dc497b1690f54 100644 --- a/tests/ui/try_err.fixed +++ b/tests/ui/try_err.fixed @@ -1,11 +1,11 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![deny(clippy::try_err)] #![allow(clippy::unnecessary_wraps, clippy::needless_question_mark)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; use std::io; use std::task::Poll; @@ -79,36 +79,22 @@ fn nested_error() -> Result { Ok(1) } -// Bad suggestion when in macro (see #6242) -macro_rules! try_validation { - ($e: expr) => {{ - match $e { +#[inline_macros] +fn calling_macro() -> Result { + // macro + inline!( + match $(Ok::<_, i32>(5)) { Ok(_) => 0, Err(_) => return Err(1), } - }}; -} - -macro_rules! ret_one { - () => { - 1 - }; -} - -macro_rules! try_validation_in_macro { - ($e: expr) => {{ - match $e { + ); + // `Err` arg is another macro + inline!( + match $(Ok::<_, i32>(5)) { Ok(_) => 0, - Err(_) => return Err(ret_one!()), + Err(_) => return Err(inline!(1)), } - }}; -} - -fn calling_macro() -> Result { - // macro - try_validation!(Ok::<_, i32>(5)); - // `Err` arg is another macro - try_validation_in_macro!(Ok::<_, i32>(5)); + ); Ok(5) } @@ -121,24 +107,19 @@ fn main() { calling_macro().unwrap(); // We don't want to lint in external macros - try_err!(); -} - -macro_rules! bar { - () => { - String::from("aasdfasdfasdfa") - }; -} - -macro_rules! foo { - () => { - bar!() - }; + external! { + pub fn try_err_fn() -> Result { + let err: i32 = 1; + // To avoid warnings during rustfix + if true { Err(err)? } else { Ok(2) } + } + } } +#[inline_macros] pub fn macro_inside(fail: bool) -> Result { if fail { - return Err(foo!()); + return Err(inline!(inline!(String::from("aasdfasdfasdfa")))); } Ok(0) } diff --git a/tests/ui/try_err.rs b/tests/ui/try_err.rs index bc6979bf45719..86aeb75cd96a5 100644 --- a/tests/ui/try_err.rs +++ b/tests/ui/try_err.rs @@ -1,11 +1,11 @@ // run-rustfix -// aux-build:macro_rules.rs +// aux-build:proc_macros.rs #![deny(clippy::try_err)] #![allow(clippy::unnecessary_wraps, clippy::needless_question_mark)] -#[macro_use] -extern crate macro_rules; +extern crate proc_macros; +use proc_macros::{external, inline_macros}; use std::io; use std::task::Poll; @@ -79,36 +79,22 @@ fn nested_error() -> Result { Ok(1) } -// Bad suggestion when in macro (see #6242) -macro_rules! try_validation { - ($e: expr) => {{ - match $e { +#[inline_macros] +fn calling_macro() -> Result { + // macro + inline!( + match $(Ok::<_, i32>(5)) { Ok(_) => 0, Err(_) => Err(1)?, } - }}; -} - -macro_rules! ret_one { - () => { - 1 - }; -} - -macro_rules! try_validation_in_macro { - ($e: expr) => {{ - match $e { + ); + // `Err` arg is another macro + inline!( + match $(Ok::<_, i32>(5)) { Ok(_) => 0, - Err(_) => Err(ret_one!())?, + Err(_) => Err(inline!(1))?, } - }}; -} - -fn calling_macro() -> Result { - // macro - try_validation!(Ok::<_, i32>(5)); - // `Err` arg is another macro - try_validation_in_macro!(Ok::<_, i32>(5)); + ); Ok(5) } @@ -121,24 +107,19 @@ fn main() { calling_macro().unwrap(); // We don't want to lint in external macros - try_err!(); -} - -macro_rules! bar { - () => { - String::from("aasdfasdfasdfa") - }; -} - -macro_rules! foo { - () => { - bar!() - }; + external! { + pub fn try_err_fn() -> Result { + let err: i32 = 1; + // To avoid warnings during rustfix + if true { Err(err)? } else { Ok(2) } + } + } } +#[inline_macros] pub fn macro_inside(fail: bool) -> Result { if fail { - Err(foo!())?; + Err(inline!(inline!(String::from("aasdfasdfasdfa"))))?; } Ok(0) } diff --git a/tests/ui/try_err.stderr b/tests/ui/try_err.stderr index 0cb1328fbfcf0..4ad0e2e56a4d3 100644 --- a/tests/ui/try_err.stderr +++ b/tests/ui/try_err.stderr @@ -29,53 +29,47 @@ LL | Err(err)?; | ^^^^^^^^^ help: try this: `return Err(err.into())` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:87:23 + --> $DIR/try_err.rs:88:23 | LL | Err(_) => Err(1)?, | ^^^^^^^ help: try this: `return Err(1)` -... -LL | try_validation!(Ok::<_, i32>(5)); - | -------------------------------- in this macro invocation | - = note: this error originates in the macro `try_validation` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_calling_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:102:23 + --> $DIR/try_err.rs:95:23 | -LL | Err(_) => Err(ret_one!())?, - | ^^^^^^^^^^^^^^^^ help: try this: `return Err(ret_one!())` -... -LL | try_validation_in_macro!(Ok::<_, i32>(5)); - | ----------------------------------------- in this macro invocation +LL | Err(_) => Err(inline!(1))?, + | ^^^^^^^^^^^^^^^^ help: try this: `return Err(inline!(1))` | - = note: this error originates in the macro `try_validation_in_macro` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_fn_calling_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:141:9 + --> $DIR/try_err.rs:122:9 | -LL | Err(foo!())?; - | ^^^^^^^^^^^^ help: try this: `return Err(foo!())` +LL | Err(inline!(inline!(String::from("aasdfasdfasdfa"))))?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `return Err(inline!(inline!(String::from("aasdfasdfasdfa"))))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:148:9 + --> $DIR/try_err.rs:129:9 | LL | Err(io::ErrorKind::WriteZero)? | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `return Poll::Ready(Err(io::ErrorKind::WriteZero.into()))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:150:9 + --> $DIR/try_err.rs:131:9 | LL | Err(io::Error::new(io::ErrorKind::InvalidInput, "error"))? | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidInput, "error")))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:158:9 + --> $DIR/try_err.rs:139:9 | LL | Err(io::ErrorKind::NotFound)? | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `return Poll::Ready(Some(Err(io::ErrorKind::NotFound.into())))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:167:16 + --> $DIR/try_err.rs:148:16 | LL | return Err(42)?; | ^^^^^^^^ help: try this: `Err(42)` diff --git a/tests/ui/uninit.rs b/tests/ui/uninit.rs index 2113173170868..412b36b4ee8f1 100644 --- a/tests/ui/uninit.rs +++ b/tests/ui/uninit.rs @@ -3,13 +3,15 @@ use std::mem::{self, MaybeUninit}; +union MyOwnMaybeUninit { + value: u8, + uninit: (), +} + fn main() { let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; - // edge case: For now we lint on empty arrays - let _: [u8; 0] = unsafe { MaybeUninit::uninit().assume_init() }; - - // edge case: For now we accept unit tuples + // This is OK, because ZSTs do not contain data. let _: () = unsafe { MaybeUninit::uninit().assume_init() }; // This is OK, because `MaybeUninit` allows uninitialized data. @@ -21,6 +23,19 @@ fn main() { // This is OK, because all constitutent types are uninit-compatible. let _: (MaybeUninit, [MaybeUninit; 2]) = unsafe { MaybeUninit::uninit().assume_init() }; + // This is OK, because our own MaybeUninit is just as fine as the one from core. + let _: MyOwnMaybeUninit = unsafe { MaybeUninit::uninit().assume_init() }; + + // This is OK, because empty arrays don't contain data. + let _: [u8; 0] = unsafe { MaybeUninit::uninit().assume_init() }; + // Was a false negative. let _: usize = unsafe { mem::MaybeUninit::uninit().assume_init() }; + + polymorphic::<()>(); + + fn polymorphic() { + // We are conservative around polymorphic types. + let _: T = unsafe { mem::MaybeUninit::uninit().assume_init() }; + } } diff --git a/tests/ui/uninit.stderr b/tests/ui/uninit.stderr index 15ef2349489fa..9e01b9a4aa816 100644 --- a/tests/ui/uninit.stderr +++ b/tests/ui/uninit.stderr @@ -1,5 +1,5 @@ error: this call for this type may be undefined behavior - --> $DIR/uninit.rs:7:29 + --> $DIR/uninit.rs:12:29 | LL | let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,15 +7,15 @@ LL | let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; = note: `#[deny(clippy::uninit_assumed_init)]` on by default error: this call for this type may be undefined behavior - --> $DIR/uninit.rs:10:31 + --> $DIR/uninit.rs:33:29 | -LL | let _: [u8; 0] = unsafe { MaybeUninit::uninit().assume_init() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: usize = unsafe { mem::MaybeUninit::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call for this type may be undefined behavior - --> $DIR/uninit.rs:25:29 + --> $DIR/uninit.rs:39:29 | -LL | let _: usize = unsafe { mem::MaybeUninit::uninit().assume_init() }; +LL | let _: T = unsafe { mem::MaybeUninit::uninit().assume_init() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 3 previous errors diff --git a/tests/ui/uninit_vec.rs b/tests/ui/uninit_vec.rs index 194e4fc157ef1..59ec64a7ab1b9 100644 --- a/tests/ui/uninit_vec.rs +++ b/tests/ui/uninit_vec.rs @@ -7,6 +7,11 @@ struct MyVec { vec: Vec, } +union MyOwnMaybeUninit { + value: u8, + uninit: (), +} + fn main() { // with_capacity() -> set_len() should be detected let mut vec: Vec = Vec::with_capacity(1000); @@ -97,4 +102,26 @@ fn main() { unsafe { vec.set_len(0); } + + // ZSTs should not be detected + let mut vec: Vec<()> = Vec::with_capacity(1000); + unsafe { + vec.set_len(10); + } + + // unions should not be detected + let mut vec: Vec = Vec::with_capacity(1000); + unsafe { + vec.set_len(10); + } + + polymorphic::<()>(); + + fn polymorphic() { + // We are conservative around polymorphic types. + let mut vec: Vec = Vec::with_capacity(1000); + unsafe { + vec.set_len(10); + } + } } diff --git a/tests/ui/uninit_vec.stderr b/tests/ui/uninit_vec.stderr index 77fc689f07635..9cdf0c95ad9f1 100644 --- a/tests/ui/uninit_vec.stderr +++ b/tests/ui/uninit_vec.stderr @@ -1,5 +1,5 @@ error: calling `set_len()` immediately after reserving a buffer creates uninitialized values - --> $DIR/uninit_vec.rs:12:5 + --> $DIR/uninit_vec.rs:17:5 | LL | let mut vec: Vec = Vec::with_capacity(1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -11,7 +11,7 @@ LL | vec.set_len(200); = note: `-D clippy::uninit-vec` implied by `-D warnings` error: calling `set_len()` immediately after reserving a buffer creates uninitialized values - --> $DIR/uninit_vec.rs:18:5 + --> $DIR/uninit_vec.rs:23:5 | LL | vec.reserve(1000); | ^^^^^^^^^^^^^^^^^^ @@ -22,7 +22,7 @@ LL | vec.set_len(200); = help: initialize the buffer or wrap the content in `MaybeUninit` error: calling `set_len()` on empty `Vec` creates out-of-bound values - --> $DIR/uninit_vec.rs:24:5 + --> $DIR/uninit_vec.rs:29:5 | LL | let mut vec: Vec = Vec::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -31,7 +31,7 @@ LL | vec.set_len(200); | ^^^^^^^^^^^^^^^^ error: calling `set_len()` on empty `Vec` creates out-of-bound values - --> $DIR/uninit_vec.rs:30:5 + --> $DIR/uninit_vec.rs:35:5 | LL | let mut vec: Vec = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | vec.set_len(200); | ^^^^^^^^^^^^^^^^ error: calling `set_len()` on empty `Vec` creates out-of-bound values - --> $DIR/uninit_vec.rs:35:5 + --> $DIR/uninit_vec.rs:40:5 | LL | let mut vec: Vec = Vec::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL | vec.set_len(200); | ^^^^^^^^^^^^^^^^ error: calling `set_len()` immediately after reserving a buffer creates uninitialized values - --> $DIR/uninit_vec.rs:49:5 + --> $DIR/uninit_vec.rs:54:5 | LL | let mut vec: Vec = Vec::with_capacity(1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -60,7 +60,7 @@ LL | vec.set_len(200); = help: initialize the buffer or wrap the content in `MaybeUninit` error: calling `set_len()` immediately after reserving a buffer creates uninitialized values - --> $DIR/uninit_vec.rs:58:5 + --> $DIR/uninit_vec.rs:63:5 | LL | my_vec.vec.reserve(1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -71,7 +71,7 @@ LL | my_vec.vec.set_len(200); = help: initialize the buffer or wrap the content in `MaybeUninit` error: calling `set_len()` immediately after reserving a buffer creates uninitialized values - --> $DIR/uninit_vec.rs:63:5 + --> $DIR/uninit_vec.rs:68:5 | LL | my_vec.vec = Vec::with_capacity(1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -82,7 +82,7 @@ LL | my_vec.vec.set_len(200); = help: initialize the buffer or wrap the content in `MaybeUninit` error: calling `set_len()` immediately after reserving a buffer creates uninitialized values - --> $DIR/uninit_vec.rs:42:9 + --> $DIR/uninit_vec.rs:47:9 | LL | let mut vec: Vec = Vec::with_capacity(1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -92,7 +92,7 @@ LL | vec.set_len(200); = help: initialize the buffer or wrap the content in `MaybeUninit` error: calling `set_len()` immediately after reserving a buffer creates uninitialized values - --> $DIR/uninit_vec.rs:45:9 + --> $DIR/uninit_vec.rs:50:9 | LL | vec.reserve(1000); | ^^^^^^^^^^^^^^^^^^ @@ -101,5 +101,16 @@ LL | vec.set_len(200); | = help: initialize the buffer or wrap the content in `MaybeUninit` -error: aborting due to 10 previous errors +error: calling `set_len()` immediately after reserving a buffer creates uninitialized values + --> $DIR/uninit_vec.rs:122:9 + | +LL | let mut vec: Vec = Vec::with_capacity(1000); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { +LL | vec.set_len(10); + | ^^^^^^^^^^^^^^^ + | + = help: initialize the buffer or wrap the content in `MaybeUninit` + +error: aborting due to 11 previous errors diff --git a/tests/ui/uninlined_format_args.fixed b/tests/ui/uninlined_format_args.fixed index cbd5cc5fceef5..1475d781c6734 100644 --- a/tests/ui/uninlined_format_args.fixed +++ b/tests/ui/uninlined_format_args.fixed @@ -1,11 +1,11 @@ -// aux-build:proc_macro_with_span.rs +// aux-build:proc_macros.rs // run-rustfix #![warn(clippy::uninlined_format_args)] #![allow(named_arguments_used_positionally, unused_imports, unused_macros, unused_variables)] #![allow(clippy::eq_op, clippy::format_in_format_args, clippy::print_literal)] -extern crate proc_macro_with_span; -use proc_macro_with_span::with_span; +extern crate proc_macros; +use proc_macros::with_span; macro_rules! no_param_str { () => { diff --git a/tests/ui/uninlined_format_args.rs b/tests/ui/uninlined_format_args.rs index cf0ea5be4813f..835afac393fae 100644 --- a/tests/ui/uninlined_format_args.rs +++ b/tests/ui/uninlined_format_args.rs @@ -1,11 +1,11 @@ -// aux-build:proc_macro_with_span.rs +// aux-build:proc_macros.rs // run-rustfix #![warn(clippy::uninlined_format_args)] #![allow(named_arguments_used_positionally, unused_imports, unused_macros, unused_variables)] #![allow(clippy::eq_op, clippy::format_in_format_args, clippy::print_literal)] -extern crate proc_macro_with_span; -use proc_macro_with_span::with_span; +extern crate proc_macros; +use proc_macros::with_span; macro_rules! no_param_str { () => { diff --git a/tests/ui/unit_arg.rs b/tests/ui/unit_arg.rs index 07e70873a8132..674ae4f1df97e 100644 --- a/tests/ui/unit_arg.rs +++ b/tests/ui/unit_arg.rs @@ -1,4 +1,4 @@ -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![warn(clippy::unit_arg)] #![allow(unused_must_use, unused_variables)] #![allow( @@ -13,9 +13,9 @@ clippy::unused_unit )] -extern crate proc_macro_with_span; +extern crate proc_macros; -use proc_macro_with_span::with_span; +use proc_macros::with_span; use std::fmt::Debug; fn foo(t: T) { diff --git a/tests/ui/unnecessary_lazy_eval.fixed b/tests/ui/unnecessary_lazy_eval.fixed index 22e9bd8bdc510..3b93800f8b750 100644 --- a/tests/ui/unnecessary_lazy_eval.fixed +++ b/tests/ui/unnecessary_lazy_eval.fixed @@ -1,12 +1,12 @@ // run-rustfix -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![warn(clippy::unnecessary_lazy_evaluations)] #![allow(clippy::redundant_closure)] #![allow(clippy::bind_instead_of_map)] #![allow(clippy::map_identity)] -extern crate proc_macro_with_span; -use proc_macro_with_span::with_span; +extern crate proc_macros; +use proc_macros::with_span; struct Deep(Option); diff --git a/tests/ui/unnecessary_lazy_eval.rs b/tests/ui/unnecessary_lazy_eval.rs index 8726d84a23fcf..2851c0c519049 100644 --- a/tests/ui/unnecessary_lazy_eval.rs +++ b/tests/ui/unnecessary_lazy_eval.rs @@ -1,12 +1,12 @@ // run-rustfix -// aux-build: proc_macro_with_span.rs +// aux-build: proc_macros.rs #![warn(clippy::unnecessary_lazy_evaluations)] #![allow(clippy::redundant_closure)] #![allow(clippy::bind_instead_of_map)] #![allow(clippy::map_identity)] -extern crate proc_macro_with_span; -use proc_macro_with_span::with_span; +extern crate proc_macros; +use proc_macros::with_span; struct Deep(Option); diff --git a/tests/ui/unnecessary_operation.fixed b/tests/ui/unnecessary_operation.fixed index 65d9c910b828d..b046694f8c6f6 100644 --- a/tests/ui/unnecessary_operation.fixed +++ b/tests/ui/unnecessary_operation.fixed @@ -1,6 +1,12 @@ // run-rustfix -#![allow(clippy::deref_addrof, dead_code, unused, clippy::no_effect)] +#![allow( + clippy::deref_addrof, + dead_code, + unused, + clippy::no_effect, + clippy::unnecessary_struct_initialization +)] #![warn(clippy::unnecessary_operation)] struct Tuple(i32); diff --git a/tests/ui/unnecessary_operation.rs b/tests/ui/unnecessary_operation.rs index 4e2acd59f04aa..9ed9679e938b0 100644 --- a/tests/ui/unnecessary_operation.rs +++ b/tests/ui/unnecessary_operation.rs @@ -1,6 +1,12 @@ // run-rustfix -#![allow(clippy::deref_addrof, dead_code, unused, clippy::no_effect)] +#![allow( + clippy::deref_addrof, + dead_code, + unused, + clippy::no_effect, + clippy::unnecessary_struct_initialization +)] #![warn(clippy::unnecessary_operation)] struct Tuple(i32); diff --git a/tests/ui/unnecessary_operation.stderr b/tests/ui/unnecessary_operation.stderr index 44cf2e01ff739..a1d0d93998a6c 100644 --- a/tests/ui/unnecessary_operation.stderr +++ b/tests/ui/unnecessary_operation.stderr @@ -1,5 +1,5 @@ error: unnecessary operation - --> $DIR/unnecessary_operation.rs:50:5 + --> $DIR/unnecessary_operation.rs:56:5 | LL | Tuple(get_number()); | ^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `get_number();` @@ -7,103 +7,103 @@ LL | Tuple(get_number()); = note: `-D clippy::unnecessary-operation` implied by `-D warnings` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:51:5 + --> $DIR/unnecessary_operation.rs:57:5 | LL | Struct { field: get_number() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:52:5 + --> $DIR/unnecessary_operation.rs:58:5 | LL | Struct { ..get_struct() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `get_struct();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:53:5 + --> $DIR/unnecessary_operation.rs:59:5 | LL | Enum::Tuple(get_number()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:54:5 + --> $DIR/unnecessary_operation.rs:60:5 | LL | Enum::Struct { field: get_number() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:55:5 + --> $DIR/unnecessary_operation.rs:61:5 | LL | 5 + get_number(); | ^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5;get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:56:5 + --> $DIR/unnecessary_operation.rs:62:5 | LL | *&get_number(); | ^^^^^^^^^^^^^^^ help: statement can be reduced to: `get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:57:5 + --> $DIR/unnecessary_operation.rs:63:5 | LL | &get_number(); | ^^^^^^^^^^^^^^ help: statement can be reduced to: `get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:58:5 + --> $DIR/unnecessary_operation.rs:64:5 | LL | (5, 6, get_number()); | ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5;6;get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:59:5 + --> $DIR/unnecessary_operation.rs:65:5 | LL | get_number()..; | ^^^^^^^^^^^^^^^ help: statement can be reduced to: `get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:60:5 + --> $DIR/unnecessary_operation.rs:66:5 | LL | ..get_number(); | ^^^^^^^^^^^^^^^ help: statement can be reduced to: `get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:61:5 + --> $DIR/unnecessary_operation.rs:67:5 | LL | 5..get_number(); | ^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5;get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:62:5 + --> $DIR/unnecessary_operation.rs:68:5 | LL | [42, get_number()]; | ^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42;get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:63:5 + --> $DIR/unnecessary_operation.rs:69:5 | LL | [42, 55][get_usize()]; | ^^^^^^^^^^^^^^^^^^^^^^ help: statement can be written as: `assert!([42, 55].len() > get_usize());` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:64:5 + --> $DIR/unnecessary_operation.rs:70:5 | LL | (42, get_number()).1; | ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42;get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:65:5 + --> $DIR/unnecessary_operation.rs:71:5 | LL | [get_number(); 55]; | ^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:66:5 + --> $DIR/unnecessary_operation.rs:72:5 | LL | [42; 55][get_usize()]; | ^^^^^^^^^^^^^^^^^^^^^^ help: statement can be written as: `assert!([42; 55].len() > get_usize());` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:67:5 + --> $DIR/unnecessary_operation.rs:73:5 | LL | / { LL | | get_number() @@ -111,7 +111,7 @@ LL | | }; | |______^ help: statement can be reduced to: `get_number();` error: unnecessary operation - --> $DIR/unnecessary_operation.rs:70:5 + --> $DIR/unnecessary_operation.rs:76:5 | LL | / FooString { LL | | s: String::from("blah"), diff --git a/tests/ui/unnecessary_struct_initialization.fixed b/tests/ui/unnecessary_struct_initialization.fixed new file mode 100644 index 0000000000000..b47129e4a36a8 --- /dev/null +++ b/tests/ui/unnecessary_struct_initialization.fixed @@ -0,0 +1,73 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::unnecessary_struct_initialization)] + +struct S { + f: String, +} + +#[derive(Clone, Copy)] +struct T { + f: u32, +} + +struct U { + f: u32, +} + +impl Clone for U { + fn clone(&self) -> Self { + // Do not lint: `Self` does not implement `Copy` + Self { ..*self } + } +} + +#[derive(Copy)] +struct V { + f: u32, +} + +impl Clone for V { + fn clone(&self) -> Self { + // Lint: `Self` implements `Copy` + *self + } +} + +fn main() { + // Should lint: `a` would be consumed anyway + let a = S { f: String::from("foo") }; + let mut b = a; + + // Should lint: `b` would be consumed, and is mutable + let c = &mut b; + + // Should not lint as `d` is not mutable + let d = S { f: String::from("foo") }; + let e = &mut S { ..d }; + + // Should lint as `f` would be consumed anyway + let f = S { f: String::from("foo") }; + let g = &f; + + // Should lint: the result of an expression is mutable + let h = &mut *Box::new(S { f: String::from("foo") }); + + // Should not lint: `m` would be both alive and borrowed + let m = T { f: 17 }; + let n = &T { ..m }; + + // Should not lint: `m` should not be modified + let o = &mut T { ..m }; + o.f = 32; + assert_eq!(m.f, 17); + + // Should not lint: `m` should not be modified + let o = &mut T { ..m } as *mut T; + unsafe { &mut *o }.f = 32; + assert_eq!(m.f, 17); + + // Should lint: the result of an expression is mutable and temporary + let p = &mut *Box::new(T { f: 5 }); +} diff --git a/tests/ui/unnecessary_struct_initialization.rs b/tests/ui/unnecessary_struct_initialization.rs new file mode 100644 index 0000000000000..63b11c626e5bf --- /dev/null +++ b/tests/ui/unnecessary_struct_initialization.rs @@ -0,0 +1,77 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::unnecessary_struct_initialization)] + +struct S { + f: String, +} + +#[derive(Clone, Copy)] +struct T { + f: u32, +} + +struct U { + f: u32, +} + +impl Clone for U { + fn clone(&self) -> Self { + // Do not lint: `Self` does not implement `Copy` + Self { ..*self } + } +} + +#[derive(Copy)] +struct V { + f: u32, +} + +impl Clone for V { + fn clone(&self) -> Self { + // Lint: `Self` implements `Copy` + Self { ..*self } + } +} + +fn main() { + // Should lint: `a` would be consumed anyway + let a = S { f: String::from("foo") }; + let mut b = S { ..a }; + + // Should lint: `b` would be consumed, and is mutable + let c = &mut S { ..b }; + + // Should not lint as `d` is not mutable + let d = S { f: String::from("foo") }; + let e = &mut S { ..d }; + + // Should lint as `f` would be consumed anyway + let f = S { f: String::from("foo") }; + let g = &S { ..f }; + + // Should lint: the result of an expression is mutable + let h = &mut S { + ..*Box::new(S { f: String::from("foo") }) + }; + + // Should not lint: `m` would be both alive and borrowed + let m = T { f: 17 }; + let n = &T { ..m }; + + // Should not lint: `m` should not be modified + let o = &mut T { ..m }; + o.f = 32; + assert_eq!(m.f, 17); + + // Should not lint: `m` should not be modified + let o = &mut T { ..m } as *mut T; + unsafe { &mut *o }.f = 32; + assert_eq!(m.f, 17); + + // Should lint: the result of an expression is mutable and temporary + let p = &mut T { + ..*Box::new(T { f: 5 }) + }; +} diff --git a/tests/ui/unnecessary_struct_initialization.stderr b/tests/ui/unnecessary_struct_initialization.stderr new file mode 100644 index 0000000000000..ca497057702f6 --- /dev/null +++ b/tests/ui/unnecessary_struct_initialization.stderr @@ -0,0 +1,46 @@ +error: unnecessary struct building + --> $DIR/unnecessary_struct_initialization.rs:34:9 + | +LL | Self { ..*self } + | ^^^^^^^^^^^^^^^^ help: replace with: `*self` + | + = note: `-D clippy::unnecessary-struct-initialization` implied by `-D warnings` + +error: unnecessary struct building + --> $DIR/unnecessary_struct_initialization.rs:41:17 + | +LL | let mut b = S { ..a }; + | ^^^^^^^^^ help: replace with: `a` + +error: unnecessary struct building + --> $DIR/unnecessary_struct_initialization.rs:44:18 + | +LL | let c = &mut S { ..b }; + | ^^^^^^^^^ help: replace with: `b` + +error: unnecessary struct building + --> $DIR/unnecessary_struct_initialization.rs:52:14 + | +LL | let g = &S { ..f }; + | ^^^^^^^^^ help: replace with: `f` + +error: unnecessary struct building + --> $DIR/unnecessary_struct_initialization.rs:55:18 + | +LL | let h = &mut S { + | __________________^ +LL | | ..*Box::new(S { f: String::from("foo") }) +LL | | }; + | |_____^ help: replace with: `*Box::new(S { f: String::from("foo") })` + +error: unnecessary struct building + --> $DIR/unnecessary_struct_initialization.rs:74:18 + | +LL | let p = &mut T { + | __________________^ +LL | | ..*Box::new(T { f: 5 }) +LL | | }; + | |_____^ help: replace with: `*Box::new(T { f: 5 })` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/unnecessary_unsafety_doc.rs b/tests/ui/unnecessary_unsafety_doc.rs index c160e31afd33b..431093ab36969 100644 --- a/tests/ui/unnecessary_unsafety_doc.rs +++ b/tests/ui/unnecessary_unsafety_doc.rs @@ -1,10 +1,10 @@ -// aux-build:doc_unsafe_macros.rs +// aux-build:proc_macros.rs #![allow(clippy::let_unit_value)] #![warn(clippy::unnecessary_safety_doc)] -#[macro_use] -extern crate doc_unsafe_macros; +extern crate proc_macros; +use proc_macros::external; /// This is has no safety section, and does not need one either pub fn destroy_the_planet() { @@ -129,7 +129,11 @@ macro_rules! very_safe { very_safe!(); // we don't lint code from external macros -undocd_safe!(); +external!( + pub fn vey_oy() { + unimplemented!(); + } +); fn main() {} diff --git a/tests/ui/unnecessary_unsafety_doc.stderr b/tests/ui/unnecessary_unsafety_doc.stderr index 72898c93fa113..b0f20fdac5fa4 100644 --- a/tests/ui/unnecessary_unsafety_doc.stderr +++ b/tests/ui/unnecessary_unsafety_doc.stderr @@ -42,7 +42,7 @@ LL | very_safe!(); = note: this error originates in the macro `very_safe` (in Nightly builds, run with -Z macro-backtrace for more info) error: docs for safe trait have unnecessary `# Safety` section - --> $DIR/unnecessary_unsafety_doc.rs:147:1 + --> $DIR/unnecessary_unsafety_doc.rs:151:1 | LL | pub trait DocumentedSafeTraitWithImplementationHeader { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/triagebot.toml b/triagebot.toml index 6f50ef932e112..3f8f6a7b98c21 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -17,6 +17,7 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB [assign.owners] "/.github" = ["@flip1995"] +"/util/gh-pages" = ["@xFrednet"] "*" = [ "@flip1995", "@Manishearth", From 982c81ad2847f2736bc907b806b620ec2ac8fd11 Mon Sep 17 00:00:00 2001 From: FixedThink <41233185+FixedThink@users.noreply.github.com> Date: Fri, 24 Mar 2023 16:08:27 +0100 Subject: [PATCH 17/86] docs fix: unknown field `allowed-locales` changelog: fix: Replace the nonexistent `allowed-locales` in the `DISALLOWED_SCRIPT_IDENTS` docs with `allowed-scripts`. --- clippy_lints/src/disallowed_script_idents.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index 084190f00132c..c9fad98e43730 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -32,7 +32,7 @@ declare_clippy_lint! { /// ### Example /// ```rust /// // Assuming that `clippy.toml` contains the following line: - /// // allowed-locales = ["Latin", "Cyrillic"] + /// // allowed-scripts = ["Latin", "Cyrillic"] /// let counter = 10; // OK, latin is allowed. /// let счётчик = 10; // OK, cyrillic is allowed. /// let zähler = 10; // OK, it's still latin. From b13f99c7e75e8769865dfe896b8ac3d2bb28cfb1 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 24 Mar 2023 15:29:30 +0000 Subject: [PATCH 18/86] Fix allow attribute, items from macros in `items_after_statements` --- clippy_lints/src/items_after_statements.rs | 31 ++++++++++--------- clippy_lints/src/lib.rs | 2 +- ..._statement.rs => items_after_statement.rs} | 17 ++++++++++ ...nt.stderr => items_after_statement.stderr} | 6 ++-- 4 files changed, 37 insertions(+), 19 deletions(-) rename tests/ui/{item_after_statement.rs => items_after_statement.rs} (69%) rename tests/ui/{item_after_statement.stderr => items_after_statement.stderr} (87%) diff --git a/clippy_lints/src/items_after_statements.rs b/clippy_lints/src/items_after_statements.rs index 46d439b4497e1..a7ec57e28505b 100644 --- a/clippy_lints/src/items_after_statements.rs +++ b/clippy_lints/src/items_after_statements.rs @@ -1,8 +1,8 @@ //! lint when items are used after statements -use clippy_utils::diagnostics::span_lint; -use rustc_ast::ast::{Block, ItemKind, StmtKind}; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use clippy_utils::diagnostics::span_lint_hir; +use rustc_hir::{Block, ItemKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -52,33 +52,34 @@ declare_clippy_lint! { declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]); -impl EarlyLintPass for ItemsAfterStatements { - fn check_block(&mut self, cx: &EarlyContext<'_>, item: &Block) { - if in_external_macro(cx.sess(), item.span) { +impl LateLintPass<'_> for ItemsAfterStatements { + fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) { + if in_external_macro(cx.sess(), block.span) { return; } - // skip initial items and trailing semicolons - let stmts = item + // skip initial items + let stmts = block .stmts .iter() - .map(|stmt| &stmt.kind) - .skip_while(|s| matches!(**s, StmtKind::Item(..) | StmtKind::Empty)); + .skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..))); // lint on all further items for stmt in stmts { - if let StmtKind::Item(ref it) = *stmt { - if in_external_macro(cx.sess(), it.span) { + if let StmtKind::Item(item_id) = stmt.kind { + let item = cx.tcx.hir().item(item_id); + if in_external_macro(cx.sess(), item.span) || !item.span.eq_ctxt(block.span) { return; } - if let ItemKind::MacroDef(..) = it.kind { + if let ItemKind::Macro(..) = item.kind { // do not lint `macro_rules`, but continue processing further statements continue; } - span_lint( + span_lint_hir( cx, ITEMS_AFTER_STATEMENTS, - it.span, + item.hir_id(), + item.span, "adding items after statements is confusing, since items exist from the \ start of the scope", ); diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c9210bf73f896..f6cea7e2daf6d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -747,7 +747,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(unused_unit::UnusedUnit)); store.register_late_pass(|_| Box::new(returns::Return)); store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf)); - store.register_early_pass(|| Box::new(items_after_statements::ItemsAfterStatements)); + store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements)); store.register_early_pass(|| Box::new(precedence::Precedence)); store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)); store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue)); diff --git a/tests/ui/item_after_statement.rs b/tests/ui/items_after_statement.rs similarity index 69% rename from tests/ui/item_after_statement.rs rename to tests/ui/items_after_statement.rs index 5e92dcab1f5a2..f12cb8f22e270 100644 --- a/tests/ui/item_after_statement.rs +++ b/tests/ui/items_after_statement.rs @@ -51,3 +51,20 @@ fn semicolon() { let _ = S::new(3); } + +fn item_from_macro() { + macro_rules! static_assert_size { + ($ty:ty, $size:expr) => { + const _: [(); $size] = [(); ::std::mem::size_of::<$ty>()]; + }; + } + + let _ = 1; + static_assert_size!(u32, 4); +} + +fn allow_attribute() { + let _ = 1; + #[allow(clippy::items_after_statements)] + const _: usize = 1; +} diff --git a/tests/ui/item_after_statement.stderr b/tests/ui/items_after_statement.stderr similarity index 87% rename from tests/ui/item_after_statement.stderr rename to tests/ui/items_after_statement.stderr index 2523c53ac53a1..f69635a977bd7 100644 --- a/tests/ui/item_after_statement.stderr +++ b/tests/ui/items_after_statement.stderr @@ -1,5 +1,5 @@ error: adding items after statements is confusing, since items exist from the start of the scope - --> $DIR/item_after_statement.rs:13:5 + --> $DIR/items_after_statement.rs:13:5 | LL | / fn foo() { LL | | println!("foo"); @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::items-after-statements` implied by `-D warnings` error: adding items after statements is confusing, since items exist from the start of the scope - --> $DIR/item_after_statement.rs:20:5 + --> $DIR/items_after_statement.rs:20:5 | LL | / fn foo() { LL | | println!("foo"); @@ -17,7 +17,7 @@ LL | | } | |_____^ error: adding items after statements is confusing, since items exist from the start of the scope - --> $DIR/item_after_statement.rs:33:13 + --> $DIR/items_after_statement.rs:33:13 | LL | / fn say_something() { LL | | println!("something"); From b782a071ad06bbf52168c34f4c51912fd039d912 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Wed, 22 Mar 2023 20:13:57 +0100 Subject: [PATCH 19/86] Add explanation on how to run `cargo-clippy` and `clippy-driver` --- book/src/development/adding_lints.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index f57dc627dce4c..0f5ee7103de1a 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -18,6 +18,7 @@ because that's clearly a non-descriptive name. - [Cargo lints](#cargo-lints) - [Rustfix tests](#rustfix-tests) - [Testing manually](#testing-manually) + - [Running directly](#running-directly) - [Lint declaration](#lint-declaration) - [Lint registration](#lint-registration) - [Lint passes](#lint-passes) @@ -186,6 +187,15 @@ cargo dev lint input.rs from the working copy root. With tests in place, let's have a look at implementing our lint now. +## Running directly + +While it's easier to just use `cargo dev lint`, it might be desirable to get +`target/release/cargo-clippy` and `target/release/clippy-driver` to work as well in some cases. +By default, they don't work because clippy dynamically links rustc. To help them find rustc, +add the path printed by`rustc --print target-libdir` (ran inside this workspace so that the rustc version matches) +to your library search path. +On linux, this can be done by setting the `LD_LIBRARY_PATH` environment variable to that path. + ## Lint declaration Let's start by opening the new file created in the `clippy_lints` crate at From 52c4dc65d942efd2c74e8c9b3334ee268a8522ae Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Tue, 14 Mar 2023 09:15:23 +0800 Subject: [PATCH 20/86] fix [`cast_possible_truncation`] offering wrong suggestion when casting from float suggest `try_into` when casting to wildcard type; fix [`cast_possible_truncation`] suggesting useless parenthesis; remove suggesting for float to float conversion in [`cast_possible_truncation`] style nit --- .../src/casts/cast_possible_truncation.rs | 42 +++++-- tests/ui/cast.rs | 6 + tests/ui/cast.stderr | 112 ++++++++++-------- 3 files changed, 100 insertions(+), 60 deletions(-) diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 823970e35abbd..95c2ecbf791b5 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -2,8 +2,9 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::expr_or_init; use clippy_utils::source::snippet; +use clippy_utils::sugg::Sugg; use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize}; -use rustc_errors::{Applicability, SuggestionStyle}; +use rustc_errors::{Applicability, Diagnostic, SuggestionStyle}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -163,19 +164,34 @@ pub(super) fn check( _ => return, }; - let name_of_cast_from = snippet(cx, cast_expr.span, ".."); - let cast_to_snip = snippet(cx, cast_to_span, ".."); - let suggestion = format!("{cast_to_snip}::try_from({name_of_cast_from})"); - span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg, |diag| { diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ..."); - diag.span_suggestion_with_style( - expr.span, - "... or use `try_from` and handle the error accordingly", - suggestion, - Applicability::Unspecified, - // always show the suggestion in a separate line - SuggestionStyle::ShowAlways, - ); + if !cast_from.is_floating_point() { + offer_suggestion(cx, expr, cast_expr, cast_to_span, diag); + } }); } + +fn offer_suggestion( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_expr: &Expr<'_>, + cast_to_span: Span, + diag: &mut Diagnostic, +) { + let cast_to_snip = snippet(cx, cast_to_span, ".."); + let suggestion = if cast_to_snip == "_" { + format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_par()) + } else { + format!("{cast_to_snip}::try_from({})", Sugg::hir(cx, cast_expr, "..")) + }; + + diag.span_suggestion_with_style( + expr.span, + "... or use `try_from` and handle the error accordingly", + suggestion, + Applicability::Unspecified, + // always show the suggestion in a separate line + SuggestionStyle::ShowAlways, + ); +} diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index 8b2673c2a7fdb..a86b85706a345 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -29,6 +29,12 @@ fn main() { 1f64 as isize; 1f64 as usize; 1f32 as u32 as u16; + { + let _x: i8 = 1i32 as _; + 1f32 as i32; + 1f64 as i32; + 1f32 as u8; + } // Test clippy::cast_possible_wrap 1u8 as i8; 1u16 as i16; diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 451078de23b22..65ecf1aa37aaa 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -44,10 +44,6 @@ LL | 1f32 as i32; | = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` -help: ... or use `try_from` and handle the error accordingly - | -LL | i32::try_from(1f32); - | ~~~~~~~~~~~~~~~~~~~ error: casting `f32` to `u32` may truncate the value --> $DIR/cast.rs:25:5 @@ -56,10 +52,6 @@ LL | 1f32 as u32; | ^^^^^^^^^^^ | = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL | u32::try_from(1f32); - | ~~~~~~~~~~~~~~~~~~~ error: casting `f32` to `u32` may lose the sign of the value --> $DIR/cast.rs:25:5 @@ -76,10 +68,6 @@ LL | 1f64 as f32; | ^^^^^^^^^^^ | = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL | f32::try_from(1f64); - | ~~~~~~~~~~~~~~~~~~~ error: casting `i32` to `i8` may truncate the value --> $DIR/cast.rs:27:5 @@ -112,10 +100,6 @@ LL | 1f64 as isize; | ^^^^^^^^^^^^^ | = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL | isize::try_from(1f64); - | ~~~~~~~~~~~~~~~~~~~~~ error: casting `f64` to `usize` may truncate the value --> $DIR/cast.rs:30:5 @@ -124,10 +108,6 @@ LL | 1f64 as usize; | ^^^^^^^^^^^^^ | = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL | usize::try_from(1f64); - | ~~~~~~~~~~~~~~~~~~~~~ error: casting `f64` to `usize` may lose the sign of the value --> $DIR/cast.rs:30:5 @@ -154,10 +134,6 @@ LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ | = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... -help: ... or use `try_from` and handle the error accordingly - | -LL | u32::try_from(1f32) as u16; - | ~~~~~~~~~~~~~~~~~~~ error: casting `f32` to `u32` may lose the sign of the value --> $DIR/cast.rs:31:5 @@ -165,8 +141,50 @@ error: casting `f32` to `u32` may lose the sign of the value LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ +error: casting `i32` to `i8` may truncate the value + --> $DIR/cast.rs:33:22 + | +LL | let _x: i8 = 1i32 as _; + | ^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | let _x: i8 = 1i32.try_into(); + | ~~~~~~~~~~~~~~~ + +error: casting `f32` to `i32` may truncate the value + --> $DIR/cast.rs:34:9 + | +LL | 1f32 as i32; + | ^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... + +error: casting `f64` to `i32` may truncate the value + --> $DIR/cast.rs:35:9 + | +LL | 1f64 as i32; + | ^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... + +error: casting `f32` to `u8` may truncate the value + --> $DIR/cast.rs:36:9 + | +LL | 1f32 as u8; + | ^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... + +error: casting `f32` to `u8` may lose the sign of the value + --> $DIR/cast.rs:36:9 + | +LL | 1f32 as u8; + | ^^^^^^^^^^ + error: casting `u8` to `i8` may wrap around the value - --> $DIR/cast.rs:33:5 + --> $DIR/cast.rs:39:5 | LL | 1u8 as i8; | ^^^^^^^^^ @@ -174,43 +192,43 @@ LL | 1u8 as i8; = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` error: casting `u16` to `i16` may wrap around the value - --> $DIR/cast.rs:34:5 + --> $DIR/cast.rs:40:5 | LL | 1u16 as i16; | ^^^^^^^^^^^ error: casting `u32` to `i32` may wrap around the value - --> $DIR/cast.rs:35:5 + --> $DIR/cast.rs:41:5 | LL | 1u32 as i32; | ^^^^^^^^^^^ error: casting `u64` to `i64` may wrap around the value - --> $DIR/cast.rs:36:5 + --> $DIR/cast.rs:42:5 | LL | 1u64 as i64; | ^^^^^^^^^^^ error: casting `usize` to `isize` may wrap around the value - --> $DIR/cast.rs:37:5 + --> $DIR/cast.rs:43:5 | LL | 1usize as isize; | ^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> $DIR/cast.rs:40:5 + --> $DIR/cast.rs:46:5 | LL | -1i32 as u32; | ^^^^^^^^^^^^ error: casting `isize` to `usize` may lose the sign of the value - --> $DIR/cast.rs:42:5 + --> $DIR/cast.rs:48:5 | LL | -1isize as usize; | ^^^^^^^^^^^^^^^^ error: casting `i64` to `i8` may truncate the value - --> $DIR/cast.rs:109:5 + --> $DIR/cast.rs:115:5 | LL | (-99999999999i64).min(1) as i8; // should be linted because signed | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -222,7 +240,7 @@ LL | i8::try_from((-99999999999i64).min(1)); // should be linted because sig | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: casting `u64` to `u8` may truncate the value - --> $DIR/cast.rs:121:5 + --> $DIR/cast.rs:127:5 | LL | 999999u64.clamp(0, 256) as u8; // should still be linted | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -234,7 +252,7 @@ LL | u8::try_from(999999u64.clamp(0, 256)); // should still be linted | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: casting `main::E2` to `u8` may truncate the value - --> $DIR/cast.rs:142:21 + --> $DIR/cast.rs:148:21 | LL | let _ = self as u8; | ^^^^^^^^^^ @@ -246,7 +264,7 @@ LL | let _ = u8::try_from(self); | ~~~~~~~~~~~~~~~~~~ error: casting `main::E2::B` to `u8` will truncate the value - --> $DIR/cast.rs:143:21 + --> $DIR/cast.rs:149:21 | LL | let _ = Self::B as u8; | ^^^^^^^^^^^^^ @@ -254,7 +272,7 @@ LL | let _ = Self::B as u8; = note: `-D clippy::cast-enum-truncation` implied by `-D warnings` error: casting `main::E5` to `i8` may truncate the value - --> $DIR/cast.rs:179:21 + --> $DIR/cast.rs:185:21 | LL | let _ = self as i8; | ^^^^^^^^^^ @@ -266,13 +284,13 @@ LL | let _ = i8::try_from(self); | ~~~~~~~~~~~~~~~~~~ error: casting `main::E5::A` to `i8` will truncate the value - --> $DIR/cast.rs:180:21 + --> $DIR/cast.rs:186:21 | LL | let _ = Self::A as i8; | ^^^^^^^^^^^^^ error: casting `main::E6` to `i16` may truncate the value - --> $DIR/cast.rs:194:21 + --> $DIR/cast.rs:200:21 | LL | let _ = self as i16; | ^^^^^^^^^^^ @@ -284,7 +302,7 @@ LL | let _ = i16::try_from(self); | ~~~~~~~~~~~~~~~~~~~ error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers - --> $DIR/cast.rs:209:21 + --> $DIR/cast.rs:215:21 | LL | let _ = self as usize; | ^^^^^^^^^^^^^ @@ -296,7 +314,7 @@ LL | let _ = usize::try_from(self); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `main::E10` to `u16` may truncate the value - --> $DIR/cast.rs:250:21 + --> $DIR/cast.rs:256:21 | LL | let _ = self as u16; | ^^^^^^^^^^^ @@ -308,7 +326,7 @@ LL | let _ = u16::try_from(self); | ~~~~~~~~~~~~~~~~~~~ error: casting `u32` to `u8` may truncate the value - --> $DIR/cast.rs:258:13 + --> $DIR/cast.rs:264:13 | LL | let c = (q >> 16) as u8; | ^^^^^^^^^^^^^^^ @@ -316,11 +334,11 @@ LL | let c = (q >> 16) as u8; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL | let c = u8::try_from((q >> 16)); - | ~~~~~~~~~~~~~~~~~~~~~~~ +LL | let c = u8::try_from(q >> 16); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `u32` to `u8` may truncate the value - --> $DIR/cast.rs:261:13 + --> $DIR/cast.rs:267:13 | LL | let c = (q / 1000) as u8; | ^^^^^^^^^^^^^^^^ @@ -328,8 +346,8 @@ LL | let c = (q / 1000) as u8; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... help: ... or use `try_from` and handle the error accordingly | -LL | let c = u8::try_from((q / 1000)); - | ~~~~~~~~~~~~~~~~~~~~~~~~ +LL | let c = u8::try_from(q / 1000); + | ~~~~~~~~~~~~~~~~~~~~~~ -error: aborting due to 36 previous errors +error: aborting due to 41 previous errors From a6f56cbdbaa6c65019278676a136299838ced65f Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Sun, 26 Mar 2023 14:16:38 +0000 Subject: [PATCH 21/86] Move unnecessary_struct_initialization to nursery --- clippy_lints/src/unnecessary_struct_initialization.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/unnecessary_struct_initialization.rs b/clippy_lints/src/unnecessary_struct_initialization.rs index af0b4b1592f4c..084b031982d8c 100644 --- a/clippy_lints/src/unnecessary_struct_initialization.rs +++ b/clippy_lints/src/unnecessary_struct_initialization.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// any field. /// /// ### Why is this bad? - /// Readibility suffers from unnecessary struct building. + /// Readability suffers from unnecessary struct building. /// /// ### Example /// ```rust @@ -25,9 +25,13 @@ declare_clippy_lint! { /// let a = S { s: String::from("Hello, world!") }; /// let b = a; /// ``` + /// + /// ### Known Problems + /// Has false positives when the base is a place expression that cannot be + /// moved out of, see [#10547](https://github.com/rust-lang/rust-clippy/issues/10547). #[clippy::version = "1.70.0"] pub UNNECESSARY_STRUCT_INITIALIZATION, - complexity, + nursery, "struct built from a base that can be written mode concisely" } declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]); From ee0de538d4196596986328311bb097a07f473273 Mon Sep 17 00:00:00 2001 From: bluthej Date: Thu, 23 Mar 2023 22:29:30 +0100 Subject: [PATCH 22/86] Add some tests --- tests/ui/clear_with_drain.fixed | 48 ++++++++++++++++++++++++++------ tests/ui/clear_with_drain.rs | 48 ++++++++++++++++++++++++++------ tests/ui/clear_with_drain.stderr | 8 +++--- 3 files changed, 84 insertions(+), 20 deletions(-) diff --git a/tests/ui/clear_with_drain.fixed b/tests/ui/clear_with_drain.fixed index 62b0af08eeae3..6ab05d9baca39 100644 --- a/tests/ui/clear_with_drain.fixed +++ b/tests/ui/clear_with_drain.fixed @@ -3,38 +3,70 @@ #![warn(clippy::clear_with_drain)] fn range() { - let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); - let iter = u.drain(0..u.len()); // Yay + let mut v = vec![1, 2, 3]; + let iter = v.drain(0..v.len()); // Yay + + let mut v = vec![1, 2, 3]; + let n = v.drain(0..v.len()).count(); // Yay + + let mut v = vec![1, 2, 3]; v.clear(); // Nay } fn range_from() { - let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); - let iter = u.drain(0..); // Yay + let mut v = vec![1, 2, 3]; + let iter = v.drain(0..); // Yay + + let mut v = vec![1, 2, 3]; + let next = v.drain(0..).next(); // Yay + + let mut v = vec![1, 2, 3]; v.clear(); // Nay } fn range_full() { - let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); - let iter = u.drain(..); // Yay + let mut v = vec![1, 2, 3]; + let iter = v.drain(..); // Yay + + let mut v = vec![1, 2, 3]; + // Yay + for x in v.drain(..) { + let y = format!("x = {x}"); + } + + let mut v = vec![1, 2, 3]; v.clear(); // Nay } fn range_to() { - let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); - let iter = u.drain(..u.len()); // Yay + let mut v = vec![1, 2, 3]; + let iter = v.drain(..v.len()); // Yay + + let mut v = vec![1, 2, 3]; + // Yay + for x in v.drain(..v.len()) { + let y = format!("x = {x}"); + } + + let mut v = vec![1, 2, 3]; v.clear(); // Nay } fn partial_drains() { let mut v = vec![1, 2, 3]; v.drain(1..); // Yay + let mut v = vec![1, 2, 3]; + v.drain(1..).max(); // Yay let mut v = vec![1, 2, 3]; v.drain(..v.len() - 1); // Yay + let mut v = vec![1, 2, 3]; + v.drain(..v.len() - 1).min(); // Yay let mut v = vec![1, 2, 3]; v.drain(1..v.len() - 1); // Yay + let mut v = vec![1, 2, 3]; + let w: Vec = v.drain(1..v.len() - 1).collect(); // Yay } fn main() {} diff --git a/tests/ui/clear_with_drain.rs b/tests/ui/clear_with_drain.rs index 721af7d2ea7e3..6926a4b60e840 100644 --- a/tests/ui/clear_with_drain.rs +++ b/tests/ui/clear_with_drain.rs @@ -3,38 +3,70 @@ #![warn(clippy::clear_with_drain)] fn range() { - let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); - let iter = u.drain(0..u.len()); // Yay + let mut v = vec![1, 2, 3]; + let iter = v.drain(0..v.len()); // Yay + + let mut v = vec![1, 2, 3]; + let n = v.drain(0..v.len()).count(); // Yay + + let mut v = vec![1, 2, 3]; v.drain(0..v.len()); // Nay } fn range_from() { - let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); - let iter = u.drain(0..); // Yay + let mut v = vec![1, 2, 3]; + let iter = v.drain(0..); // Yay + + let mut v = vec![1, 2, 3]; + let next = v.drain(0..).next(); // Yay + + let mut v = vec![1, 2, 3]; v.drain(0..); // Nay } fn range_full() { - let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); - let iter = u.drain(..); // Yay + let mut v = vec![1, 2, 3]; + let iter = v.drain(..); // Yay + + let mut v = vec![1, 2, 3]; + // Yay + for x in v.drain(..) { + let y = format!("x = {x}"); + } + + let mut v = vec![1, 2, 3]; v.drain(..); // Nay } fn range_to() { - let (mut u, mut v) = (vec![1, 2, 3], vec![1, 2, 3]); - let iter = u.drain(..u.len()); // Yay + let mut v = vec![1, 2, 3]; + let iter = v.drain(..v.len()); // Yay + + let mut v = vec![1, 2, 3]; + // Yay + for x in v.drain(..v.len()) { + let y = format!("x = {x}"); + } + + let mut v = vec![1, 2, 3]; v.drain(..v.len()); // Nay } fn partial_drains() { let mut v = vec![1, 2, 3]; v.drain(1..); // Yay + let mut v = vec![1, 2, 3]; + v.drain(1..).max(); // Yay let mut v = vec![1, 2, 3]; v.drain(..v.len() - 1); // Yay + let mut v = vec![1, 2, 3]; + v.drain(..v.len() - 1).min(); // Yay let mut v = vec![1, 2, 3]; v.drain(1..v.len() - 1); // Yay + let mut v = vec![1, 2, 3]; + let w: Vec = v.drain(1..v.len() - 1).collect(); // Yay } fn main() {} diff --git a/tests/ui/clear_with_drain.stderr b/tests/ui/clear_with_drain.stderr index 0d6d8263e10da..b7206cd9007dc 100644 --- a/tests/ui/clear_with_drain.stderr +++ b/tests/ui/clear_with_drain.stderr @@ -1,5 +1,5 @@ error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:8:7 + --> $DIR/clear_with_drain.rs:13:7 | LL | v.drain(0..v.len()); // Nay | ^^^^^^^^^^^^^^^^^ help: try: `clear()` @@ -7,19 +7,19 @@ LL | v.drain(0..v.len()); // Nay = note: `-D clippy::clear-with-drain` implied by `-D warnings` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:14:7 + --> $DIR/clear_with_drain.rs:24:7 | LL | v.drain(0..); // Nay | ^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:20:7 + --> $DIR/clear_with_drain.rs:38:7 | LL | v.drain(..); // Nay | ^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:26:7 + --> $DIR/clear_with_drain.rs:52:7 | LL | v.drain(..v.len()); // Nay | ^^^^^^^^^^^^^^^^ help: try: `clear()` From 2493be2196f6ba16eaea25915c25d93f1c2664b5 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 26 Mar 2023 18:25:15 +0200 Subject: [PATCH 23/86] Improve `is_range_full` implementation Make this function work with signed integer types by extracting the underlying type and finding the min and max values. Change the signature to make it more consistent: - The range is now given as an `Expr` in order to extract the type - The container's path is now passed, and only as an `Option` so that the function can be called in the general case without a container --- clippy_lints/src/methods/clear_with_drain.rs | 7 +- clippy_lints/src/methods/iter_with_drain.rs | 7 +- clippy_utils/src/lib.rs | 71 ++++++++++++++++---- 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints/src/methods/clear_with_drain.rs index bf7e7d1240511..24496bd4689f5 100644 --- a/clippy_lints/src/methods/clear_with_drain.rs +++ b/clippy_lints/src/methods/clear_with_drain.rs @@ -1,9 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::higher::Range; use clippy_utils::is_range_full; use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; -use rustc_hir::Expr; +use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::symbol::sym; use rustc_span::Span; @@ -12,7 +11,9 @@ use super::CLEAR_WITH_DRAIN; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv); - if is_type_diagnostic_item(cx, ty, sym::Vec) && let Some(range) = Range::hir(arg) && is_range_full(cx, recv, range) + if is_type_diagnostic_item(cx, ty, sym::Vec) + && let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind + && is_range_full(cx, arg, Some(container_path)) { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints/src/methods/iter_with_drain.rs index ea92e3a549f49..f6772c5c6b369 100644 --- a/clippy_lints/src/methods/iter_with_drain.rs +++ b/clippy_lints/src/methods/iter_with_drain.rs @@ -1,8 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::higher::Range; use clippy_utils::is_range_full; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::symbol::sym; use rustc_span::Span; @@ -14,8 +13,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span && let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def() && let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did()) && matches!(ty_name, sym::Vec | sym::VecDeque) - && let Some(range) = Range::hir(arg) - && is_range_full(cx, recv, range) + && let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind + && is_range_full(cx, arg, Some(container_path)) { span_lint_and_sugg( cx, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index e8225feb33d8d..2e839fdf47288 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -96,6 +96,7 @@ use rustc_hir::{ use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::place::PlaceBase; +use rustc_middle::mir::ConstantKind; use rustc_middle::ty as rustc_ty; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::binding::BindingMode; @@ -114,7 +115,7 @@ use rustc_span::symbol::{kw, Ident, Symbol}; use rustc_span::Span; use rustc_target::abi::Integer; -use crate::consts::{constant, Constant}; +use crate::consts::{constant, miri_to_const, Constant}; use crate::higher::Range; use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param}; use crate::visitors::for_each_expr; @@ -1492,22 +1493,66 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { } } -/// Checks whether the given `Range` is equivalent to a `RangeFull`. -/// Inclusive ranges are not considered because they already constitute a lint. -pub fn is_range_full(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool { - range.start.map_or(true, |e| is_integer_const(cx, e, 0)) - && range.end.map_or(true, |e| { - if range.limits == RangeLimits::HalfOpen - && let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind - && let ExprKind::MethodCall(name, self_arg, [], _) = e.kind - && name.ident.name == sym::len - && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind +/// Checks whether the given `Expr` is a range equivalent to a `RangeFull`. +/// For the lower bound, this means that: +/// - either there is none +/// - or it is the smallest value that can be represented by the range's integer type +/// For the upper bound, this means that: +/// - either there is none +/// - or it is the largest value that can be represented by the range's integer type and is +/// inclusive +/// - or it is a call to some container's `len` method and is exclusive, and the range is passed to +/// a method call on that same container (e.g. `v.drain(..v.len())`) +/// If the given `Expr` is not some kind of range, the function returns `false`. +pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool { + let ty = cx.typeck_results().expr_ty(expr); + if let Some(Range { start, end, limits }) = Range::hir(expr) { + let start_is_none_or_min = start.map_or(true, |start| { + if let rustc_ty::Adt(_, subst) = ty.kind() + && let bnd_ty = subst.type_at(0) + && let Some(min_val) = bnd_ty.numeric_min_val(cx.tcx) + && let const_val = cx.tcx.valtree_to_const_val((bnd_ty, min_val.to_valtree())) + && let min_const_kind = ConstantKind::from_value(const_val, bnd_ty) + && let Some(min_const) = miri_to_const(cx.tcx, min_const_kind) + && let Some((start_const, _)) = constant(cx, cx.typeck_results(), start) { - container_path.res == path.res + start_const == min_const } else { false } - }) + }); + let end_is_none_or_max = end.map_or(true, |end| { + match limits { + RangeLimits::Closed => { + if let rustc_ty::Adt(_, subst) = ty.kind() + && let bnd_ty = subst.type_at(0) + && let Some(max_val) = bnd_ty.numeric_max_val(cx.tcx) + && let const_val = cx.tcx.valtree_to_const_val((bnd_ty, max_val.to_valtree())) + && let max_const_kind = ConstantKind::from_value(const_val, bnd_ty) + && let Some(max_const) = miri_to_const(cx.tcx, max_const_kind) + && let Some((end_const, _)) = constant(cx, cx.typeck_results(), end) + { + end_const == max_const + } else { + false + } + }, + RangeLimits::HalfOpen => { + if let Some(container_path) = container_path + && let ExprKind::MethodCall(name, self_arg, [], _) = end.kind + && name.ident.name == sym::len + && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind + { + container_path.res == path.res + } else { + false + } + }, + } + }); + return start_is_none_or_min && end_is_none_or_max; + } + false } /// Checks whether the given expression is a constant integer of the given value. From 3966580c9d146b6938e691c8a1abefc89e0aa7b7 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 26 Mar 2023 18:59:20 +0200 Subject: [PATCH 24/86] Add tests with `usize::MIN` --- tests/ui/clear_with_drain.fixed | 12 ++++++++++++ tests/ui/clear_with_drain.rs | 12 ++++++++++++ tests/ui/clear_with_drain.stderr | 22 +++++++++++++++++----- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/tests/ui/clear_with_drain.fixed b/tests/ui/clear_with_drain.fixed index 6ab05d9baca39..0a15463ddf792 100644 --- a/tests/ui/clear_with_drain.fixed +++ b/tests/ui/clear_with_drain.fixed @@ -9,6 +9,12 @@ fn range() { let mut v = vec![1, 2, 3]; let n = v.drain(0..v.len()).count(); // Yay + let mut v = vec![1, 2, 3]; + let n = v.drain(usize::MIN..v.len()).count(); // Yay + + let mut v = vec![1, 2, 3]; + v.clear(); // Nay + let mut v = vec![1, 2, 3]; v.clear(); // Nay } @@ -20,6 +26,12 @@ fn range_from() { let mut v = vec![1, 2, 3]; let next = v.drain(0..).next(); // Yay + let mut v = vec![1, 2, 3]; + let next = v.drain(usize::MIN..).next(); // Yay + + let mut v = vec![1, 2, 3]; + v.clear(); // Nay + let mut v = vec![1, 2, 3]; v.clear(); // Nay } diff --git a/tests/ui/clear_with_drain.rs b/tests/ui/clear_with_drain.rs index 6926a4b60e840..40201d9cc4dfc 100644 --- a/tests/ui/clear_with_drain.rs +++ b/tests/ui/clear_with_drain.rs @@ -9,8 +9,14 @@ fn range() { let mut v = vec![1, 2, 3]; let n = v.drain(0..v.len()).count(); // Yay + let mut v = vec![1, 2, 3]; + let n = v.drain(usize::MIN..v.len()).count(); // Yay + let mut v = vec![1, 2, 3]; v.drain(0..v.len()); // Nay + + let mut v = vec![1, 2, 3]; + v.drain(usize::MIN..v.len()); // Nay } fn range_from() { @@ -20,8 +26,14 @@ fn range_from() { let mut v = vec![1, 2, 3]; let next = v.drain(0..).next(); // Yay + let mut v = vec![1, 2, 3]; + let next = v.drain(usize::MIN..).next(); // Yay + let mut v = vec![1, 2, 3]; v.drain(0..); // Nay + + let mut v = vec![1, 2, 3]; + v.drain(usize::MIN..); // Nay } fn range_full() { diff --git a/tests/ui/clear_with_drain.stderr b/tests/ui/clear_with_drain.stderr index b7206cd9007dc..e7152a1cb687d 100644 --- a/tests/ui/clear_with_drain.stderr +++ b/tests/ui/clear_with_drain.stderr @@ -1,5 +1,5 @@ error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:13:7 + --> $DIR/clear_with_drain.rs:16:7 | LL | v.drain(0..v.len()); // Nay | ^^^^^^^^^^^^^^^^^ help: try: `clear()` @@ -7,22 +7,34 @@ LL | v.drain(0..v.len()); // Nay = note: `-D clippy::clear-with-drain` implied by `-D warnings` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:24:7 + --> $DIR/clear_with_drain.rs:19:7 + | +LL | v.drain(usize::MIN..v.len()); // Nay + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `Vec` + --> $DIR/clear_with_drain.rs:33:7 | LL | v.drain(0..); // Nay | ^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:38:7 + --> $DIR/clear_with_drain.rs:36:7 + | +LL | v.drain(usize::MIN..); // Nay + | ^^^^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `Vec` + --> $DIR/clear_with_drain.rs:50:7 | LL | v.drain(..); // Nay | ^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:52:7 + --> $DIR/clear_with_drain.rs:64:7 | LL | v.drain(..v.len()); // Nay | ^^^^^^^^^^^^^^^^ help: try: `clear()` -error: aborting due to 4 previous errors +error: aborting due to 6 previous errors From db4e4afce8f87eb73db3808981215cd28e97095d Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 26 Mar 2023 20:33:54 +0000 Subject: [PATCH 25/86] Don't elaborate non-obligations into obligations --- clippy_lints/src/needless_pass_by_value.rs | 4 ++-- clippy_utils/src/lib.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 1ab81aee7b8d5..327e090d38be8 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -124,9 +124,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter()) .filter(|p| !p.is_global()) - .filter_map(|obligation| { + .filter_map(|pred| { // Note that we do not want to deal with qualified predicates here. - match obligation.predicate.kind().no_bound_vars() { + match pred.kind().no_bound_vars() { Some(ty::PredicateKind::Clause(ty::Clause::Trait(pred))) if pred.def_id() != sized_trait => { Some(pred) }, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 29830557a4454..fd06c0b86775a 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2106,7 +2106,6 @@ pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool { traits::impossible_predicates( cx.tcx, traits::elaborate_predicates(cx.tcx, predicates) - .map(|o| o.predicate) .collect::>(), ) } From 022f76d432f3e3289af68845d9169c1866c14d70 Mon Sep 17 00:00:00 2001 From: Micha White Date: Sat, 2 Jul 2022 18:42:08 -0400 Subject: [PATCH 26/86] Added the new lint with some docs and tests --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/unnecessary_box_returns.rs | 88 +++++++++++++++++++++ tests/ui/unnecessary_box_returns.rs | 31 ++++++++ tests/ui/unnecessary_box_returns.stderr | 19 +++++ 6 files changed, 142 insertions(+) create mode 100644 clippy_lints/src/unnecessary_box_returns.rs create mode 100644 tests/ui/unnecessary_box_returns.rs create mode 100644 tests/ui/unnecessary_box_returns.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 1323f973ccfdd..1a37ff2e171d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4974,6 +4974,7 @@ Released 2018-09-13 [`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash [`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord [`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints +[`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns [`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast [`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map [`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 8ca91301472e6..89076e5d294f5 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -616,6 +616,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::unit_types::UNIT_CMP_INFO, crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO, crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO, + crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO, crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO, crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO, crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c9210bf73f896..c172ee263c6a1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -300,6 +300,7 @@ mod uninit_vec; mod unit_return_expecting_ord; mod unit_types; mod unnamed_address; +mod unnecessary_box_returns; mod unnecessary_owned_empty_strings; mod unnecessary_self_imports; mod unnecessary_struct_initialization; @@ -940,6 +941,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute)); store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv()))); store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); + store.register_late_pass(|_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/unnecessary_box_returns.rs b/clippy_lints/src/unnecessary_box_returns.rs new file mode 100644 index 0000000000000..533ee026500e8 --- /dev/null +++ b/clippy_lints/src/unnecessary_box_returns.rs @@ -0,0 +1,88 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{def_id::LocalDefId, intravisit::FnKind, Body, FnDecl, FnRetTy}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for a return type containing a `Box` where `T` implements `Sized` + /// + /// ### Why is this bad? + /// + /// It's better to just return `T` in these cases. The caller may not need + /// the value to be boxed, and it's expensive to free the memory once the + /// `Box` been dropped. + /// + /// ### Example + /// ```rust + /// fn foo() -> Box { + /// Box::new(String::from("Hello, world!")) + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn foo() -> String { + /// String::from("Hello, world!") + /// } + /// ``` + #[clippy::version = "1.70.0"] + pub UNNECESSARY_BOX_RETURNS, + pedantic, + "Needlessly returning a Box" +} +declare_lint_pass!(UnnecessaryBoxReturns => [UNNECESSARY_BOX_RETURNS]); + +impl LateLintPass<'_> for UnnecessaryBoxReturns { + fn check_fn( + &mut self, + cx: &LateContext<'_>, + fn_kind: FnKind<'_>, + decl: &FnDecl<'_>, + _: &Body<'_>, + _: Span, + def_id: LocalDefId, + ) { + // it's unclear what part of a closure you would span, so for now it's ignored + // if this is changed, please also make sure not to call `hir_ty_to_ty` below + if matches!(fn_kind, FnKind::Closure) { + return; + } + + let FnRetTy::Return(return_ty_hir) = &decl.output else { return }; + + let return_ty = cx + .tcx + .erase_late_bound_regions(cx.tcx.fn_sig(def_id).skip_binder()) + .output(); + + if !return_ty.is_box() { + return; + } + + let boxed_ty = return_ty.boxed_ty(); + + // it's sometimes useful to return Box if T is unsized, so don't lint those + if boxed_ty.is_sized(cx.tcx, cx.param_env) { + span_lint_and_then( + cx, + UNNECESSARY_BOX_RETURNS, + return_ty_hir.span, + format!("boxed return of the sized type `{boxed_ty}`").as_str(), + |diagnostic| { + diagnostic.span_suggestion( + return_ty_hir.span, + "try", + boxed_ty.to_string(), + // the return value and function callers also needs to + // be changed, so this can't be MachineApplicable + Applicability::Unspecified, + ); + diagnostic.help("changing this also requires a change to the return expressions in this function"); + }, + ); + } + } +} diff --git a/tests/ui/unnecessary_box_returns.rs b/tests/ui/unnecessary_box_returns.rs new file mode 100644 index 0000000000000..49e24878c82da --- /dev/null +++ b/tests/ui/unnecessary_box_returns.rs @@ -0,0 +1,31 @@ +#![warn(clippy::unnecessary_box_returns)] + +struct Foo {} + +// lint +fn boxed_usize() -> Box { + Box::new(5) +} + +// lint +fn boxed_foo() -> Box { + Box::new(Foo {}) +} + +// don't lint: str is unsized +fn boxed_str() -> Box { + "Hello, world!".to_string().into_boxed_str() +} + +// don't lint: this has an unspecified return type +fn default() {} + +// don't lint: this doesn't return a Box +fn string() -> String { + String::from("Hello, world") +} + +fn main() { + // don't lint: this is a closure + let a = || -> Box { Box::new(5) }; +} diff --git a/tests/ui/unnecessary_box_returns.stderr b/tests/ui/unnecessary_box_returns.stderr new file mode 100644 index 0000000000000..8acaf33de047d --- /dev/null +++ b/tests/ui/unnecessary_box_returns.stderr @@ -0,0 +1,19 @@ +error: boxed return of the sized type `usize` + --> $DIR/unnecessary_box_returns.rs:6:21 + | +LL | fn boxed_usize() -> Box { + | ^^^^^^^^^^ help: try: `usize` + | + = help: changing this also requires a change to the return expressions in this function + = note: `-D clippy::unnecessary-box-returns` implied by `-D warnings` + +error: boxed return of the sized type `Foo` + --> $DIR/unnecessary_box_returns.rs:11:19 + | +LL | fn boxed_foo() -> Box { + | ^^^^^^^^ help: try: `Foo` + | + = help: changing this also requires a change to the return expressions in this function + +error: aborting due to 2 previous errors + From 1b55c81db565d8e94146b9e2a7ed54f242e349ec Mon Sep 17 00:00:00 2001 From: Micha White Date: Sun, 26 Feb 2023 12:11:47 -0500 Subject: [PATCH 27/86] Lint on trait declarations, not implementations --- clippy_lints/src/unnecessary_box_returns.rs | 96 +++++++++++---------- tests/ui/unnecessary_box_returns.rs | 19 ++++ tests/ui/unnecessary_box_returns.stderr | 24 +++++- 3 files changed, 91 insertions(+), 48 deletions(-) diff --git a/clippy_lints/src/unnecessary_box_returns.rs b/clippy_lints/src/unnecessary_box_returns.rs index 533ee026500e8..77c1506e54299 100644 --- a/clippy_lints/src/unnecessary_box_returns.rs +++ b/clippy_lints/src/unnecessary_box_returns.rs @@ -1,9 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; -use rustc_hir::{def_id::LocalDefId, intravisit::FnKind, Body, FnDecl, FnRetTy}; +use rustc_hir::{def_id::LocalDefId, FnDecl, FnRetTy, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -35,54 +34,63 @@ declare_clippy_lint! { } declare_lint_pass!(UnnecessaryBoxReturns => [UNNECESSARY_BOX_RETURNS]); -impl LateLintPass<'_> for UnnecessaryBoxReturns { - fn check_fn( - &mut self, - cx: &LateContext<'_>, - fn_kind: FnKind<'_>, - decl: &FnDecl<'_>, - _: &Body<'_>, - _: Span, - def_id: LocalDefId, - ) { - // it's unclear what part of a closure you would span, so for now it's ignored - // if this is changed, please also make sure not to call `hir_ty_to_ty` below - if matches!(fn_kind, FnKind::Closure) { - return; - } +fn check_fn_decl(cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId) { + let FnRetTy::Return(return_ty_hir) = &decl.output else { return }; - let FnRetTy::Return(return_ty_hir) = &decl.output else { return }; + let return_ty = cx + .tcx + .erase_late_bound_regions(cx.tcx.fn_sig(def_id).skip_binder()) + .output(); - let return_ty = cx - .tcx - .erase_late_bound_regions(cx.tcx.fn_sig(def_id).skip_binder()) - .output(); + if !return_ty.is_box() { + return; + } - if !return_ty.is_box() { + let boxed_ty = return_ty.boxed_ty(); + + // it's sometimes useful to return Box if T is unsized, so don't lint those + if boxed_ty.is_sized(cx.tcx, cx.param_env) { + span_lint_and_then( + cx, + UNNECESSARY_BOX_RETURNS, + return_ty_hir.span, + format!("boxed return of the sized type `{boxed_ty}`").as_str(), + |diagnostic| { + diagnostic.span_suggestion( + return_ty_hir.span, + "try", + boxed_ty.to_string(), + // the return value and function callers also needs to + // be changed, so this can't be MachineApplicable + Applicability::Unspecified, + ); + diagnostic.help("changing this also requires a change to the return expressions in this function"); + }, + ); + } +} + +impl LateLintPass<'_> for UnnecessaryBoxReturns { + fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { + let TraitItemKind::Fn(signature, _) = &item.kind else { return }; + check_fn_decl(cx, signature.decl, item.owner_id.def_id); + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'_>) { + // Ignore implementations of traits, because the lint should be on the + // trait, not on the implmentation of it. + let Node::Item(parent) = cx.tcx.hir().get_parent(item.hir_id()) else { return }; + let ItemKind::Impl(parent) = parent.kind else { return }; + if parent.of_trait.is_some() { return; } - let boxed_ty = return_ty.boxed_ty(); + let ImplItemKind::Fn(signature, ..) = &item.kind else { return }; + check_fn_decl(cx, signature.decl, item.owner_id.def_id); + } - // it's sometimes useful to return Box if T is unsized, so don't lint those - if boxed_ty.is_sized(cx.tcx, cx.param_env) { - span_lint_and_then( - cx, - UNNECESSARY_BOX_RETURNS, - return_ty_hir.span, - format!("boxed return of the sized type `{boxed_ty}`").as_str(), - |diagnostic| { - diagnostic.span_suggestion( - return_ty_hir.span, - "try", - boxed_ty.to_string(), - // the return value and function callers also needs to - // be changed, so this can't be MachineApplicable - Applicability::Unspecified, - ); - diagnostic.help("changing this also requires a change to the return expressions in this function"); - }, - ); - } + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + let ItemKind::Fn(signature, ..) = &item.kind else { return }; + check_fn_decl(cx, signature.decl, item.owner_id.def_id); } } diff --git a/tests/ui/unnecessary_box_returns.rs b/tests/ui/unnecessary_box_returns.rs index 49e24878c82da..399266f90b0c0 100644 --- a/tests/ui/unnecessary_box_returns.rs +++ b/tests/ui/unnecessary_box_returns.rs @@ -1,7 +1,26 @@ #![warn(clippy::unnecessary_box_returns)] +trait Bar { + // lint + fn baz(&self) -> Box; +} + struct Foo {} +impl Bar for Foo { + // don't lint: this is a problem with the trait, not the implementation + fn baz(&self) -> Box { + Box::new(42) + } +} + +impl Foo { + fn baz(&self) -> Box { + // lint + Box::new(13) + } +} + // lint fn boxed_usize() -> Box { Box::new(5) diff --git a/tests/ui/unnecessary_box_returns.stderr b/tests/ui/unnecessary_box_returns.stderr index 8acaf33de047d..a431e37a201bc 100644 --- a/tests/ui/unnecessary_box_returns.stderr +++ b/tests/ui/unnecessary_box_returns.stderr @@ -1,19 +1,35 @@ error: boxed return of the sized type `usize` - --> $DIR/unnecessary_box_returns.rs:6:21 + --> $DIR/unnecessary_box_returns.rs:5:22 + | +LL | fn baz(&self) -> Box; + | ^^^^^^^^^^ help: try: `usize` + | + = help: changing this also requires a change to the return expressions in this function + = note: `-D clippy::unnecessary-box-returns` implied by `-D warnings` + +error: boxed return of the sized type `usize` + --> $DIR/unnecessary_box_returns.rs:18:22 + | +LL | fn baz(&self) -> Box { + | ^^^^^^^^^^ help: try: `usize` + | + = help: changing this also requires a change to the return expressions in this function + +error: boxed return of the sized type `usize` + --> $DIR/unnecessary_box_returns.rs:25:21 | LL | fn boxed_usize() -> Box { | ^^^^^^^^^^ help: try: `usize` | = help: changing this also requires a change to the return expressions in this function - = note: `-D clippy::unnecessary-box-returns` implied by `-D warnings` error: boxed return of the sized type `Foo` - --> $DIR/unnecessary_box_returns.rs:11:19 + --> $DIR/unnecessary_box_returns.rs:30:19 | LL | fn boxed_foo() -> Box { | ^^^^^^^^ help: try: `Foo` | = help: changing this also requires a change to the return expressions in this function -error: aborting due to 2 previous errors +error: aborting due to 4 previous errors From a143fb7a11695c45545e6a207f677bd1112032d7 Mon Sep 17 00:00:00 2001 From: Micha White Date: Sun, 26 Feb 2023 13:17:34 -0500 Subject: [PATCH 28/86] Avoid breaking exported API --- book/src/lint_configuration.md | 1 + clippy_lints/src/lib.rs | 6 +- clippy_lints/src/unnecessary_box_returns.rs | 84 +++++++++++++-------- clippy_lints/src/utils/conf.rs | 2 +- tests/ui/unnecessary_box_returns.rs | 9 ++- tests/ui/unnecessary_box_returns.stderr | 6 +- 6 files changed, 68 insertions(+), 40 deletions(-) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 9ed6627b74130..dbd1a404150ac 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -130,6 +130,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat * [option_option](https://rust-lang.github.io/rust-clippy/master/index.html#option_option) * [linkedlist](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist) * [rc_mutex](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex) +* [unnecessary_box_returns](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns) ### msrv diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c172ee263c6a1..117732c6efe90 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -941,7 +941,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute)); store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv()))); store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); - store.register_late_pass(|_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns)); + store.register_late_pass(move |_| { + Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new( + avoid_breaking_exported_api, + )) + }); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/unnecessary_box_returns.rs b/clippy_lints/src/unnecessary_box_returns.rs index 77c1506e54299..10aee89606ddc 100644 --- a/clippy_lints/src/unnecessary_box_returns.rs +++ b/clippy_lints/src/unnecessary_box_returns.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; use rustc_hir::{def_id::LocalDefId, FnDecl, FnRetTy, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; declare_clippy_lint! { /// ### What it does @@ -32,48 +32,66 @@ declare_clippy_lint! { pedantic, "Needlessly returning a Box" } -declare_lint_pass!(UnnecessaryBoxReturns => [UNNECESSARY_BOX_RETURNS]); -fn check_fn_decl(cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId) { - let FnRetTy::Return(return_ty_hir) = &decl.output else { return }; +pub struct UnnecessaryBoxReturns { + avoid_breaking_exported_api: bool, +} - let return_ty = cx - .tcx - .erase_late_bound_regions(cx.tcx.fn_sig(def_id).skip_binder()) - .output(); +impl_lint_pass!(UnnecessaryBoxReturns => [UNNECESSARY_BOX_RETURNS]); - if !return_ty.is_box() { - return; +impl UnnecessaryBoxReturns { + pub fn new(avoid_breaking_exported_api: bool) -> Self { + Self { + avoid_breaking_exported_api, + } } - let boxed_ty = return_ty.boxed_ty(); + fn check_fn_decl(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId) { + // we don't want to tell someone to break an exported function if they ask us not to + if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { + return; + } + + let FnRetTy::Return(return_ty_hir) = &decl.output else { return }; + + let return_ty = cx + .tcx + .erase_late_bound_regions(cx.tcx.fn_sig(def_id).skip_binder()) + .output(); - // it's sometimes useful to return Box if T is unsized, so don't lint those - if boxed_ty.is_sized(cx.tcx, cx.param_env) { - span_lint_and_then( - cx, - UNNECESSARY_BOX_RETURNS, - return_ty_hir.span, - format!("boxed return of the sized type `{boxed_ty}`").as_str(), - |diagnostic| { - diagnostic.span_suggestion( - return_ty_hir.span, - "try", - boxed_ty.to_string(), - // the return value and function callers also needs to - // be changed, so this can't be MachineApplicable - Applicability::Unspecified, - ); - diagnostic.help("changing this also requires a change to the return expressions in this function"); - }, - ); + if !return_ty.is_box() { + return; + } + + let boxed_ty = return_ty.boxed_ty(); + + // it's sometimes useful to return Box if T is unsized, so don't lint those + if boxed_ty.is_sized(cx.tcx, cx.param_env) { + span_lint_and_then( + cx, + UNNECESSARY_BOX_RETURNS, + return_ty_hir.span, + format!("boxed return of the sized type `{boxed_ty}`").as_str(), + |diagnostic| { + diagnostic.span_suggestion( + return_ty_hir.span, + "try", + boxed_ty.to_string(), + // the return value and function callers also needs to + // be changed, so this can't be MachineApplicable + Applicability::Unspecified, + ); + diagnostic.help("changing this also requires a change to the return expressions in this function"); + }, + ); + } } } impl LateLintPass<'_> for UnnecessaryBoxReturns { fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { let TraitItemKind::Fn(signature, _) = &item.kind else { return }; - check_fn_decl(cx, signature.decl, item.owner_id.def_id); + self.check_fn_decl(cx, signature.decl, item.owner_id.def_id); } fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'_>) { @@ -86,11 +104,11 @@ impl LateLintPass<'_> for UnnecessaryBoxReturns { } let ImplItemKind::Fn(signature, ..) = &item.kind else { return }; - check_fn_decl(cx, signature.decl, item.owner_id.def_id); + self.check_fn_decl(cx, signature.decl, item.owner_id.def_id); } fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { let ItemKind::Fn(signature, ..) = &item.kind else { return }; - check_fn_decl(cx, signature.decl, item.owner_id.def_id); + self.check_fn_decl(cx, signature.decl, item.owner_id.def_id); } } diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 8ba252425a3d0..5384ae01f9262 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -249,7 +249,7 @@ define_Conf! { /// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"] /// ``` (arithmetic_side_effects_allowed_unary: rustc_data_structures::fx::FxHashSet = <_>::default()), - /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX. + /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS. /// /// Suppress lints whenever the suggested change would cause breakage for other crates. (avoid_breaking_exported_api: bool = true), diff --git a/tests/ui/unnecessary_box_returns.rs b/tests/ui/unnecessary_box_returns.rs index 399266f90b0c0..f2f6aae380e93 100644 --- a/tests/ui/unnecessary_box_returns.rs +++ b/tests/ui/unnecessary_box_returns.rs @@ -5,7 +5,7 @@ trait Bar { fn baz(&self) -> Box; } -struct Foo {} +pub struct Foo {} impl Bar for Foo { // don't lint: this is a problem with the trait, not the implementation @@ -27,7 +27,12 @@ fn boxed_usize() -> Box { } // lint -fn boxed_foo() -> Box { +fn _boxed_foo() -> Box { + Box::new(Foo {}) +} + +// don't lint: this is exported +pub fn boxed_foo() -> Box { Box::new(Foo {}) } diff --git a/tests/ui/unnecessary_box_returns.stderr b/tests/ui/unnecessary_box_returns.stderr index a431e37a201bc..0870af5f28f7b 100644 --- a/tests/ui/unnecessary_box_returns.stderr +++ b/tests/ui/unnecessary_box_returns.stderr @@ -24,10 +24,10 @@ LL | fn boxed_usize() -> Box { = help: changing this also requires a change to the return expressions in this function error: boxed return of the sized type `Foo` - --> $DIR/unnecessary_box_returns.rs:30:19 + --> $DIR/unnecessary_box_returns.rs:30:20 | -LL | fn boxed_foo() -> Box { - | ^^^^^^^^ help: try: `Foo` +LL | fn _boxed_foo() -> Box { + | ^^^^^^^^ help: try: `Foo` | = help: changing this also requires a change to the return expressions in this function From 76d13bb7fa20ba316c75023c66d74fed8e1881df Mon Sep 17 00:00:00 2001 From: Micha White Date: Sun, 26 Mar 2023 11:00:10 -0400 Subject: [PATCH 29/86] Don't lint functions with "box" in their name --- clippy_lints/src/unnecessary_box_returns.rs | 14 ++++++++++---- tests/ui/unnecessary_box_returns.rs | 13 +++++++++---- tests/ui/unnecessary_box_returns.stderr | 12 ++++++------ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/unnecessary_box_returns.rs b/clippy_lints/src/unnecessary_box_returns.rs index 10aee89606ddc..912bcda630b83 100644 --- a/clippy_lints/src/unnecessary_box_returns.rs +++ b/clippy_lints/src/unnecessary_box_returns.rs @@ -3,6 +3,7 @@ use rustc_errors::Applicability; use rustc_hir::{def_id::LocalDefId, FnDecl, FnRetTy, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Symbol; declare_clippy_lint! { /// ### What it does @@ -46,12 +47,17 @@ impl UnnecessaryBoxReturns { } } - fn check_fn_decl(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId) { + fn check_fn_item(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) { // we don't want to tell someone to break an exported function if they ask us not to if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { return; } + // functions which contain the word "box" are exempt from this lint + if name.as_str().contains("box") { + return; + } + let FnRetTy::Return(return_ty_hir) = &decl.output else { return }; let return_ty = cx @@ -91,7 +97,7 @@ impl UnnecessaryBoxReturns { impl LateLintPass<'_> for UnnecessaryBoxReturns { fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { let TraitItemKind::Fn(signature, _) = &item.kind else { return }; - self.check_fn_decl(cx, signature.decl, item.owner_id.def_id); + self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name); } fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'_>) { @@ -104,11 +110,11 @@ impl LateLintPass<'_> for UnnecessaryBoxReturns { } let ImplItemKind::Fn(signature, ..) = &item.kind else { return }; - self.check_fn_decl(cx, signature.decl, item.owner_id.def_id); + self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name); } fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { let ItemKind::Fn(signature, ..) = &item.kind else { return }; - self.check_fn_decl(cx, signature.decl, item.owner_id.def_id); + self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name); } } diff --git a/tests/ui/unnecessary_box_returns.rs b/tests/ui/unnecessary_box_returns.rs index f2f6aae380e93..fe60d929759ba 100644 --- a/tests/ui/unnecessary_box_returns.rs +++ b/tests/ui/unnecessary_box_returns.rs @@ -22,25 +22,30 @@ impl Foo { } // lint -fn boxed_usize() -> Box { +fn bxed_usize() -> Box { Box::new(5) } // lint -fn _boxed_foo() -> Box { +fn _bxed_foo() -> Box { Box::new(Foo {}) } // don't lint: this is exported -pub fn boxed_foo() -> Box { +pub fn bxed_foo() -> Box { Box::new(Foo {}) } // don't lint: str is unsized -fn boxed_str() -> Box { +fn bxed_str() -> Box { "Hello, world!".to_string().into_boxed_str() } +// don't lint: function contains the word, "box" +fn boxed_usize() -> Box { + Box::new(7) +} + // don't lint: this has an unspecified return type fn default() {} diff --git a/tests/ui/unnecessary_box_returns.stderr b/tests/ui/unnecessary_box_returns.stderr index 0870af5f28f7b..b17512c10a177 100644 --- a/tests/ui/unnecessary_box_returns.stderr +++ b/tests/ui/unnecessary_box_returns.stderr @@ -16,18 +16,18 @@ LL | fn baz(&self) -> Box { = help: changing this also requires a change to the return expressions in this function error: boxed return of the sized type `usize` - --> $DIR/unnecessary_box_returns.rs:25:21 + --> $DIR/unnecessary_box_returns.rs:25:20 | -LL | fn boxed_usize() -> Box { - | ^^^^^^^^^^ help: try: `usize` +LL | fn bxed_usize() -> Box { + | ^^^^^^^^^^ help: try: `usize` | = help: changing this also requires a change to the return expressions in this function error: boxed return of the sized type `Foo` - --> $DIR/unnecessary_box_returns.rs:30:20 + --> $DIR/unnecessary_box_returns.rs:30:19 | -LL | fn _boxed_foo() -> Box { - | ^^^^^^^^ help: try: `Foo` +LL | fn _bxed_foo() -> Box { + | ^^^^^^^^ help: try: `Foo` | = help: changing this also requires a change to the return expressions in this function From df65d21f4c98a19573f1cd89d9ff01ee8812cf43 Mon Sep 17 00:00:00 2001 From: bluthej Date: Mon, 27 Mar 2023 11:11:40 +0200 Subject: [PATCH 30/86] Include tests where the iterator is used later --- tests/ui/clear_with_drain.fixed | 10 ++++++---- tests/ui/clear_with_drain.rs | 10 ++++++---- tests/ui/clear_with_drain.stderr | 12 ++++++------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/ui/clear_with_drain.fixed b/tests/ui/clear_with_drain.fixed index 0a15463ddf792..9c4dc010ca7fb 100644 --- a/tests/ui/clear_with_drain.fixed +++ b/tests/ui/clear_with_drain.fixed @@ -10,7 +10,8 @@ fn range() { let n = v.drain(0..v.len()).count(); // Yay let mut v = vec![1, 2, 3]; - let n = v.drain(usize::MIN..v.len()).count(); // Yay + let iter = v.drain(usize::MIN..v.len()); // Yay + let n = iter.count(); let mut v = vec![1, 2, 3]; v.clear(); // Nay @@ -24,7 +25,8 @@ fn range_from() { let iter = v.drain(0..); // Yay let mut v = vec![1, 2, 3]; - let next = v.drain(0..).next(); // Yay + let mut iter = v.drain(0..); // Yay + let next = iter.next(); let mut v = vec![1, 2, 3]; let next = v.drain(usize::MIN..).next(); // Yay @@ -55,8 +57,8 @@ fn range_to() { let iter = v.drain(..v.len()); // Yay let mut v = vec![1, 2, 3]; - // Yay - for x in v.drain(..v.len()) { + let iter = v.drain(..v.len()); // Yay + for x in iter { let y = format!("x = {x}"); } diff --git a/tests/ui/clear_with_drain.rs b/tests/ui/clear_with_drain.rs index 40201d9cc4dfc..f00dbab234cc8 100644 --- a/tests/ui/clear_with_drain.rs +++ b/tests/ui/clear_with_drain.rs @@ -10,7 +10,8 @@ fn range() { let n = v.drain(0..v.len()).count(); // Yay let mut v = vec![1, 2, 3]; - let n = v.drain(usize::MIN..v.len()).count(); // Yay + let iter = v.drain(usize::MIN..v.len()); // Yay + let n = iter.count(); let mut v = vec![1, 2, 3]; v.drain(0..v.len()); // Nay @@ -24,7 +25,8 @@ fn range_from() { let iter = v.drain(0..); // Yay let mut v = vec![1, 2, 3]; - let next = v.drain(0..).next(); // Yay + let mut iter = v.drain(0..); // Yay + let next = iter.next(); let mut v = vec![1, 2, 3]; let next = v.drain(usize::MIN..).next(); // Yay @@ -55,8 +57,8 @@ fn range_to() { let iter = v.drain(..v.len()); // Yay let mut v = vec![1, 2, 3]; - // Yay - for x in v.drain(..v.len()) { + let iter = v.drain(..v.len()); // Yay + for x in iter { let y = format!("x = {x}"); } diff --git a/tests/ui/clear_with_drain.stderr b/tests/ui/clear_with_drain.stderr index e7152a1cb687d..c88aa1a23cb65 100644 --- a/tests/ui/clear_with_drain.stderr +++ b/tests/ui/clear_with_drain.stderr @@ -1,5 +1,5 @@ error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:16:7 + --> $DIR/clear_with_drain.rs:17:7 | LL | v.drain(0..v.len()); // Nay | ^^^^^^^^^^^^^^^^^ help: try: `clear()` @@ -7,31 +7,31 @@ LL | v.drain(0..v.len()); // Nay = note: `-D clippy::clear-with-drain` implied by `-D warnings` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:19:7 + --> $DIR/clear_with_drain.rs:20:7 | LL | v.drain(usize::MIN..v.len()); // Nay | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:33:7 + --> $DIR/clear_with_drain.rs:35:7 | LL | v.drain(0..); // Nay | ^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:36:7 + --> $DIR/clear_with_drain.rs:38:7 | LL | v.drain(usize::MIN..); // Nay | ^^^^^^^^^^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:50:7 + --> $DIR/clear_with_drain.rs:52:7 | LL | v.drain(..); // Nay | ^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:64:7 + --> $DIR/clear_with_drain.rs:66:7 | LL | v.drain(..v.len()); // Nay | ^^^^^^^^^^^^^^^^ help: try: `clear()` From 28b80059ed3ff5ade532e5547af8be0612927fe5 Mon Sep 17 00:00:00 2001 From: Jamen Marz Date: Mon, 27 Mar 2023 10:14:08 -0400 Subject: [PATCH 31/86] Add notes to non-structural const in pattern error message --- tests/ui/crashes/ice-6254.stderr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ui/crashes/ice-6254.stderr b/tests/ui/crashes/ice-6254.stderr index 22d82a30c6aad..263c27d3d646a 100644 --- a/tests/ui/crashes/ice-6254.stderr +++ b/tests/ui/crashes/ice-6254.stderr @@ -6,6 +6,8 @@ LL | FOO_REF_REF => {}, | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #62411 + = note: the traits must be derived, manual `impl`s are not sufficient + = note: see https://doc.rust-lang.org/stable/std/marker/trait.StructuralEq.html for details = note: `-D indirect-structural-match` implied by `-D warnings` error: aborting due to previous error From fc6262fa0c0cd9bd3e32b3db4f3df5c268acf291 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 16 Mar 2023 22:00:08 +0000 Subject: [PATCH 32/86] Add `(..)` syntax for RTN --- clippy_lints/src/ref_option_ref.rs | 4 ++-- clippy_lints/src/types/borrowed_box.rs | 2 +- clippy_lints/src/types/utils.rs | 4 ++-- clippy_lints/src/use_self.rs | 5 +++-- clippy_utils/src/hir_utils.rs | 7 +------ 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/ref_option_ref.rs b/clippy_lints/src/ref_option_ref.rs index 448a32b77c036..c984a8286eb88 100644 --- a/clippy_lints/src/ref_option_ref.rs +++ b/clippy_lints/src/ref_option_ref.rs @@ -3,7 +3,7 @@ use clippy_utils::last_path_segment; use clippy_utils::source::snippet; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{GenericArg, Mutability, Ty, TyKind}; +use rustc_hir::{GenericArg, GenericArgsParentheses, Mutability, Ty, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; @@ -47,7 +47,7 @@ impl<'tcx> LateLintPass<'tcx> for RefOptionRef { if cx.tcx.is_diagnostic_item(sym::Option, def_id); if let Some(params) = last_path_segment(qpath).args ; - if !params.parenthesized; + if params.parenthesized == GenericArgsParentheses::No; if let Some(inner_ty) = params.args.iter().find_map(|arg| match arg { GenericArg::Type(inner_ty) => Some(inner_ty), _ => None, diff --git a/clippy_lints/src/types/borrowed_box.rs b/clippy_lints/src/types/borrowed_box.rs index 65dfe7637ea99..acdf54710691e 100644 --- a/clippy_lints/src/types/borrowed_box.rs +++ b/clippy_lints/src/types/borrowed_box.rs @@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, m if let QPath::Resolved(None, path) = *qpath; if let [ref bx] = *path.segments; if let Some(params) = bx.args; - if !params.parenthesized; + if params.parenthesized == hir::GenericArgsParentheses::No; if let Some(inner) = params.args.iter().find_map(|arg| match arg { GenericArg::Type(ty) => Some(ty), _ => None, diff --git a/clippy_lints/src/types/utils.rs b/clippy_lints/src/types/utils.rs index 7f43b7841ff33..a30748db88fc9 100644 --- a/clippy_lints/src/types/utils.rs +++ b/clippy_lints/src/types/utils.rs @@ -1,6 +1,6 @@ use clippy_utils::last_path_segment; use if_chain::if_chain; -use rustc_hir::{GenericArg, QPath, TyKind}; +use rustc_hir::{GenericArg, GenericArgsParentheses, QPath, TyKind}; use rustc_lint::LateContext; use rustc_span::source_map::Span; @@ -8,7 +8,7 @@ pub(super) fn match_borrows_parameter(_cx: &LateContext<'_>, qpath: &QPath<'_>) let last = last_path_segment(qpath); if_chain! { if let Some(params) = last.args; - if !params.parenthesized; + if params.parenthesized == GenericArgsParentheses::No; if let Some(ty) = params.args.iter().find_map(|arg| match arg { GenericArg::Type(ty) => Some(ty), _ => None, diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index 8ea5475fb2527..7dfb0956077e4 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -10,7 +10,7 @@ use rustc_hir::{ def::{CtorOf, DefKind, Res}, def_id::LocalDefId, intravisit::{walk_inf, walk_ty, Visitor}, - Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericParam, GenericParamKind, HirId, Impl, ImplItemKind, Item, + Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericArgsParentheses, GenericParam, GenericParamKind, HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind, }; use rustc_hir_analysis::hir_ty_to_ty; @@ -100,7 +100,8 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { if let TyKind::Path(QPath::Resolved(_, item_path)) = self_ty.kind; let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args; if parameters.as_ref().map_or(true, |params| { - !params.parenthesized && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_))) + params.parenthesized == GenericArgsParentheses::No + && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_))) }); if !item.span.from_expansion(); if !is_from_proc_macro(cx, item); // expensive, should be last check diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 3a6d23ca5c102..3ee7147828bd5 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -401,14 +401,9 @@ impl HirEqInterExpr<'_, '_, '_> { } fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool { - if !(left.parenthesized || right.parenthesized) { + if left.parenthesized == right.parenthesized { over(left.args, right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work && over(left.bindings, right.bindings, |l, r| self.eq_type_binding(l, r)) - } else if left.parenthesized && right.parenthesized { - over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r)) - && both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| { - self.eq_ty(l, r) - }) } else { false } From 3259b48568c4fe19b60a47011c9fe424b707533c Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Tue, 28 Mar 2023 12:22:22 +0000 Subject: [PATCH 33/86] Migrate `format_args.rs` to `rustc_ast::FormatArgs` No longer lints empty precisions `{:.}` as the spans aren't available --- clippy_lints/Cargo.toml | 1 + clippy_lints/src/format_args.rs | 210 +++++++++++------- .../src/utils/format_args_collector.rs | 79 ++++++- clippy_utils/src/macros.rs | 73 +++--- .../uninlined_format_args.stderr | 22 +- tests/ui/uninlined_format_args.fixed | 2 +- tests/ui/uninlined_format_args.stderr | 14 +- tests/ui/unused_format_specs.fixed | 18 -- tests/ui/unused_format_specs.rs | 18 -- tests/ui/unused_format_specs.stderr | 54 ----- tests/ui/unused_format_specs_unfixable.stderr | 12 +- 11 files changed, 264 insertions(+), 239 deletions(-) delete mode 100644 tests/ui/unused_format_specs.fixed delete mode 100644 tests/ui/unused_format_specs.rs delete mode 100644 tests/ui/unused_format_specs.stderr diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 6701bedfe5ed8..5c6040f63f50f 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -9,6 +9,7 @@ keywords = ["clippy", "lint", "plugin"] edition = "2021" [dependencies] +arrayvec = { version = "0.7", default-features = false } cargo_metadata = "0.15.3" clippy_utils = { path = "../clippy_utils" } declare_clippy_lint = { path = "../declare_clippy_lint" } diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index e7caed65bf28c..08e45ed7d0ec8 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -1,27 +1,31 @@ +use arrayvec::ArrayVec; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::is_diag_trait_item; -use clippy_utils::macros::FormatParamKind::{Implicit, Named, NamedInline, Numbered, Starred}; use clippy_utils::macros::{ - is_assert_macro, is_format_macro, is_panic, root_macro_call, Count, FormatArg, FormatArgsExpn, FormatParam, - FormatParamUsage, + find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, + is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage, }; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{implements_trait, is_type_lang_item}; use if_chain::if_chain; use itertools::Itertools; +use rustc_ast::{ + FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, + FormatPlaceholder, FormatTrait, +}; use rustc_errors::{ Applicability, SuggestionStyle::{CompletelyHidden, ShowCode}, }; -use rustc_hir::{Expr, ExprKind, HirId, LangItem, QPath}; +use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::Ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::DefId; use rustc_span::edition::Edition::Edition2021; -use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol}; +use rustc_span::{sym, Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -184,72 +188,79 @@ impl FormatArgs { impl<'tcx> LateLintPass<'tcx> for FormatArgs { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if let Some(format_args) = FormatArgsExpn::parse(cx, expr) - && let expr_expn_data = expr.span.ctxt().outer_expn_data() - && let outermost_expn_data = outermost_expn_data(expr_expn_data) - && let Some(macro_def_id) = outermost_expn_data.macro_def_id - && is_format_macro(cx, macro_def_id) - && let ExpnKind::Macro(_, name) = outermost_expn_data.kind - { - for arg in &format_args.args { - check_unused_format_specifier(cx, arg); - if !arg.format.is_default() { - continue; - } - if is_aliased(&format_args, arg.param.value.hir_id) { - continue; + let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return }; + if !is_format_macro(cx, macro_call.def_id) { + return; + } + let name = cx.tcx.item_name(macro_call.def_id); + + find_format_args(cx, expr, macro_call.expn, |format_args| { + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece + && let Ok(index) = placeholder.argument.index + && let Some(arg) = format_args.arguments.all_args().get(index) + { + let arg_expr = find_format_arg_expr(expr, arg); + + check_unused_format_specifier(cx, placeholder, arg_expr); + + if placeholder.format_trait != FormatTrait::Display + || placeholder.format_options != FormatOptions::default() + || is_aliased(format_args, index) + { + continue; + } + + if let Ok(arg_hir_expr) = arg_expr { + check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr); + check_to_string_in_format_args(cx, name, arg_hir_expr); + } } - check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value); - check_to_string_in_format_args(cx, name, arg.param.value); } + if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) { - check_uninlined_args(cx, &format_args, outermost_expn_data.call_site, macro_def_id, self.ignore_mixed); + check_uninlined_args(cx, format_args, macro_call.span, macro_call.def_id, self.ignore_mixed); } - } + }); } extract_msrv_attr!(LateContext); } -fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) { - let param_ty = cx.typeck_results().expr_ty(arg.param.value).peel_refs(); +fn check_unused_format_specifier( + cx: &LateContext<'_>, + placeholder: &FormatPlaceholder, + arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>, +) { + let ty_or_ast_expr = arg_expr.map(|expr| cx.typeck_results().expr_ty(expr).peel_refs()); - if let Count::Implied(Some(mut span)) = arg.format.precision - && !span.is_empty() - { - span_lint_and_then( - cx, - UNUSED_FORMAT_SPECS, - span, - "empty precision specifier has no effect", - |diag| { - if param_ty.is_floating_point() { - diag.note("a precision specifier is not required to format floats"); - } + let is_format_args = match ty_or_ast_expr { + Ok(ty) => is_type_lang_item(cx, ty, LangItem::FormatArguments), + Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)), + }; - if arg.format.is_default() { - // If there's no other specifiers remove the `:` too - span = arg.format_span(); - } + let options = &placeholder.format_options; - diag.span_suggestion_verbose(span, "remove the `.`", "", Applicability::MachineApplicable); - }, - ); - } + let arg_span = match arg_expr { + Ok(expr) => expr.span, + Err(expr) => expr.span, + }; - if is_type_lang_item(cx, param_ty, LangItem::FormatArguments) && !arg.format.is_default_for_trait() { + if let Some(placeholder_span) = placeholder.span + && is_format_args + && *options != FormatOptions::default() + { span_lint_and_then( cx, UNUSED_FORMAT_SPECS, - arg.span, + placeholder_span, "format specifiers have no effect on `format_args!()`", |diag| { - let mut suggest_format = |spec, span| { + let mut suggest_format = |spec| { let message = format!("for the {spec} to apply consider using `format!()`"); - if let Some(mac_call) = root_macro_call(arg.param.value.span) + if let Some(mac_call) = root_macro_call(arg_span) && cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id) - && arg.span.eq_ctxt(mac_call.span) { diag.span_suggestion( cx.sess().source_map().span_until_char(mac_call.span, '!'), @@ -257,25 +268,27 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) { "format", Applicability::MaybeIncorrect, ); - } else if let Some(span) = span { - diag.span_help(span, message); + } else { + diag.help(message); } }; - if !arg.format.width.is_implied() { - suggest_format("width", arg.format.width.span()); + if options.width.is_some() { + suggest_format("width"); } - if !arg.format.precision.is_implied() { - suggest_format("precision", arg.format.precision.span()); + if options.precision.is_some() { + suggest_format("precision"); } - diag.span_suggestion_verbose( - arg.format_span(), - "if the current behavior is intentional, remove the format specifiers", - "", - Applicability::MaybeIncorrect, - ); + if let Some(format_span) = format_placeholder_format_span(placeholder) { + diag.span_suggestion_verbose( + format_span, + "if the current behavior is intentional, remove the format specifiers", + "", + Applicability::MaybeIncorrect, + ); + } }, ); } @@ -283,12 +296,12 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) { fn check_uninlined_args( cx: &LateContext<'_>, - args: &FormatArgsExpn<'_>, + args: &rustc_ast::FormatArgs, call_site: Span, def_id: DefId, ignore_mixed: bool, ) { - if args.format_string.span.from_expansion() { + if args.span.from_expansion() { return; } if call_site.edition() < Edition2021 && (is_panic(cx, def_id) || is_assert_macro(cx, def_id)) { @@ -303,7 +316,13 @@ fn check_uninlined_args( // we cannot remove any other arguments in the format string, // because the index numbers might be wrong after inlining. // Example of an un-inlinable format: print!("{}{1}", foo, 2) - if !args.params().all(|p| check_one_arg(args, &p, &mut fixes, ignore_mixed)) || fixes.is_empty() { + for (pos, usage) in format_arg_positions(args) { + if !check_one_arg(args, pos, usage, &mut fixes, ignore_mixed) { + return; + } + } + + if fixes.is_empty() { return; } @@ -332,38 +351,36 @@ fn check_uninlined_args( } fn check_one_arg( - args: &FormatArgsExpn<'_>, - param: &FormatParam<'_>, + args: &rustc_ast::FormatArgs, + pos: &FormatArgPosition, + usage: FormatParamUsage, fixes: &mut Vec<(Span, String)>, ignore_mixed: bool, ) -> bool { - if matches!(param.kind, Implicit | Starred | Named(_) | Numbered) - && let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind - && let [segment] = path.segments + let index = pos.index.unwrap(); + let arg = &args.arguments.all_args()[index]; + + if !matches!(arg.kind, FormatArgumentKind::Captured(_)) + && let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind + && let [segment] = path.segments.as_slice() && segment.args.is_none() - && let Some(arg_span) = args.value_with_prev_comma_span(param.value.hir_id) + && let Some(arg_span) = format_arg_removal_span(args, index) + && let Some(pos_span) = pos.span { - let replacement = match param.usage { + let replacement = match usage { FormatParamUsage::Argument => segment.ident.name.to_string(), FormatParamUsage::Width => format!("{}$", segment.ident.name), FormatParamUsage::Precision => format!(".{}$", segment.ident.name), }; - fixes.push((param.span, replacement)); + fixes.push((pos_span, replacement)); fixes.push((arg_span, String::new())); true // successful inlining, continue checking } else { // Do not continue inlining (return false) in case // * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)` // * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already - param.kind != Numbered && (!ignore_mixed || matches!(param.kind, NamedInline(_))) - } -} - -fn outermost_expn_data(expn_data: ExpnData) -> ExpnData { - if expn_data.call_site.from_expansion() { - outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data()) - } else { - expn_data + pos.kind != FormatArgPositionKind::Number + && (!ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_))) } } @@ -438,10 +455,31 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex } } -/// Returns true if `hir_id` is referred to by multiple format params -fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool { - args.params() - .filter(|param| param.value.hir_id == hir_id) +fn format_arg_positions( + format_args: &rustc_ast::FormatArgs, +) -> impl Iterator { + format_args.template.iter().flat_map(|piece| match piece { + FormatArgsPiece::Placeholder(placeholder) => { + let mut positions = ArrayVec::<_, 3>::new(); + + positions.push((&placeholder.argument, FormatParamUsage::Argument)); + if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width { + positions.push((position, FormatParamUsage::Width)); + } + if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision { + positions.push((position, FormatParamUsage::Precision)); + } + + positions + }, + FormatArgsPiece::Literal(_) => ArrayVec::new(), + }) +} + +/// Returns true if the format argument at `index` is referred to by multiple format params +fn is_aliased(format_args: &rustc_ast::FormatArgs, index: usize) -> bool { + format_arg_positions(format_args) + .filter(|(position, _)| position.index == Ok(index)) .at_most_one() .is_err() } diff --git a/clippy_lints/src/utils/format_args_collector.rs b/clippy_lints/src/utils/format_args_collector.rs index be56b842b98c6..09fcb82c37c88 100644 --- a/clippy_lints/src/utils/format_args_collector.rs +++ b/clippy_lints/src/utils/format_args_collector.rs @@ -1,7 +1,12 @@ use clippy_utils::macros::collect_ast_format_args; -use rustc_ast::{Expr, ExprKind}; +use clippy_utils::source::snippet_opt; +use itertools::Itertools; +use rustc_ast::{Expr, ExprKind, FormatArgs}; +use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::hygiene; +use std::iter::once; declare_clippy_lint! { /// ### What it does @@ -15,9 +20,79 @@ declare_clippy_lint! { declare_lint_pass!(FormatArgsCollector => [FORMAT_ARGS_COLLECTOR]); impl EarlyLintPass for FormatArgsCollector { - fn check_expr(&mut self, _: &EarlyContext<'_>, expr: &Expr) { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::FormatArgs(args) = &expr.kind { + if has_span_from_proc_macro(cx, args) { + return; + } + collect_ast_format_args(expr.span, args); } } } + +/// Detects if the format string or an argument has its span set by a proc macro to something inside +/// a macro callsite, e.g. +/// +/// ```ignore +/// println!(some_proc_macro!("input {}"), a); +/// ``` +/// +/// Where `some_proc_macro` expands to +/// +/// ```ignore +/// println!("output {}", a); +/// ``` +/// +/// But with the span of `"output {}"` set to the macro input +/// +/// ```ignore +/// println!(some_proc_macro!("input {}"), a); +/// // ^^^^^^^^^^ +/// ``` +fn has_span_from_proc_macro(cx: &EarlyContext<'_>, args: &FormatArgs) -> bool { + let ctxt = args.span.ctxt(); + + // `format!("{} {} {c}", "one", "two", c = "three")` + // ^^^^^ ^^^^^ ^^^^^^^ + let argument_span = args + .arguments + .explicit_args() + .iter() + .map(|argument| hygiene::walk_chain(argument.expr.span, ctxt)); + + // `format!("{} {} {c}", "one", "two", c = "three")` + // ^^ ^^ ^^^^^^ + let between_spans = once(args.span) + .chain(argument_span) + .tuple_windows() + .map(|(start, end)| start.between(end)); + + for between_span in between_spans { + let mut seen_comma = false; + + let Some(snippet) = snippet_opt(cx, between_span) else { return true }; + for token in tokenize(&snippet) { + match token.kind { + TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {}, + TokenKind::Comma if !seen_comma => seen_comma = true, + // named arguments, `start_val, name = end_val` + // ^^^^^^^^^ between_span + TokenKind::Ident | TokenKind::Eq if seen_comma => {}, + // An unexpected token usually indicates that we crossed a macro boundary + // + // `println!(some_proc_macro!("input {}"), a)` + // ^^^ between_span + // `println!("{}", val!(x))` + // ^^^^^^^ between_span + _ => return true, + } + } + + if !seen_comma { + return true; + } + } + + false +} diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index c0e32068ecacc..b03e0d4a1ca59 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -6,7 +6,7 @@ use crate::visitors::{for_each_expr, Descend}; use arrayvec::ArrayVec; use itertools::{izip, Either, Itertools}; use rustc_ast::ast::LitKind; -use rustc_ast::FormatArgs; +use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, LangItem, Node, QPath, TyKind}; @@ -391,30 +391,67 @@ pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) { }); } -/// Calls `callback` with an AST [`FormatArgs`] node if one is found +/// Calls `callback` with an AST [`FormatArgs`] node if a `format_args` expansion is found as a +/// descendant of `expn_id` pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs)) { let format_args_expr = for_each_expr(start, |expr| { let ctxt = expr.span.ctxt(); - if ctxt == start.span.ctxt() { - ControlFlow::Continue(Descend::Yes) - } else if ctxt.outer_expn().is_descendant_of(expn_id) - && macro_backtrace(expr.span) + if ctxt.outer_expn().is_descendant_of(expn_id) { + if macro_backtrace(expr.span) .map(|macro_call| cx.tcx.item_name(macro_call.def_id)) .any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl)) - { - ControlFlow::Break(expr) + { + ControlFlow::Break(expr) + } else { + ControlFlow::Continue(Descend::Yes) + } } else { ControlFlow::Continue(Descend::No) } }); - if let Some(format_args_expr) = format_args_expr { + if let Some(expr) = format_args_expr { AST_FORMAT_ARGS.with(|ast_format_args| { - ast_format_args.borrow().get(&format_args_expr.span).map(callback); + ast_format_args.borrow().get(&expr.span).map(callback); }); } } +/// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if +/// it cannot be found it will return the [`rustc_ast::Expr`]. +pub fn find_format_arg_expr<'hir, 'ast>( + start: &'hir Expr<'hir>, + target: &'ast FormatArgument, +) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr> { + for_each_expr(start, |expr| { + if expr.span == target.expr.span { + ControlFlow::Break(expr) + } else { + ControlFlow::Continue(()) + } + }) + .ok_or(&target.expr) +} + +/// Span of the `:` and format specifiers +/// +/// ```ignore +/// format!("{:.}"), format!("{foo:.}") +/// ^^ ^^ +/// ``` +pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option { + let base = placeholder.span?.data(); + + // `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing + // brace `{...|}` + Some(Span::new( + placeholder.argument.span?.hi(), + base.hi - BytePos(1), + base.ctxt, + base.parent, + )) +} + /// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value /// `10` /// @@ -897,22 +934,6 @@ pub struct FormatArg<'tcx> { pub span: Span, } -impl<'tcx> FormatArg<'tcx> { - /// Span of the `:` and format specifiers - /// - /// ```ignore - /// format!("{:.}"), format!("{foo:.}") - /// ^^ ^^ - /// ``` - pub fn format_span(&self) -> Span { - let base = self.span.data(); - - // `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing - // brace `{...|}` - Span::new(self.param.span.hi(), base.hi - BytePos(1), base.ctxt, base.parent) - } -} - /// A parsed `format_args!` expansion. #[derive(Debug)] pub struct FormatArgsExpn<'tcx> { diff --git a/tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.stderr b/tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.stderr index ee94176219615..1be0cda12fc11 100644 --- a/tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.stderr +++ b/tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.stderr @@ -11,29 +11,29 @@ LL - println!("val='{}'", local_i32); LL + println!("val='{local_i32}'"); | -error: literal with an empty format string - --> $DIR/uninlined_format_args.rs:10:35 +error: variables can be used directly in the `format!` string + --> $DIR/uninlined_format_args.rs:10:5 | LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64); - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: `-D clippy::print-literal` implied by `-D warnings` -help: try this +help: change this to | LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64); -LL + println!("Hello x is {:.*}", local_i32, local_f64); +LL + println!("Hello {} is {local_f64:.local_i32$}", "x"); | -error: variables can be used directly in the `format!` string - --> $DIR/uninlined_format_args.rs:10:5 +error: literal with an empty format string + --> $DIR/uninlined_format_args.rs:10:35 | LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^ | -help: change this to + = note: `-D clippy::print-literal` implied by `-D warnings` +help: try this | LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64); -LL + println!("Hello {} is {local_f64:.local_i32$}", "x"); +LL + println!("Hello x is {:.*}", local_i32, local_f64); | error: variables can be used directly in the `format!` string diff --git a/tests/ui/uninlined_format_args.fixed b/tests/ui/uninlined_format_args.fixed index 1475d781c6734..5ecb9a5bf9eb3 100644 --- a/tests/ui/uninlined_format_args.fixed +++ b/tests/ui/uninlined_format_args.fixed @@ -119,7 +119,7 @@ fn tester(fn_arg: i32) { println!("Width = {local_i32}, value with width = {local_f64:local_i32$}"); println!("{local_i32:width$.prec$}"); println!("{width:width$.prec$}"); - println!("{}", format!("{local_i32}")); + println!("{}", format!("{}", local_i32)); my_println!("{}", local_i32); my_println_args!("{}", local_i32); diff --git a/tests/ui/uninlined_format_args.stderr b/tests/ui/uninlined_format_args.stderr index a12abf8bef8af..dc4af6ef42ecd 100644 --- a/tests/ui/uninlined_format_args.stderr +++ b/tests/ui/uninlined_format_args.stderr @@ -774,18 +774,6 @@ LL - println!("{:w$.p$}", w = width, p = prec); LL + println!("{width:width$.prec$}"); | -error: variables can be used directly in the `format!` string - --> $DIR/uninlined_format_args.rs:125:20 - | -LL | println!("{}", format!("{}", local_i32)); - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: change this to - | -LL - println!("{}", format!("{}", local_i32)); -LL + println!("{}", format!("{local_i32}")); - | - error: variables can be used directly in the `format!` string --> $DIR/uninlined_format_args.rs:143:5 | @@ -856,5 +844,5 @@ LL - println!("expand='{}'", local_i32); LL + println!("expand='{local_i32}'"); | -error: aborting due to 72 previous errors +error: aborting due to 71 previous errors diff --git a/tests/ui/unused_format_specs.fixed b/tests/ui/unused_format_specs.fixed deleted file mode 100644 index 2930722b42d9d..0000000000000 --- a/tests/ui/unused_format_specs.fixed +++ /dev/null @@ -1,18 +0,0 @@ -// run-rustfix - -#![warn(clippy::unused_format_specs)] -#![allow(unused)] - -fn main() { - let f = 1.0f64; - println!("{}", 1.0); - println!("{f} {f:?}"); - - println!("{}", 1); -} - -fn should_not_lint() { - let f = 1.0f64; - println!("{:.1}", 1.0); - println!("{f:.w$} {f:.*?}", 3, w = 2); -} diff --git a/tests/ui/unused_format_specs.rs b/tests/ui/unused_format_specs.rs deleted file mode 100644 index ee192a000d4b5..0000000000000 --- a/tests/ui/unused_format_specs.rs +++ /dev/null @@ -1,18 +0,0 @@ -// run-rustfix - -#![warn(clippy::unused_format_specs)] -#![allow(unused)] - -fn main() { - let f = 1.0f64; - println!("{:.}", 1.0); - println!("{f:.} {f:.?}"); - - println!("{:.}", 1); -} - -fn should_not_lint() { - let f = 1.0f64; - println!("{:.1}", 1.0); - println!("{f:.w$} {f:.*?}", 3, w = 2); -} diff --git a/tests/ui/unused_format_specs.stderr b/tests/ui/unused_format_specs.stderr deleted file mode 100644 index 7231c17e74c19..0000000000000 --- a/tests/ui/unused_format_specs.stderr +++ /dev/null @@ -1,54 +0,0 @@ -error: empty precision specifier has no effect - --> $DIR/unused_format_specs.rs:8:17 - | -LL | println!("{:.}", 1.0); - | ^ - | - = note: a precision specifier is not required to format floats - = note: `-D clippy::unused-format-specs` implied by `-D warnings` -help: remove the `.` - | -LL - println!("{:.}", 1.0); -LL + println!("{}", 1.0); - | - -error: empty precision specifier has no effect - --> $DIR/unused_format_specs.rs:9:18 - | -LL | println!("{f:.} {f:.?}"); - | ^ - | - = note: a precision specifier is not required to format floats -help: remove the `.` - | -LL - println!("{f:.} {f:.?}"); -LL + println!("{f} {f:.?}"); - | - -error: empty precision specifier has no effect - --> $DIR/unused_format_specs.rs:9:24 - | -LL | println!("{f:.} {f:.?}"); - | ^ - | - = note: a precision specifier is not required to format floats -help: remove the `.` - | -LL - println!("{f:.} {f:.?}"); -LL + println!("{f:.} {f:?}"); - | - -error: empty precision specifier has no effect - --> $DIR/unused_format_specs.rs:11:17 - | -LL | println!("{:.}", 1); - | ^ - | -help: remove the `.` - | -LL - println!("{:.}", 1); -LL + println!("{}", 1); - | - -error: aborting due to 4 previous errors - diff --git a/tests/ui/unused_format_specs_unfixable.stderr b/tests/ui/unused_format_specs_unfixable.stderr index 9f1890282e6ac..cb7156b6baf0d 100644 --- a/tests/ui/unused_format_specs_unfixable.stderr +++ b/tests/ui/unused_format_specs_unfixable.stderr @@ -37,11 +37,7 @@ error: format specifiers have no effect on `format_args!()` LL | println!("{:5}.", format_args_from_macro!()); | ^^^^ | -help: for the width to apply consider using `format!()` - --> $DIR/unused_format_specs_unfixable.rs:16:17 - | -LL | println!("{:5}.", format_args_from_macro!()); - | ^ + = help: for the width to apply consider using `format!()` help: if the current behavior is intentional, remove the format specifiers | LL - println!("{:5}.", format_args_from_macro!()); @@ -54,11 +50,7 @@ error: format specifiers have no effect on `format_args!()` LL | println!("{args:5}"); | ^^^^^^^^ | -help: for the width to apply consider using `format!()` - --> $DIR/unused_format_specs_unfixable.rs:19:21 - | -LL | println!("{args:5}"); - | ^ + = help: for the width to apply consider using `format!()` help: if the current behavior is intentional, remove the format specifiers | LL - println!("{args:5}"); From 6589d794927451ed5d503b108e0e6900bbf91d3d Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Tue, 28 Mar 2023 21:28:57 +0000 Subject: [PATCH 34/86] Replace remaining usage of `FormatArgsExpn` --- clippy_lints/src/explicit_write.rs | 29 +- clippy_lints/src/format.rs | 91 ++- clippy_lints/src/format_impl.rs | 60 +- clippy_lints/src/methods/expect_fun_call.rs | 27 +- clippy_utils/src/lib.rs | 1 - clippy_utils/src/macros.rs | 727 +------------------- 6 files changed, 132 insertions(+), 803 deletions(-) diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index c0ea6f338a230..315df6c714ffc 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::macros::FormatArgsExpn; +use clippy_utils::macros::{find_format_args, format_args_inputs_span}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_expn_of, match_function_call, paths}; use if_chain::if_chain; @@ -8,7 +8,7 @@ use rustc_hir::def::Res; use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; +use rustc_span::{sym, ExpnId}; declare_clippy_lint! { /// ### What it does @@ -43,23 +43,22 @@ declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]); impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if_chain! { - // match call to unwrap - if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind; - if unwrap_fun.ident.name == sym::unwrap; + // match call to unwrap + if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind + && unwrap_fun.ident.name == sym::unwrap // match call to write_fmt - if let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind); - if write_fun.ident.name == sym!(write_fmt); + && let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind) + && write_fun.ident.name == sym!(write_fmt) // match calls to std::io::stdout() / std::io::stderr () - if let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() { + && let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() { Some("stdout") } else if match_function_call(cx, write_recv, &paths::STDERR).is_some() { Some("stderr") } else { None - }; - if let Some(format_args) = FormatArgsExpn::parse(cx, write_arg); - then { + } + { + find_format_args(cx, write_arg, ExpnId::root(), |format_args| { let calling_macro = // ordering is important here, since `writeln!` uses `write!` internally if is_expn_of(write_call.span, "writeln").is_some() { @@ -92,7 +91,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { let mut applicability = Applicability::MachineApplicable; let inputs_snippet = snippet_with_applicability( cx, - format_args.inputs_span(), + format_args_inputs_span(format_args), "..", &mut applicability, ); @@ -104,8 +103,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { "try this", format!("{prefix}{sugg_mac}!({inputs_snippet})"), applicability, - ) - } + ); + }); } } } diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 8040938c6263e..d34d6e9279e4d 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -1,14 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn}; -use clippy_utils::source::snippet_with_context; +use clippy_utils::macros::{find_format_arg_expr, find_format_args, root_macro_call_first_node}; +use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::sugg::Sugg; -use if_chain::if_chain; +use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::symbol::kw; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -44,55 +43,53 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]); impl<'tcx> LateLintPass<'tcx> for UselessFormat { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let (format_args, call_site) = if_chain! { - if let Some(macro_call) = root_macro_call_first_node(cx, expr); - if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id); - if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn); - then { - (format_args, macro_call.span) - } else { - return - } - }; + let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return }; + if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) { + return; + } + + find_format_args(cx, expr, macro_call.expn, |format_args| { + let mut applicability = Applicability::MachineApplicable; + let call_site = macro_call.span; - let mut applicability = Applicability::MachineApplicable; - if format_args.args.is_empty() { - match *format_args.format_string.parts { - [] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability), - [_] => { + match (format_args.arguments.all_args(), &format_args.template[..]) { + ([], []) => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability), + ([], [_]) => { // Simulate macro expansion, converting {{ and }} to { and }. - let s_expand = format_args.format_string.snippet.replace("{{", "{").replace("}}", "}"); + let Some(snippet) = snippet_opt(cx, format_args.span) else { return }; + let s_expand = snippet.replace("{{", "{").replace("}}", "}"); let sugg = format!("{s_expand}.to_string()"); span_useless_format(cx, call_site, sugg, applicability); }, - [..] => {}, - } - } else if let [arg] = &*format_args.args { - let value = arg.param.value; - if_chain! { - if format_args.format_string.parts == [kw::Empty]; - if arg.format.is_default(); - if match cx.typeck_results().expr_ty(value).peel_refs().kind() { - ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(), - ty::Str => true, - _ => false, - }; - then { - let is_new_string = match value.kind { - ExprKind::Binary(..) => true, - ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string, - _ => false, - }; - let sugg = if is_new_string { - snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned() - } else { - let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "", &mut applicability); - format!("{}.to_string()", sugg.maybe_par()) - }; - span_useless_format(cx, call_site, sugg, applicability); - } + ([arg], [piece]) => { + if let Ok(value) = find_format_arg_expr(expr, arg) + && let FormatArgsPiece::Placeholder(placeholder) = piece + && placeholder.format_trait == FormatTrait::Display + && placeholder.format_options == FormatOptions::default() + && match cx.typeck_results().expr_ty(value).peel_refs().kind() { + ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(), + ty::Str => true, + _ => false, + } + { + let is_new_string = match value.kind { + ExprKind::Binary(..) => true, + ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string, + _ => false, + }; + let sugg = if is_new_string { + snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned() + } else { + let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "", &mut applicability); + format!("{}.to_string()", sugg.maybe_par()) + }; + span_useless_format(cx, call_site, sugg, applicability); + + } + }, + _ => {}, } - }; + }); } } diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs index ed1342a546543..e3ddbfb5981f8 100644 --- a/clippy_lints/src/format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -1,11 +1,13 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; -use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn}; +use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node}; use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators}; use if_chain::if_chain; +use rustc_ast::{FormatArgsPiece, FormatTrait}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; use rustc_span::{sym, symbol::kw, Symbol}; declare_clippy_lint! { @@ -89,7 +91,7 @@ declare_clippy_lint! { } #[derive(Clone, Copy)] -struct FormatTrait { +struct FormatTraitNames { /// e.g. `sym::Display` name: Symbol, /// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}` @@ -99,7 +101,7 @@ struct FormatTrait { #[derive(Default)] pub struct FormatImpl { // Whether we are inside Display or Debug trait impl - None for neither - format_trait_impl: Option, + format_trait_impl: Option, } impl FormatImpl { @@ -161,43 +163,57 @@ fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) { } } -fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) { +fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTraitNames) { // Check each arg in format calls - do we ever use Display on self (directly or via deref)? - if_chain! { - if let Some(outer_macro) = root_macro_call_first_node(cx, expr); - if let macro_def_id = outer_macro.def_id; - if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn); - if is_format_macro(cx, macro_def_id); - then { - for arg in format_args.args { - if arg.format.r#trait != impl_trait.name { - continue; + if let Some(outer_macro) = root_macro_call_first_node(cx, expr) + && let macro_def_id = outer_macro.def_id + && is_format_macro(cx, macro_def_id) + { + find_format_args(cx, expr, outer_macro.expn, |format_args| { + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece + && let trait_name = match placeholder.format_trait { + FormatTrait::Display => sym::Display, + FormatTrait::Debug => sym::Debug, + FormatTrait::LowerExp => sym!(LowerExp), + FormatTrait::UpperExp => sym!(UpperExp), + FormatTrait::Octal => sym!(Octal), + FormatTrait::Pointer => sym::Pointer, + FormatTrait::Binary => sym!(Binary), + FormatTrait::LowerHex => sym!(LowerHex), + FormatTrait::UpperHex => sym!(UpperHex), + } + && trait_name == impl_trait.name + && let Ok(index) = placeholder.argument.index + && let Some(arg) = format_args.arguments.all_args().get(index) + && let Ok(arg_expr) = find_format_arg_expr(expr, arg) + { + check_format_arg_self(cx, expr.span, arg_expr, impl_trait); } - check_format_arg_self(cx, expr, &arg, impl_trait); } - } + }); } } -fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) { +fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_trait: FormatTraitNames) { // Handle multiple dereferencing of references e.g. &&self // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl) // Since the argument to fmt is itself a reference: &self - let reference = peel_ref_operators(cx, arg.param.value); + let reference = peel_ref_operators(cx, arg); let map = cx.tcx.hir(); // Is the reference self? if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) { - let FormatTrait { name, .. } = impl_trait; + let FormatTraitNames { name, .. } = impl_trait; span_lint( cx, RECURSIVE_FORMAT_IMPL, - expr.span, + span, &format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"), ); } } -fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) { +fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) { if_chain! { if let Some(macro_call) = root_macro_call_first_node(cx, expr); if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id); @@ -227,7 +243,7 @@ fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: } } -fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option { +fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option { if_chain! { if impl_item.ident.name == sym::fmt; if let ImplItemKind::Fn(_, body_id) = impl_item.kind; @@ -241,7 +257,7 @@ fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Optio .and_then(|param| param.pat.simple_ident()) .map(|ident| ident.name); - Some(FormatTrait { + Some(FormatTraitNames { name, formatter_name, }) diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index a22285058d48e..92d21bb893262 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn}; +use clippy_utils::macros::{find_format_args, format_args_inputs_span, root_macro_call_first_node}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use rustc_errors::Applicability; @@ -136,18 +136,19 @@ pub(super) fn check<'tcx>( if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) { return; } - let Some(format_args) = FormatArgsExpn::find_nested(cx, arg_root, macro_call.expn) else { return }; - let span = format_args.inputs_span(); - let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); - span_lint_and_sugg( - cx, - EXPECT_FUN_CALL, - span_replace_word, - &format!("use of `{name}` followed by a function call"), - "try this", - format!("unwrap_or_else({closure_args} panic!({sugg}))"), - applicability, - ); + find_format_args(cx, arg_root, macro_call.expn, |format_args| { + let span = format_args_inputs_span(format_args); + let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + &format!("use of `{name}` followed by a function call"), + "try this", + format!("unwrap_or_else({closure_args} panic!({sugg}))"), + applicability, + ); + }); return; } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 2e839fdf47288..f4af00599cd26 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -33,7 +33,6 @@ extern crate rustc_lexer; extern crate rustc_lint; extern crate rustc_middle; extern crate rustc_mir_dataflow; -extern crate rustc_parse_format; extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index b03e0d4a1ca59..62d388a5ece8d 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -1,24 +1,16 @@ #![allow(clippy::similar_names)] // `expr` and `expn` -use crate::source::snippet_opt; use crate::visitors::{for_each_expr, Descend}; use arrayvec::ArrayVec; -use itertools::{izip, Either, Itertools}; -use rustc_ast::ast::LitKind; use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder}; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, LangItem, Node, QPath, TyKind}; -use rustc_lexer::unescape::unescape_literal; -use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; +use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath}; use rustc_lint::LateContext; -use rustc_parse_format::{self as rpf, Alignment}; use rustc_span::def_id::DefId; use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; -use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol}; +use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, Symbol}; use std::cell::RefCell; -use std::iter::{once, zip}; use std::ops::ControlFlow; use std::sync::atomic::{AtomicBool, Ordering}; @@ -226,11 +218,11 @@ pub enum PanicExpn<'a> { /// A single argument that implements `Display` - `panic!("{}", object)` Display(&'a Expr<'a>), /// Anything else - `panic!("error {}: {}", a, b)` - Format(FormatArgsExpn<'a>), + Format(&'a Expr<'a>), } impl<'a> PanicExpn<'a> { - pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option { + pub fn parse(expr: &'a Expr<'a>) -> Option { let ExprKind::Call(callee, [arg, rest @ ..]) = &expr.kind else { return None }; let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None }; let result = match path.segments.last().unwrap().ident.as_str() { @@ -240,7 +232,7 @@ impl<'a> PanicExpn<'a> { let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None }; Self::Display(e) }, - "panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?), + "panic_fmt" => Self::Format(arg), // Since Rust 1.52, `assert_{eq,ne}` macros expand to use: // `core::panicking::assert_failed(.., left_val, right_val, None | Some(format_args!(..)));` "assert_failed" => { @@ -252,7 +244,7 @@ impl<'a> PanicExpn<'a> { // `msg_arg` is either `None` (no custom message) or `Some(format_args!(..))` (custom message) let msg_arg = &rest[2]; match msg_arg.kind { - ExprKind::Call(_, [fmt_arg]) => Self::Format(FormatArgsExpn::parse(cx, fmt_arg)?), + ExprKind::Call(_, [fmt_arg]) => Self::Format(fmt_arg), _ => Self::Empty, } }, @@ -304,7 +296,7 @@ fn find_assert_args_inner<'a, const N: usize>( let mut args = ArrayVec::new(); let panic_expn = for_each_expr(expr, |e| { if args.is_full() { - match PanicExpn::parse(cx, e) { + match PanicExpn::parse(e) { Some(expn) => ControlFlow::Break(expn), None => ControlFlow::Continue(Descend::Yes), } @@ -452,6 +444,21 @@ pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option )) } +/// Span covering the format string and values +/// +/// ```ignore +/// format("{}.{}", 10, 11) +/// // ^^^^^^^^^^^^^^^ +/// ``` +pub fn format_args_inputs_span(format_args: &FormatArgs) -> Span { + match format_args.arguments.explicit_args() { + [] => format_args.span, + [.., last] => format_args + .span + .to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt())), + } +} + /// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value /// `10` /// @@ -473,251 +480,6 @@ pub fn format_arg_removal_span(format_args: &FormatArgs, index: usize) -> Option Some(current.with_lo(prev.hi())) } -/// The format string doesn't exist in the HIR, so we reassemble it from source code -#[derive(Debug)] -pub struct FormatString { - /// Span of the whole format string literal, including `[r#]"`. - pub span: Span, - /// Snippet of the whole format string literal, including `[r#]"`. - pub snippet: String, - /// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side. - pub style: Option, - /// The unescaped value of the format string, e.g. `"val – {}"` for the literal - /// `"val \u{2013} {}"`. - pub unescaped: String, - /// The format string split by format args like `{..}`. - pub parts: Vec, -} - -impl FormatString { - fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option { - // format_args!(r"a {} b \", 1); - // - // expands to - // - // ::core::fmt::Arguments::new_v1(&["a ", " b \\"], - // &[::core::fmt::ArgumentV1::new_display(&1)]); - // - // where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"` - let span = pieces.span; - let snippet = snippet_opt(cx, span)?; - - let (inner, style) = match tokenize(&snippet).next()?.kind { - TokenKind::Literal { kind, .. } => { - let style = match kind { - LiteralKind::Str { .. } => None, - LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()), - _ => return None, - }; - - let start = style.map_or(1, |n| 2 + n); - let end = snippet.len() - style.map_or(1, |n| 1 + n); - - (&snippet[start..end], style) - }, - _ => return None, - }; - - let mode = if style.is_some() { - unescape::Mode::RawStr - } else { - unescape::Mode::Str - }; - - let mut unescaped = String::with_capacity(inner.len()); - // Sometimes the original string comes from a macro which accepts a malformed string, such as in a - // #[display(""somestring)] attribute (accepted by the `displaythis` crate). Reconstructing the - // string from the span will not be possible, so we will just return None here. - let mut unparsable = false; - unescape_literal(inner, mode, &mut |_, ch| match ch { - Ok(ch) => unescaped.push(ch), - Err(e) if !e.is_fatal() => (), - Err(_) => unparsable = true, - }); - if unparsable { - return None; - } - - let mut parts = Vec::new(); - let _: Option = for_each_expr(pieces, |expr| { - if let ExprKind::Lit(lit) = &expr.kind - && let LitKind::Str(symbol, _) = lit.node - { - parts.push(symbol); - } - ControlFlow::Continue(()) - }); - - Some(Self { - span, - snippet, - style, - unescaped, - parts, - }) - } -} - -struct FormatArgsValues<'tcx> { - /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for - /// `format!("{x} {} {}", 1, z + 2)`. - value_args: Vec<&'tcx Expr<'tcx>>, - /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in - /// `value_args` - pos_to_value_index: Vec, - /// Used to check if a value is declared inline & to resolve `InnerSpan`s. - format_string_span: SpanData, -} - -impl<'tcx> FormatArgsValues<'tcx> { - fn new_empty(format_string_span: SpanData) -> Self { - Self { - value_args: Vec::new(), - pos_to_value_index: Vec::new(), - format_string_span, - } - } - - fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self { - let mut pos_to_value_index = Vec::new(); - let mut value_args = Vec::new(); - let _: Option = for_each_expr(args, |expr| { - if expr.span.ctxt() == args.span.ctxt() { - // ArgumentV1::new_() - // ArgumentV1::from_usize() - if let ExprKind::Call(callee, [val]) = expr.kind - && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind - && let TyKind::Path(QPath::LangItem(LangItem::FormatArgument, _, _)) = ty.kind - { - let val_idx = if val.span.ctxt() == expr.span.ctxt() - && let ExprKind::Field(_, field) = val.kind - && let Ok(idx) = field.name.as_str().parse() - { - // tuple index - idx - } else { - // assume the value expression is passed directly - pos_to_value_index.len() - }; - - pos_to_value_index.push(val_idx); - } - ControlFlow::Continue(Descend::Yes) - } else { - // assume that any expr with a differing span is a value - value_args.push(expr); - ControlFlow::Continue(Descend::No) - } - }); - - Self { - value_args, - pos_to_value_index, - format_string_span, - } - } -} - -/// The positions of a format argument's value, precision and width -/// -/// A position is an index into the second argument of `Arguments::new_v1[_formatted]` -#[derive(Debug, Default, Copy, Clone)] -struct ParamPosition { - /// The position stored in `rt::v1::Argument::position`. - value: usize, - /// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`. - width: Option, - /// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`. - precision: Option, -} - -impl<'tcx> Visitor<'tcx> for ParamPosition { - fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) { - match field.ident.name { - sym::position => { - if let ExprKind::Lit(lit) = &field.expr.kind - && let LitKind::Int(pos, _) = lit.node - { - self.value = pos as usize; - } - }, - sym::precision => { - self.precision = parse_count(field.expr); - }, - sym::width => { - self.width = parse_count(field.expr); - }, - _ => walk_expr(self, field.expr), - } - } -} - -fn parse_count(expr: &Expr<'_>) -> Option { - // <::core::fmt::rt::v1::Count>::Param(1usize), - if let ExprKind::Call(ctor, [val]) = expr.kind - && let ExprKind::Path(QPath::TypeRelative(_, path)) = ctor.kind - && path.ident.name == sym::Param - && let ExprKind::Lit(lit) = &val.kind - && let LitKind::Int(pos, _) = lit.node - { - Some(pos as usize) - } else { - None - } -} - -/// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)` -fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option + 'tcx> { - if let ExprKind::AddrOf(.., array) = fmt_arg.kind - && let ExprKind::Array(specs) = array.kind - { - Some(specs.iter().map(|spec| { - if let ExprKind::Call(f, args) = spec.kind - && let ExprKind::Path(QPath::TypeRelative(ty, f)) = f.kind - && let TyKind::Path(QPath::LangItem(LangItem::FormatPlaceholder, _, _)) = ty.kind - && f.ident.name == sym::new - && let [position, _fill, _align, _flags, precision, width] = args - && let ExprKind::Lit(position) = &position.kind - && let LitKind::Int(position, _) = position.node { - ParamPosition { - value: position as usize, - width: parse_count(width), - precision: parse_count(precision), - } - } else { - ParamPosition::default() - } - })) - } else { - None - } -} - -/// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan` -fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span { - Span::new( - base.lo + BytePos::from_usize(inner.start), - base.lo + BytePos::from_usize(inner.end), - base.ctxt, - base.parent, - ) -} - -/// How a format parameter is used in the format string -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum FormatParamKind { - /// An implicit parameter , such as `{}` or `{:?}`. - Implicit, - /// A parameter with an explicit number, e.g. `{1}`, `{0:?}`, or `{:.0$}` - Numbered, - /// A parameter with an asterisk precision. e.g. `{:.*}`. - Starred, - /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`. - Named(Symbol), - /// An implicit named parameter, such as the `y` in `format!("{y}")`. - NamedInline(Symbol), -} - /// Where a format parameter is being used in the format string #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum FormatParamUsage { @@ -729,451 +491,6 @@ pub enum FormatParamUsage { Precision, } -/// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g. -/// -/// ``` -/// let precision = 2; -/// format!("{:.precision$}", 0.1234); -/// ``` -/// -/// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234` -/// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2` -#[derive(Debug, Copy, Clone)] -pub struct FormatParam<'tcx> { - /// The expression this parameter refers to. - pub value: &'tcx Expr<'tcx>, - /// How this parameter refers to its `value`. - pub kind: FormatParamKind, - /// Where this format param is being used - argument/width/precision - pub usage: FormatParamUsage, - /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters. - /// - /// ```text - /// format!("{}, { }, {0}, {name}", ...); - /// ^ ~~ ~ ~~~~ - /// ``` - pub span: Span, -} - -impl<'tcx> FormatParam<'tcx> { - fn new( - mut kind: FormatParamKind, - usage: FormatParamUsage, - position: usize, - inner: rpf::InnerSpan, - values: &FormatArgsValues<'tcx>, - ) -> Option { - let value_index = *values.pos_to_value_index.get(position)?; - let value = *values.value_args.get(value_index)?; - let span = span_from_inner(values.format_string_span, inner); - - // if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points - // into the format string - if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) { - kind = FormatParamKind::NamedInline(name); - } - - Some(Self { - value, - kind, - usage, - span, - }) - } -} - -/// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and -/// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers. -#[derive(Debug, Copy, Clone)] -pub enum Count<'tcx> { - /// Specified with a literal number, stores the value. - Is(usize, Span), - /// Specified using `$` and `*` syntaxes. The `*` format is still considered to be - /// `FormatParamKind::Numbered`. - Param(FormatParam<'tcx>), - /// Not specified. - Implied(Option), -} - -impl<'tcx> Count<'tcx> { - fn new( - usage: FormatParamUsage, - count: rpf::Count<'_>, - position: Option, - inner: Option, - values: &FormatArgsValues<'tcx>, - ) -> Option { - let span = inner.map(|inner| span_from_inner(values.format_string_span, inner)); - - Some(match count { - rpf::Count::CountIs(val) => Self::Is(val, span?), - rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new( - FormatParamKind::Named(Symbol::intern(name)), - usage, - position?, - inner?, - values, - )?), - rpf::Count::CountIsParam(_) => Self::Param(FormatParam::new( - FormatParamKind::Numbered, - usage, - position?, - inner?, - values, - )?), - rpf::Count::CountIsStar(_) => Self::Param(FormatParam::new( - FormatParamKind::Starred, - usage, - position?, - inner?, - values, - )?), - rpf::Count::CountImplied => Self::Implied(span), - }) - } - - pub fn is_implied(self) -> bool { - matches!(self, Count::Implied(_)) - } - - pub fn param(self) -> Option> { - match self { - Count::Param(param) => Some(param), - _ => None, - } - } - - pub fn span(self) -> Option { - match self { - Count::Is(_, span) => Some(span), - Count::Param(param) => Some(param.span), - Count::Implied(span) => span, - } - } -} - -/// Specification for the formatting of an argument in the format string. See -/// for the precise meanings. -#[derive(Debug)] -pub struct FormatSpec<'tcx> { - /// Optionally specified character to fill alignment with. - pub fill: Option, - /// Optionally specified alignment. - pub align: Alignment, - /// Whether all flag options are set to default (no flags specified). - pub no_flags: bool, - /// Represents either the maximum width or the integer precision. - pub precision: Count<'tcx>, - /// The minimum width, will be padded according to `width`/`align` - pub width: Count<'tcx>, - /// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for - /// `{:?}`. - pub r#trait: Symbol, - pub trait_span: Option, -} - -impl<'tcx> FormatSpec<'tcx> { - fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option { - Some(Self { - fill: spec.fill, - align: spec.align, - no_flags: spec.sign.is_none() && !spec.alternate && !spec.zero_pad && spec.debug_hex.is_none(), - precision: Count::new( - FormatParamUsage::Precision, - spec.precision, - positions.precision, - spec.precision_span, - values, - )?, - width: Count::new( - FormatParamUsage::Width, - spec.width, - positions.width, - spec.width_span, - values, - )?, - r#trait: match spec.ty { - "" => sym::Display, - "?" => sym::Debug, - "o" => sym!(Octal), - "x" => sym!(LowerHex), - "X" => sym!(UpperHex), - "p" => sym::Pointer, - "b" => sym!(Binary), - "e" => sym!(LowerExp), - "E" => sym!(UpperExp), - _ => return None, - }, - trait_span: spec - .ty_span - .map(|span| span_from_inner(values.format_string_span, span)), - }) - } - - /// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`, - /// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}` - pub fn is_default(&self) -> bool { - self.r#trait == sym::Display && self.is_default_for_trait() - } - - /// Has no other formatting specifiers than setting the format trait. returns true for `{}`, - /// `{foo}`, `{:?}`, but false for `{foo:5}`, `{3:.5?}` - pub fn is_default_for_trait(&self) -> bool { - self.width.is_implied() && self.precision.is_implied() && self.align == Alignment::AlignUnknown && self.no_flags - } -} - -/// A format argument, such as `{}`, `{foo:?}`. -#[derive(Debug)] -pub struct FormatArg<'tcx> { - /// The parameter the argument refers to. - pub param: FormatParam<'tcx>, - /// How to format `param`. - pub format: FormatSpec<'tcx>, - /// span of the whole argument, `{..}`. - pub span: Span, -} - -/// A parsed `format_args!` expansion. -#[derive(Debug)] -pub struct FormatArgsExpn<'tcx> { - /// The format string literal. - pub format_string: FormatString, - /// The format arguments, such as `{:?}`. - pub args: Vec>, - /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will - /// include this added newline. - pub newline: bool, - /// Spans of the commas between the format string and explicit values, excluding any trailing - /// comma - /// - /// ```ignore - /// format!("..", 1, 2, 3,) - /// // ^ ^ ^ - /// ``` - comma_spans: Vec, - /// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for - /// `format!("{x} {} {y}", 1, z + 2)`. - explicit_values: Vec<&'tcx Expr<'tcx>>, -} - -impl<'tcx> FormatArgsExpn<'tcx> { - /// Gets the spans of the commas inbetween the format string and explicit args, not including - /// any trailing comma - /// - /// ```ignore - /// format!("{} {}", a, b) - /// // ^ ^ - /// ``` - /// - /// Ensures that the format string and values aren't coming from a proc macro that sets the - /// output span to that of its input - fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option> { - // `format!("{} {} {c}", "one", "two", c = "three")` - // ^^^^^ ^^^^^ ^^^^^^^ - let value_spans = explicit_values - .iter() - .map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt())); - - // `format!("{} {} {c}", "one", "two", c = "three")` - // ^^ ^^ ^^^^^^ - let between_spans = once(fmt_span) - .chain(value_spans) - .tuple_windows() - .map(|(start, end)| start.between(end)); - - let mut comma_spans = Vec::new(); - for between_span in between_spans { - let mut offset = 0; - let mut seen_comma = false; - - for token in tokenize(&snippet_opt(cx, between_span)?) { - match token.kind { - TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {}, - TokenKind::Comma if !seen_comma => { - seen_comma = true; - - let base = between_span.data(); - comma_spans.push(Span::new( - base.lo + BytePos(offset), - base.lo + BytePos(offset + 1), - base.ctxt, - base.parent, - )); - }, - // named arguments, `start_val, name = end_val` - // ^^^^^^^^^ between_span - TokenKind::Ident | TokenKind::Eq if seen_comma => {}, - // An unexpected token usually indicates the format string or a value came from a proc macro output - // that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that - // emits a string literal with the span set to that of `"input"` - _ => return None, - } - offset += token.len; - } - - if !seen_comma { - return None; - } - } - - Some(comma_spans) - } - - pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option { - let macro_name = macro_backtrace(expr.span) - .map(|macro_call| cx.tcx.item_name(macro_call.def_id)) - .find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?; - let newline = macro_name == sym::format_args_nl; - - // ::core::fmt::Arguments::new_const(pieces) - // ::core::fmt::Arguments::new_v1(pieces, args) - // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg) - if let ExprKind::Call(callee, [pieces, rest @ ..]) = expr.kind - && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind - && let TyKind::Path(QPath::LangItem(LangItem::FormatArguments, _, _)) = ty.kind - && matches!(seg.ident.as_str(), "new_const" | "new_v1" | "new_v1_formatted") - { - let format_string = FormatString::new(cx, pieces)?; - - let mut parser = rpf::Parser::new( - &format_string.unescaped, - format_string.style, - Some(format_string.snippet.clone()), - // `format_string.unescaped` does not contain the appended newline - false, - rpf::ParseMode::Format, - ); - - let parsed_args = parser - .by_ref() - .filter_map(|piece| match piece { - rpf::Piece::NextArgument(a) => Some(a), - rpf::Piece::String(_) => None, - }) - .collect_vec(); - if !parser.errors.is_empty() { - return None; - } - - let positions = if let Some(fmt_arg) = rest.get(1) { - // If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse - // them. - - Either::Left(parse_rt_fmt(fmt_arg)?) - } else { - // If no format specs are given, the positions are in the given order and there are - // no `precision`/`width`s to consider. - - Either::Right((0..).map(|n| ParamPosition { - value: n, - width: None, - precision: None, - })) - }; - - let values = if let Some(args) = rest.first() { - FormatArgsValues::new(args, format_string.span.data()) - } else { - FormatArgsValues::new_empty(format_string.span.data()) - }; - - let args = izip!(positions, parsed_args, parser.arg_places) - .map(|(position, parsed_arg, arg_span)| { - Some(FormatArg { - param: FormatParam::new( - match parsed_arg.position { - rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit, - rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered, - // NamedInline is handled by `FormatParam::new()` - rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)), - }, - FormatParamUsage::Argument, - position.value, - parsed_arg.position_span, - &values, - )?, - format: FormatSpec::new(parsed_arg.format, position, &values)?, - span: span_from_inner(values.format_string_span, arg_span), - }) - }) - .collect::>>()?; - - let mut explicit_values = values.value_args; - // remove values generated for implicitly captured vars - let len = explicit_values - .iter() - .take_while(|val| !format_string.span.contains(val.span)) - .count(); - explicit_values.truncate(len); - - let comma_spans = Self::comma_spans(cx, &explicit_values, format_string.span)?; - - Some(Self { - format_string, - args, - newline, - comma_spans, - explicit_values, - }) - } else { - None - } - } - - pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option { - for_each_expr(expr, |e| { - let e_ctxt = e.span.ctxt(); - if e_ctxt == expr.span.ctxt() { - ControlFlow::Continue(Descend::Yes) - } else if e_ctxt.outer_expn().is_descendant_of(expn_id) { - if let Some(args) = FormatArgsExpn::parse(cx, e) { - ControlFlow::Break(args) - } else { - ControlFlow::Continue(Descend::No) - } - } else { - ControlFlow::Continue(Descend::No) - } - }) - } - - /// Source callsite span of all inputs - pub fn inputs_span(&self) -> Span { - match *self.explicit_values { - [] => self.format_string.span, - [.., last] => self - .format_string - .span - .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())), - } - } - - /// Get the span of a value expanded to the previous comma, e.g. for the value `10` - /// - /// ```ignore - /// format("{}.{}", 10, 11) - /// // ^^^^ - /// ``` - pub fn value_with_prev_comma_span(&self, value_id: HirId) -> Option { - for (comma_span, value) in zip(&self.comma_spans, &self.explicit_values) { - if value.hir_id == value_id { - return Some(comma_span.to(hygiene::walk_chain(value.span, comma_span.ctxt()))); - } - } - - None - } - - /// Iterator of all format params, both values and those referenced by `width`/`precision`s. - pub fn params(&'tcx self) -> impl Iterator> { - self.args - .iter() - .flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()]) - .flatten() - } -} - /// A node with a `HirId` and a `Span` pub trait HirNode { fn hir_id(&self) -> HirId; From 783879e6fe48b5bd776bf2b1c9314fda3e30447d Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 28 Mar 2023 20:52:55 -0400 Subject: [PATCH 35/86] Partial no-op refactoring of #9948 This contains preparatory work for #9948 to keep that change to the minimum, and make it easier to review it. --- clippy_dev/src/new_lint.rs | 8 +-- clippy_dev/src/update_lints.rs | 14 ++--- tests/ui/format_args_unfixable.rs | 44 ++++++++++++++ tests/ui/format_args_unfixable.stderr | 36 +++++------ tests/ui/uninlined_format_args.fixed | 86 ++++++++++++++++++++++++++- tests/ui/uninlined_format_args.rs | 86 ++++++++++++++++++++++++++- 6 files changed, 239 insertions(+), 35 deletions(-) diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 420214d9256d8..13a2770342781 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -369,9 +369,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R }} todo!(); }} - "#, - context_import = context_import, - name_upper = name_upper, + "# ); } else { let _: fmt::Result = writedoc!( @@ -385,9 +383,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R pub(super) fn check(cx: &{context_import}) {{ todo!(); }} - "#, - context_import = context_import, - name_upper = name_upper, + "# ); } diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 779e4d0e1e308..95222a9acdfc5 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -537,17 +537,13 @@ fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> { /// Nothing. This lint has been deprecated. /// /// ### Deprecation reason - /// {} - #[clippy::version = \"{}\"] - pub {}, - \"{}\" + /// {deprecation_reason} + #[clippy::version = \"{version}\"] + pub {name}, + \"{reason}\" }} - ", - deprecation_reason, - version, - name, - reason, + " ) } diff --git a/tests/ui/format_args_unfixable.rs b/tests/ui/format_args_unfixable.rs index eb0ac15bfbf12..423bfaf97965e 100644 --- a/tests/ui/format_args_unfixable.rs +++ b/tests/ui/format_args_unfixable.rs @@ -1,4 +1,5 @@ #![warn(clippy::format_in_format_args, clippy::to_string_in_format_args)] +#![allow(unused)] #![allow(clippy::assertions_on_constants, clippy::eq_op, clippy::uninlined_format_args)] use std::io::{stdout, Error, ErrorKind, Write}; @@ -57,3 +58,46 @@ fn main() { my_macro!(); println!("error: {}", my_other_macro!()); } + +macro_rules! _internal { + ($($args:tt)*) => { + println!("{}", format_args!($($args)*)) + }; +} + +macro_rules! my_println2 { + ($target:expr, $($args:tt)+) => {{ + if $target { + _internal!($($args)+) + } + }}; +} + +macro_rules! my_println2_args { + ($target:expr, $($args:tt)+) => {{ + if $target { + _internal!("foo: {}", format_args!($($args)+)) + } + }}; +} + +fn test2() { + let error = Error::new(ErrorKind::Other, "bad thing"); + + // None of these should be linted without the config change + my_println2!(true, "error: {}", format!("something failed at {}", Location::caller())); + my_println2!( + true, + "{}: {}", + error, + format!("something failed at {}", Location::caller()) + ); + + my_println2_args!(true, "error: {}", format!("something failed at {}", Location::caller())); + my_println2_args!( + true, + "{}: {}", + error, + format!("something failed at {}", Location::caller()) + ); +} diff --git a/tests/ui/format_args_unfixable.stderr b/tests/ui/format_args_unfixable.stderr index b291d475ad906..c1be48c3b7269 100644 --- a/tests/ui/format_args_unfixable.stderr +++ b/tests/ui/format_args_unfixable.stderr @@ -1,5 +1,5 @@ error: `format!` in `println!` args - --> $DIR/format_args_unfixable.rs:25:5 + --> $DIR/format_args_unfixable.rs:26:5 | LL | println!("error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | println!("error: {}", format!("something failed at {}", Location::calle = note: `-D clippy::format-in-format-args` implied by `-D warnings` error: `format!` in `println!` args - --> $DIR/format_args_unfixable.rs:26:5 + --> $DIR/format_args_unfixable.rs:27:5 | LL | println!("{}: {}", error, format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -18,7 +18,7 @@ LL | println!("{}: {}", error, format!("something failed at {}", Location::c = help: or consider changing `format!` to `format_args!` error: `format!` in `println!` args - --> $DIR/format_args_unfixable.rs:27:5 + --> $DIR/format_args_unfixable.rs:28:5 | LL | println!("{:?}: {}", error, format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -27,7 +27,7 @@ LL | println!("{:?}: {}", error, format!("something failed at {}", Location: = help: or consider changing `format!` to `format_args!` error: `format!` in `println!` args - --> $DIR/format_args_unfixable.rs:28:5 + --> $DIR/format_args_unfixable.rs:29:5 | LL | println!("{{}}: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -36,7 +36,7 @@ LL | println!("{{}}: {}", format!("something failed at {}", Location::caller = help: or consider changing `format!` to `format_args!` error: `format!` in `println!` args - --> $DIR/format_args_unfixable.rs:29:5 + --> $DIR/format_args_unfixable.rs:30:5 | LL | println!(r#"error: "{}""#, format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -45,7 +45,7 @@ LL | println!(r#"error: "{}""#, format!("something failed at {}", Location:: = help: or consider changing `format!` to `format_args!` error: `format!` in `println!` args - --> $DIR/format_args_unfixable.rs:30:5 + --> $DIR/format_args_unfixable.rs:31:5 | LL | println!("error: {}", format!(r#"something failed at "{}""#, Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -54,7 +54,7 @@ LL | println!("error: {}", format!(r#"something failed at "{}""#, Location:: = help: or consider changing `format!` to `format_args!` error: `format!` in `println!` args - --> $DIR/format_args_unfixable.rs:31:5 + --> $DIR/format_args_unfixable.rs:32:5 | LL | println!("error: {}", format!("something failed at {} {0}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -63,7 +63,7 @@ LL | println!("error: {}", format!("something failed at {} {0}", Location::c = help: or consider changing `format!` to `format_args!` error: `format!` in `format!` args - --> $DIR/format_args_unfixable.rs:32:13 + --> $DIR/format_args_unfixable.rs:33:13 | LL | let _ = format!("error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL | let _ = format!("error: {}", format!("something failed at {}", Location = help: or consider changing `format!` to `format_args!` error: `format!` in `write!` args - --> $DIR/format_args_unfixable.rs:33:13 + --> $DIR/format_args_unfixable.rs:34:13 | LL | let _ = write!( | _____________^ @@ -86,7 +86,7 @@ LL | | ); = help: or consider changing `format!` to `format_args!` error: `format!` in `writeln!` args - --> $DIR/format_args_unfixable.rs:38:13 + --> $DIR/format_args_unfixable.rs:39:13 | LL | let _ = writeln!( | _____________^ @@ -100,7 +100,7 @@ LL | | ); = help: or consider changing `format!` to `format_args!` error: `format!` in `print!` args - --> $DIR/format_args_unfixable.rs:43:5 + --> $DIR/format_args_unfixable.rs:44:5 | LL | print!("error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -109,7 +109,7 @@ LL | print!("error: {}", format!("something failed at {}", Location::caller( = help: or consider changing `format!` to `format_args!` error: `format!` in `eprint!` args - --> $DIR/format_args_unfixable.rs:44:5 + --> $DIR/format_args_unfixable.rs:45:5 | LL | eprint!("error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -118,7 +118,7 @@ LL | eprint!("error: {}", format!("something failed at {}", Location::caller = help: or consider changing `format!` to `format_args!` error: `format!` in `eprintln!` args - --> $DIR/format_args_unfixable.rs:45:5 + --> $DIR/format_args_unfixable.rs:46:5 | LL | eprintln!("error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -127,7 +127,7 @@ LL | eprintln!("error: {}", format!("something failed at {}", Location::call = help: or consider changing `format!` to `format_args!` error: `format!` in `format_args!` args - --> $DIR/format_args_unfixable.rs:46:13 + --> $DIR/format_args_unfixable.rs:47:13 | LL | let _ = format_args!("error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -136,7 +136,7 @@ LL | let _ = format_args!("error: {}", format!("something failed at {}", Loc = help: or consider changing `format!` to `format_args!` error: `format!` in `assert!` args - --> $DIR/format_args_unfixable.rs:47:5 + --> $DIR/format_args_unfixable.rs:48:5 | LL | assert!(true, "error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -145,7 +145,7 @@ LL | assert!(true, "error: {}", format!("something failed at {}", Location:: = help: or consider changing `format!` to `format_args!` error: `format!` in `assert_eq!` args - --> $DIR/format_args_unfixable.rs:48:5 + --> $DIR/format_args_unfixable.rs:49:5 | LL | assert_eq!(0, 0, "error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -154,7 +154,7 @@ LL | assert_eq!(0, 0, "error: {}", format!("something failed at {}", Locatio = help: or consider changing `format!` to `format_args!` error: `format!` in `assert_ne!` args - --> $DIR/format_args_unfixable.rs:49:5 + --> $DIR/format_args_unfixable.rs:50:5 | LL | assert_ne!(0, 0, "error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -163,7 +163,7 @@ LL | assert_ne!(0, 0, "error: {}", format!("something failed at {}", Locatio = help: or consider changing `format!` to `format_args!` error: `format!` in `panic!` args - --> $DIR/format_args_unfixable.rs:50:5 + --> $DIR/format_args_unfixable.rs:51:5 | LL | panic!("error: {}", format!("something failed at {}", Location::caller())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/uninlined_format_args.fixed b/tests/ui/uninlined_format_args.fixed index 5ecb9a5bf9eb3..3122081a44f98 100644 --- a/tests/ui/uninlined_format_args.fixed +++ b/tests/ui/uninlined_format_args.fixed @@ -1,7 +1,7 @@ // aux-build:proc_macros.rs // run-rustfix #![warn(clippy::uninlined_format_args)] -#![allow(named_arguments_used_positionally, unused_imports, unused_macros, unused_variables)] +#![allow(named_arguments_used_positionally, unused)] #![allow(clippy::eq_op, clippy::format_in_format_args, clippy::print_literal)] extern crate proc_macros; @@ -178,3 +178,87 @@ fn _meets_msrv() { fn _do_not_fire() { println!("{:?}", None::<()>); } + +macro_rules! _internal { + ($($args:tt)*) => { + println!("{}", format_args!($($args)*)) + }; +} + +macro_rules! my_println2 { + ($target:expr, $($args:tt)+) => {{ + if $target { + _internal!($($args)+) + } + }}; +} + +macro_rules! my_println2_args { + ($target:expr, $($args:tt)+) => {{ + if $target { + _internal!("foo: {}", format_args!($($args)+)) + } + }}; +} + +macro_rules! my_concat { + ($fmt:literal $(, $e:expr)*) => { + println!(concat!("ERROR: ", $fmt), $($e,)*) + } +} + +macro_rules! my_good_macro { + ($fmt:literal $(, $e:expr)* $(,)?) => { + println!($fmt $(, $e)*) + } +} + +macro_rules! my_bad_macro { + ($fmt:literal, $($e:expr),*) => { + println!($fmt, $($e,)*) + } +} + +macro_rules! my_bad_macro2 { + ($fmt:literal) => { + let s = $fmt.clone(); + println!("{}", s); + }; + ($fmt:literal, $($e:expr)+) => { + println!($fmt, $($e,)*) + }; +} + +// This abomination was suggested by @Alexendoo, may the Rust gods have mercy on their soul... +// https://github.com/rust-lang/rust-clippy/pull/9948#issuecomment-1327965962 +macro_rules! used_twice { + ( + large = $large:literal, + small = $small:literal, + $val:expr, + ) => { + if $val < 5 { + println!($small, $val); + } else { + println!($large, $val); + } + }; +} + +fn tester2() { + let local_i32 = 1; + my_println2_args!(true, "{}", local_i32); + my_println2!(true, "{}", local_i32); + my_concat!("{}", local_i32); + my_good_macro!("{}", local_i32); + my_good_macro!("{}", local_i32,); + + // FIXME: Broken false positives, currently unhandled + my_bad_macro!("{}", local_i32); + my_bad_macro2!("{}", local_i32); + used_twice! { + large = "large value: {}", + small = "small value: {}", + local_i32, + }; +} diff --git a/tests/ui/uninlined_format_args.rs b/tests/ui/uninlined_format_args.rs index 835afac393fae..b153ef256e0c2 100644 --- a/tests/ui/uninlined_format_args.rs +++ b/tests/ui/uninlined_format_args.rs @@ -1,7 +1,7 @@ // aux-build:proc_macros.rs // run-rustfix #![warn(clippy::uninlined_format_args)] -#![allow(named_arguments_used_positionally, unused_imports, unused_macros, unused_variables)] +#![allow(named_arguments_used_positionally, unused)] #![allow(clippy::eq_op, clippy::format_in_format_args, clippy::print_literal)] extern crate proc_macros; @@ -183,3 +183,87 @@ fn _meets_msrv() { fn _do_not_fire() { println!("{:?}", None::<()>); } + +macro_rules! _internal { + ($($args:tt)*) => { + println!("{}", format_args!($($args)*)) + }; +} + +macro_rules! my_println2 { + ($target:expr, $($args:tt)+) => {{ + if $target { + _internal!($($args)+) + } + }}; +} + +macro_rules! my_println2_args { + ($target:expr, $($args:tt)+) => {{ + if $target { + _internal!("foo: {}", format_args!($($args)+)) + } + }}; +} + +macro_rules! my_concat { + ($fmt:literal $(, $e:expr)*) => { + println!(concat!("ERROR: ", $fmt), $($e,)*) + } +} + +macro_rules! my_good_macro { + ($fmt:literal $(, $e:expr)* $(,)?) => { + println!($fmt $(, $e)*) + } +} + +macro_rules! my_bad_macro { + ($fmt:literal, $($e:expr),*) => { + println!($fmt, $($e,)*) + } +} + +macro_rules! my_bad_macro2 { + ($fmt:literal) => { + let s = $fmt.clone(); + println!("{}", s); + }; + ($fmt:literal, $($e:expr)+) => { + println!($fmt, $($e,)*) + }; +} + +// This abomination was suggested by @Alexendoo, may the Rust gods have mercy on their soul... +// https://github.com/rust-lang/rust-clippy/pull/9948#issuecomment-1327965962 +macro_rules! used_twice { + ( + large = $large:literal, + small = $small:literal, + $val:expr, + ) => { + if $val < 5 { + println!($small, $val); + } else { + println!($large, $val); + } + }; +} + +fn tester2() { + let local_i32 = 1; + my_println2_args!(true, "{}", local_i32); + my_println2!(true, "{}", local_i32); + my_concat!("{}", local_i32); + my_good_macro!("{}", local_i32); + my_good_macro!("{}", local_i32,); + + // FIXME: Broken false positives, currently unhandled + my_bad_macro!("{}", local_i32); + my_bad_macro2!("{}", local_i32); + used_twice! { + large = "large value: {}", + small = "small value: {}", + local_i32, + }; +} From 4f5a019d6e836ff4b56154bf6c646cc358310fa9 Mon Sep 17 00:00:00 2001 From: csmoe Date: Wed, 29 Mar 2023 15:12:20 +0800 Subject: [PATCH 36/86] Update clippy_lints/src/large_futures.rs Co-authored-by: Fridtjof Stoldt --- clippy_lints/src/large_futures.rs | 45 +++++++-------- .../ui-toml/large_futures/large_futures.fixed | 29 ---------- tests/ui-toml/large_futures/large_futures.rs | 2 - .../large_futures/large_futures.stderr | 2 +- tests/ui/large_futures.fixed | 41 -------------- tests/ui/large_futures.rs | 24 +++++++- tests/ui/large_futures.stderr | 56 ++++++++++++++++--- 7 files changed, 93 insertions(+), 106 deletions(-) delete mode 100644 tests/ui-toml/large_futures/large_futures.fixed delete mode 100644 tests/ui/large_futures.fixed diff --git a/clippy_lints/src/large_futures.rs b/clippy_lints/src/large_futures.rs index 494bb2a97d265..1b0544813718a 100644 --- a/clippy_lints/src/large_futures.rs +++ b/clippy_lints/src/large_futures.rs @@ -59,31 +59,28 @@ impl_lint_pass!(LargeFuture => [LARGE_FUTURES]); impl<'tcx> LateLintPass<'tcx> for LargeFuture { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if matches!(expr.span.ctxt().outer_expn_data().kind, rustc_span::ExpnKind::Macro(..)) { + return; + } if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind { - if let ExprKind::Call(func, [expr, ..]) = expr.kind { - if matches!( - func.kind, - ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) - ) { - let ty = cx.typeck_results().expr_ty(expr); - if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() - && implements_trait(cx, ty, future_trait_def_id, &[]) { - if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) { - let size = layout.layout.size(); - if size >= Size::from_bytes(self.future_size_threshold) { - span_lint_and_sugg( - cx, - LARGE_FUTURES, - expr.span, - &format!("large future with a size of {} bytes", size.bytes()), - "consider `Box::pin` on it", - format!("Box::pin({})", snippet(cx, expr.span, "..")), - Applicability::MachineApplicable, - ); - } - } - } - } + if let ExprKind::Call(func, [expr, ..]) = expr.kind + && let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind + && let ty = cx.typeck_results().expr_ty(expr) + && let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() + && implements_trait(cx, ty, future_trait_def_id, &[]) + && let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) + && let size = layout.layout.size() + && size >= Size::from_bytes(self.future_size_threshold) + { + span_lint_and_sugg( + cx, + LARGE_FUTURES, + expr.span, + &format!("large future with a size of {} bytes", size.bytes()), + "consider `Box::pin` on it", + format!("Box::pin({})", snippet(cx, expr.span, "..")), + Applicability::Unspecified, + ); } } } diff --git a/tests/ui-toml/large_futures/large_futures.fixed b/tests/ui-toml/large_futures/large_futures.fixed deleted file mode 100644 index 1238c512b0fa2..0000000000000 --- a/tests/ui-toml/large_futures/large_futures.fixed +++ /dev/null @@ -1,29 +0,0 @@ -// run-rustfix - -#![warn(clippy::large_futures)] - -fn main() {} - -pub async fn should_warn() { - let x = [0u8; 1024]; - async {}.await; - dbg!(x); -} - -pub async fn should_not_warn() { - let x = [0u8; 1020]; - async {}.await; - dbg!(x); -} - -pub async fn bar() { - Box::pin(should_warn()).await; - - async { - let x = [0u8; 1024]; - dbg!(x); - } - .await; - - should_not_warn().await; -} diff --git a/tests/ui-toml/large_futures/large_futures.rs b/tests/ui-toml/large_futures/large_futures.rs index 80039d9047b39..4158df8b5ff55 100644 --- a/tests/ui-toml/large_futures/large_futures.rs +++ b/tests/ui-toml/large_futures/large_futures.rs @@ -1,5 +1,3 @@ -// run-rustfix - #![warn(clippy::large_futures)] fn main() {} diff --git a/tests/ui-toml/large_futures/large_futures.stderr b/tests/ui-toml/large_futures/large_futures.stderr index f7895f8eaf7a1..b92734de2f08c 100644 --- a/tests/ui-toml/large_futures/large_futures.stderr +++ b/tests/ui-toml/large_futures/large_futures.stderr @@ -1,5 +1,5 @@ error: large future with a size of 1026 bytes - --> $DIR/large_futures.rs:20:5 + --> $DIR/large_futures.rs:18:5 | LL | should_warn().await; | ^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(should_warn())` diff --git a/tests/ui/large_futures.fixed b/tests/ui/large_futures.fixed deleted file mode 100644 index 9d839998afd7b..0000000000000 --- a/tests/ui/large_futures.fixed +++ /dev/null @@ -1,41 +0,0 @@ -// run-rustfix - -#![feature(generators)] -#![warn(clippy::large_futures)] -#![allow(clippy::future_not_send)] -#![allow(clippy::manual_async_fn)] - -async fn big_fut(_arg: [u8; 1024 * 16]) {} - -async fn wait() { - let f = async { - Box::pin(big_fut([0u8; 1024 * 16])).await; - }; - Box::pin(f).await -} -async fn calls_fut(fut: impl std::future::Future) { - loop { - Box::pin(wait()).await; - if true { - return fut.await; - } else { - Box::pin(wait()).await; - } - } -} - -pub async fn test() { - let fut = big_fut([0u8; 1024 * 16]); - Box::pin(foo()).await; - Box::pin(calls_fut(fut)).await; -} - -pub fn foo() -> impl std::future::Future { - async { - let x = [0i32; 1024 * 16]; - async {}.await; - dbg!(x); - } -} - -fn main() {} diff --git a/tests/ui/large_futures.rs b/tests/ui/large_futures.rs index 8b7aaa61b888b..4a8ba995da556 100644 --- a/tests/ui/large_futures.rs +++ b/tests/ui/large_futures.rs @@ -1,5 +1,3 @@ -// run-rustfix - #![feature(generators)] #![warn(clippy::large_futures)] #![allow(clippy::future_not_send)] @@ -38,4 +36,26 @@ pub fn foo() -> impl std::future::Future { } } +pub async fn lines() { + async { + let x = [0i32; 1024 * 16]; + async {}.await; + println!("{:?}", x); + } + .await; +} + +pub async fn macro_expn() { + macro_rules! macro_ { + () => { + async { + let x = [0i32; 1024 * 16]; + async {}.await; + println!("macro: {:?}", x); + } + }; + } + macro_!().await +} + fn main() {} diff --git a/tests/ui/large_futures.stderr b/tests/ui/large_futures.stderr index 557455299a9c0..67e0fceff6ef3 100644 --- a/tests/ui/large_futures.stderr +++ b/tests/ui/large_futures.stderr @@ -1,5 +1,5 @@ error: large future with a size of 16385 bytes - --> $DIR/large_futures.rs:12:9 + --> $DIR/large_futures.rs:10:9 | LL | big_fut([0u8; 1024 * 16]).await; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(big_fut([0u8; 1024 * 16]))` @@ -7,34 +7,76 @@ LL | big_fut([0u8; 1024 * 16]).await; = note: `-D clippy::large-futures` implied by `-D warnings` error: large future with a size of 16386 bytes - --> $DIR/large_futures.rs:14:5 + --> $DIR/large_futures.rs:12:5 | LL | f.await | ^ help: consider `Box::pin` on it: `Box::pin(f)` error: large future with a size of 16387 bytes - --> $DIR/large_futures.rs:18:9 + --> $DIR/large_futures.rs:16:9 | LL | wait().await; | ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())` error: large future with a size of 16387 bytes - --> $DIR/large_futures.rs:22:13 + --> $DIR/large_futures.rs:20:13 | LL | wait().await; | ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())` error: large future with a size of 65540 bytes - --> $DIR/large_futures.rs:29:5 + --> $DIR/large_futures.rs:27:5 | LL | foo().await; | ^^^^^ help: consider `Box::pin` on it: `Box::pin(foo())` error: large future with a size of 49159 bytes - --> $DIR/large_futures.rs:30:5 + --> $DIR/large_futures.rs:28:5 | LL | calls_fut(fut).await; | ^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(calls_fut(fut))` -error: aborting due to 6 previous errors +error: large future with a size of 65540 bytes + --> $DIR/large_futures.rs:40:5 + | +LL | / async { +LL | | let x = [0i32; 1024 * 16]; +LL | | async {}.await; +LL | | println!("{:?}", x); +LL | | } + | |_____^ + | +help: consider `Box::pin` on it + | +LL ~ Box::pin(async { +LL + let x = [0i32; 1024 * 16]; +LL + async {}.await; +LL + println!("{:?}", x); +LL + }) + | + +error: large future with a size of 65540 bytes + --> $DIR/large_futures.rs:51:13 + | +LL | / async { +LL | | let x = [0i32; 1024 * 16]; +LL | | async {}.await; +LL | | println!("macro: {:?}", x); +LL | | } + | |_____________^ +... +LL | macro_!().await + | --------- in this macro invocation + | + = note: this error originates in the macro `macro_` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider `Box::pin` on it + | +LL ~ Box::pin(async { +LL + let x = [0i32; 1024 * 16]; +LL + async {}.await; +LL + println!("macro: {:?}", x); +LL + }) + | + +error: aborting due to 8 previous errors From 51b4d2a1e825c39c444be1385f3618302ef0b452 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 26 Mar 2023 14:52:46 +0000 Subject: [PATCH 37/86] In uninit checking, add fallback for polymorphic types --- clippy_utils/src/ty.rs | 22 +++++++++++++++++++--- tests/ui/uninit.rs | 18 +++++++++++++++--- tests/ui/uninit.stderr | 10 +++++----- tests/ui/uninit_vec.rs | 8 ++++++++ 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 0b47234647fb6..9449f0b55674d 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -541,9 +541,25 @@ pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { pub fn is_uninit_value_valid_for_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { cx.tcx .check_validity_requirement((ValidityRequirement::Uninit, cx.param_env.and(ty))) - // For types containing generic parameters we cannot get a layout to check. - // Therefore, we are conservative and assume that they don't allow uninit. - .unwrap_or(false) + .unwrap_or_else(|_| is_uninit_value_valid_for_ty_fallback(cx, ty)) +} + +/// A fallback for polymorphic types, which are not supported by `check_validity_requirement`. +fn is_uninit_value_valid_for_ty_fallback<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + match *ty.kind() { + // The array length may be polymorphic, let's try the inner type. + ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component), + // Peek through tuples and try their fallbacks. + ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)), + // Unions are always fine right now. + // This includes MaybeUninit, the main way people use uninitialized memory. + // For ADTs, we could look at all fields just like for tuples, but that's potentially + // exponential, so let's avoid doing that for now. Code doing that is sketchy enough to + // just use an `#[allow()]`. + ty::Adt(adt, _) => adt.is_union(), + // For the rest, conservatively assume that they cannot be uninit. + _ => false, + } } /// Gets an iterator over all predicates which apply to the given item. diff --git a/tests/ui/uninit.rs b/tests/ui/uninit.rs index 412b36b4ee8f1..c996de89422b5 100644 --- a/tests/ui/uninit.rs +++ b/tests/ui/uninit.rs @@ -1,7 +1,7 @@ #![feature(stmt_expr_attributes)] #![allow(clippy::let_unit_value, invalid_value)] -use std::mem::{self, MaybeUninit}; +use std::mem::MaybeUninit; union MyOwnMaybeUninit { value: u8, @@ -30,12 +30,24 @@ fn main() { let _: [u8; 0] = unsafe { MaybeUninit::uninit().assume_init() }; // Was a false negative. - let _: usize = unsafe { mem::MaybeUninit::uninit().assume_init() }; + let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; polymorphic::<()>(); + polymorphic_maybe_uninit_array::<10>(); + polymorphic_maybe_uninit::(); fn polymorphic() { // We are conservative around polymorphic types. - let _: T = unsafe { mem::MaybeUninit::uninit().assume_init() }; + let _: T = unsafe { MaybeUninit::uninit().assume_init() }; + } + + fn polymorphic_maybe_uninit_array() { + // While the type is polymorphic, MaybeUninit is not. + let _: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + } + + fn polymorphic_maybe_uninit() { + // The entire type is polymorphic, but it's wrapped in a MaybeUninit. + let _: MaybeUninit = unsafe { MaybeUninit::uninit().assume_init() }; } } diff --git a/tests/ui/uninit.stderr b/tests/ui/uninit.stderr index 9e01b9a4aa816..248de56da76cb 100644 --- a/tests/ui/uninit.stderr +++ b/tests/ui/uninit.stderr @@ -9,14 +9,14 @@ LL | let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; error: this call for this type may be undefined behavior --> $DIR/uninit.rs:33:29 | -LL | let _: usize = unsafe { mem::MaybeUninit::uninit().assume_init() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call for this type may be undefined behavior - --> $DIR/uninit.rs:39:29 + --> $DIR/uninit.rs:41:29 | -LL | let _: T = unsafe { mem::MaybeUninit::uninit().assume_init() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: T = unsafe { MaybeUninit::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 3 previous errors diff --git a/tests/ui/uninit_vec.rs b/tests/ui/uninit_vec.rs index 59ec64a7ab1b9..79effc82fdf7c 100644 --- a/tests/ui/uninit_vec.rs +++ b/tests/ui/uninit_vec.rs @@ -124,4 +124,12 @@ fn main() { vec.set_len(10); } } + + fn poly_maybe_uninit() { + // We are conservative around polymorphic types. + let mut vec: Vec> = Vec::with_capacity(1000); + unsafe { + vec.set_len(10); + } + } } From 4cf5bdc60c7ccec3b5f395ee393615c224e28555 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 12 Dec 2022 00:42:45 -0500 Subject: [PATCH 38/86] Stabilize a portion of 'once_cell' Move items not part of this stabilization to 'lazy_cell' or 'once_cell_try' --- clippy_dev/src/lib.rs | 1 - clippy_lints/src/lib.rs | 1 - clippy_utils/src/lib.rs | 1 - src/driver.rs | 2 +- tests/compile-test.rs | 2 +- tests/dogfood.rs | 2 +- tests/lint_message_convention.rs | 2 +- tests/workspace.rs | 2 +- 8 files changed, 5 insertions(+), 8 deletions(-) diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index e70488165b99b..8871873c6612f 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,5 +1,4 @@ #![feature(let_chains)] -#![feature(once_cell)] #![feature(rustc_private)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] // warn on lints, that are included in `rust-lang/rust`s bootstrap diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c9210bf73f896..3da7f95c1b9fe 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -7,7 +7,6 @@ #![feature(let_chains)] #![feature(lint_reasons)] #![feature(never_type)] -#![feature(once_cell)] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] #![recursion_limit = "512"] diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index fd06c0b86775a..619aa9f4bf6fa 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3,7 +3,6 @@ #![feature(let_chains)] #![feature(lint_reasons)] #![feature(never_type)] -#![feature(once_cell)] #![feature(rustc_private)] #![recursion_limit = "512"] #![cfg_attr(feature = "deny-warnings", deny(warnings))] diff --git a/src/driver.rs b/src/driver.rs index f08393c303ef8..9e0822404b6b8 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -1,6 +1,6 @@ #![feature(rustc_private)] #![feature(let_chains)] -#![feature(once_cell)] +#![feature(lazy_cell)] #![feature(lint_reasons)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] // warn on lints, that are included in `rust-lang/rust`s bootstrap diff --git a/tests/compile-test.rs b/tests/compile-test.rs index c10ee969c0146..57890ff31737b 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -1,5 +1,5 @@ #![feature(test)] // compiletest_rs requires this attribute -#![feature(once_cell)] +#![feature(lazy_cell)] #![feature(is_sorted)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(rust_2018_idioms, unused_lifetimes)] diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 3a5d478fa3140..68a878e9a3d31 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -3,7 +3,7 @@ //! //! See [Eating your own dog food](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) for context -#![feature(once_cell)] +#![feature(lazy_cell)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(rust_2018_idioms, unused_lifetimes)] diff --git a/tests/lint_message_convention.rs b/tests/lint_message_convention.rs index abd0d1bc5934f..8feea800fdbe3 100644 --- a/tests/lint_message_convention.rs +++ b/tests/lint_message_convention.rs @@ -1,4 +1,4 @@ -#![feature(once_cell)] +#![feature(lazy_cell)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(rust_2018_idioms, unused_lifetimes)] diff --git a/tests/workspace.rs b/tests/workspace.rs index 95325e0603782..c9cbc50546cf3 100644 --- a/tests/workspace.rs +++ b/tests/workspace.rs @@ -1,4 +1,4 @@ -#![feature(once_cell)] +#![feature(lazy_cell)] use std::path::PathBuf; use std::process::Command; From a2165533e1b5797e1043b961b4d5aaa599e8a305 Mon Sep 17 00:00:00 2001 From: Elliot Bobrow Date: Wed, 29 Mar 2023 18:19:29 -0700 Subject: [PATCH 39/86] fix `single_component_path_imports` FP --- .../src/single_component_path_imports.rs | 59 +++++++++++++++---- tests/ui/single_component_path_imports.fixed | 6 ++ tests/ui/single_component_path_imports.rs | 6 ++ tests/ui/single_component_path_imports.stderr | 4 +- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index d46f6a6352c63..5743dd21c2835 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use rustc_ast::node_id::{NodeId, NodeMap}; -use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind}; +use rustc_ast::visit::{walk_expr, Visitor}; +use rustc_ast::{ptr::P, Crate, Expr, ExprKind, Item, ItemKind, MacroDef, ModKind, Ty, TyKind, UseTreeKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -55,7 +56,7 @@ impl EarlyLintPass for SingleComponentPathImports { return; } - self.check_mod(cx, &krate.items); + self.check_mod(&krate.items); } fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { @@ -84,8 +85,43 @@ impl EarlyLintPass for SingleComponentPathImports { } } +#[derive(Default)] +struct ImportUsageVisitor { + // keep track of imports reused with `self` keyword, such as `self::std` in the example below. + // Removing the `use std;` would make this a compile error (#10549) + // ``` + // use std; + // + // fn main() { + // let _ = self::std::io::stdout(); + // } + // ``` + imports_referenced_with_self: Vec, +} + +impl<'tcx> Visitor<'tcx> for ImportUsageVisitor { + fn visit_expr(&mut self, expr: &Expr) { + if let ExprKind::Path(_, path) = &expr.kind + && path.segments.len() > 1 + && path.segments[0].ident.name == kw::SelfLower + { + self.imports_referenced_with_self.push(path.segments[1].ident.name); + } + walk_expr(self, expr); + } + + fn visit_ty(&mut self, ty: &Ty) { + if let TyKind::Path(_, path) = &ty.kind + && path.segments.len() > 1 + && path.segments[0].ident.name == kw::SelfLower + { + self.imports_referenced_with_self.push(path.segments[1].ident.name); + } + } +} + impl SingleComponentPathImports { - fn check_mod(&mut self, cx: &EarlyContext<'_>, items: &[P]) { + fn check_mod(&mut self, items: &[P]) { // keep track of imports reused with `self` keyword, such as `self::crypto_hash` in the example // below. Removing the `use crypto_hash;` would make this a compile error // ``` @@ -108,18 +144,16 @@ impl SingleComponentPathImports { // ``` let mut macros = Vec::new(); + let mut import_usage_visitor = ImportUsageVisitor::default(); for item in items { - self.track_uses( - cx, - item, - &mut imports_reused_with_self, - &mut single_use_usages, - &mut macros, - ); + self.track_uses(item, &mut imports_reused_with_self, &mut single_use_usages, &mut macros); + import_usage_visitor.visit_item(item); } for usage in single_use_usages { - if !imports_reused_with_self.contains(&usage.name) { + if !imports_reused_with_self.contains(&usage.name) + && !import_usage_visitor.imports_referenced_with_self.contains(&usage.name) + { self.found.entry(usage.item_id).or_default().push(usage); } } @@ -127,7 +161,6 @@ impl SingleComponentPathImports { fn track_uses( &mut self, - cx: &EarlyContext<'_>, item: &Item, imports_reused_with_self: &mut Vec, single_use_usages: &mut Vec, @@ -139,7 +172,7 @@ impl SingleComponentPathImports { match &item.kind { ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => { - self.check_mod(cx, items); + self.check_mod(items); }, ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => { macros.push(item.ident.name); diff --git a/tests/ui/single_component_path_imports.fixed b/tests/ui/single_component_path_imports.fixed index 4c40739d6f553..8c96c4715d308 100644 --- a/tests/ui/single_component_path_imports.fixed +++ b/tests/ui/single_component_path_imports.fixed @@ -2,9 +2,11 @@ #![warn(clippy::single_component_path_imports)] #![allow(unused_imports)] +use core; use serde as edres; pub use serde; +use std; macro_rules! m { () => { @@ -17,6 +19,10 @@ fn main() { // False positive #5154, shouldn't trigger lint. m!(); + + // False positive #10549 + let _ = self::std::io::stdout(); + let _ = 0 as self::core::ffi::c_uint; } mod hello_mod { diff --git a/tests/ui/single_component_path_imports.rs b/tests/ui/single_component_path_imports.rs index 9280bab3c71b5..8434bf7eaf1f5 100644 --- a/tests/ui/single_component_path_imports.rs +++ b/tests/ui/single_component_path_imports.rs @@ -2,9 +2,11 @@ #![warn(clippy::single_component_path_imports)] #![allow(unused_imports)] +use core; use regex; use serde as edres; pub use serde; +use std; macro_rules! m { () => { @@ -17,6 +19,10 @@ fn main() { // False positive #5154, shouldn't trigger lint. m!(); + + // False positive #10549 + let _ = self::std::io::stdout(); + let _ = 0 as self::core::ffi::c_uint; } mod hello_mod { diff --git a/tests/ui/single_component_path_imports.stderr b/tests/ui/single_component_path_imports.stderr index 71dcc25d6e5b8..d69a86470a5e5 100644 --- a/tests/ui/single_component_path_imports.stderr +++ b/tests/ui/single_component_path_imports.stderr @@ -1,5 +1,5 @@ error: this import is redundant - --> $DIR/single_component_path_imports.rs:5:1 + --> $DIR/single_component_path_imports.rs:6:1 | LL | use regex; | ^^^^^^^^^^ help: remove it entirely @@ -7,7 +7,7 @@ LL | use regex; = note: `-D clippy::single-component-path-imports` implied by `-D warnings` error: this import is redundant - --> $DIR/single_component_path_imports.rs:23:5 + --> $DIR/single_component_path_imports.rs:29:5 | LL | use regex; | ^^^^^^^^^^ help: remove it entirely From 6e87ae0f1ae5247c83470c6c039c0946063bfdda Mon Sep 17 00:00:00 2001 From: xFrednet Date: Thu, 30 Mar 2023 10:49:29 +0200 Subject: [PATCH 40/86] Run metadata collection --- book/src/lint_configuration.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 33f2b5c1de99c..2aebd632507cb 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -54,6 +54,7 @@ Please use that command to update the file and do not edit it by hand. | [allow-mixed-uninlined-format-args](#allow-mixed-uninlined-format-args) | `true` | | [suppress-restriction-lint-in-const](#suppress-restriction-lint-in-const) | `false` | | [missing-docs-in-crate-items](#missing-docs-in-crate-items) | `false` | +| [future-size-threshold](#future-size-threshold) | `16384` | ### arithmetic-side-effects-allowed Suppress checking of the passed type names in all types of operations. @@ -550,4 +551,12 @@ crate. For example, `pub(crate)` items. * [missing_docs_in_private_items](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items) +### future-size-threshold +The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint + +**Default Value:** `16384` (`u64`) + +* [large_futures](https://rust-lang.github.io/rust-clippy/master/index.html#large_futures) + + From 17f80456d18c954cd82a1232807ae8c5bc1cb5cb Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Thu, 30 Mar 2023 16:17:11 +0000 Subject: [PATCH 41/86] Ignore `file!()` macro in `print_literal`, `write_literal` --- clippy_lints/src/write.rs | 14 ++++++++++---- tests/ui/auxiliary/proc_macros.rs | 2 +- tests/ui/print_literal.rs | 4 ++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 8114a8463faad..d7c94b909bdc5 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -463,12 +463,18 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { && let Some(value_string) = snippet_opt(cx, arg.expr.span) { let (replacement, replace_raw) = match lit.kind { - LitKind::Str | LitKind::StrRaw(_) => extract_str_literal(&value_string), + LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) { + Some(extracted) => extracted, + None => return, + }, LitKind::Char => ( match lit.symbol.as_str() { "\"" => "\\\"", "\\'" => "'", - _ => &value_string[1..value_string.len() - 1], + _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { + Some(stripped) => stripped, + None => return, + }, } .to_string(), false, @@ -533,13 +539,13 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { /// `r#"a"#` -> (`a`, true) /// /// `"b"` -> (`b`, false) -fn extract_str_literal(literal: &str) -> (String, bool) { +fn extract_str_literal(literal: &str) -> Option<(String, bool)> { let (literal, raw) = match literal.strip_prefix('r') { Some(stripped) => (stripped.trim_matches('#'), true), None => (literal, false), }; - (literal[1..literal.len() - 1].to_string(), raw) + Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw)) } enum UnescapeErr { diff --git a/tests/ui/auxiliary/proc_macros.rs b/tests/ui/auxiliary/proc_macros.rs index 325be83a0d73d..3d5beab1eff1c 100644 --- a/tests/ui/auxiliary/proc_macros.rs +++ b/tests/ui/auxiliary/proc_macros.rs @@ -63,7 +63,7 @@ fn group_with_span(delimiter: Delimiter, stream: TokenStream, span: Span) -> Gro /// Token used to escape the following token from the macro's span rules. const ESCAPE_CHAR: char = '$'; -/// Takes a single token followed by a sequence tokens. Returns the sequence of tokens with their +/// Takes a single token followed by a sequence of tokens. Returns the sequence of tokens with their /// span set to that of the first token. Tokens may be escaped with either `#ident` or `#(tokens)`. #[proc_macro] pub fn with_span(input: TokenStream) -> TokenStream { diff --git a/tests/ui/print_literal.rs b/tests/ui/print_literal.rs index 86f908f66b8f3..538513e9156f8 100644 --- a/tests/ui/print_literal.rs +++ b/tests/ui/print_literal.rs @@ -38,4 +38,8 @@ fn main() { // named args shouldn't change anything either println!("{foo} {bar}", foo = "hello", bar = "world"); println!("{bar} {foo}", foo = "hello", bar = "world"); + + // The string literal from `file!()` has a callsite span that isn't marked as coming from an + // expansion + println!("file: {}", file!()); } From d29dec1a3f15914871cd0451dab899501156688d Mon Sep 17 00:00:00 2001 From: Jynn Nelson Date: Thu, 30 Mar 2023 13:23:48 -0400 Subject: [PATCH 42/86] Update subtree sync docs for changes in rustc-dev-guide --- book/src/development/infrastructure/sync.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/book/src/development/infrastructure/sync.md b/book/src/development/infrastructure/sync.md index 02cfc11b55acb..45a72c2921464 100644 --- a/book/src/development/infrastructure/sync.md +++ b/book/src/development/infrastructure/sync.md @@ -19,8 +19,7 @@ to beta. For reference, the first sync following this cadence was performed the 2020-08-27. This process is described in detail in the following sections. For general -information about `subtree`s in the Rust repository see [Rust's -`CONTRIBUTING.md`][subtree]. +information about `subtree`s in the Rust repository see [the rustc-dev-guide][subtree]. ## Patching git-subtree to work with big repos @@ -118,5 +117,5 @@ All of the following commands have to be run inside the `rust` directory. 3. Open a PR to [`rust-lang/rust`] [gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493 -[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree +[subtree]: https://rustc-dev-guide.rust-lang.org/external-repos.html#external-dependencies-subtree [`rust-lang/rust`]: https://github.com/rust-lang/rust From 41b367fa5f0127ed7d245f517aaed9b8f4855974 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Sat, 11 Mar 2023 18:50:50 -0500 Subject: [PATCH 43/86] Gramar, and spelin kleanup A few minor cleanups in various markdown files, mostly focusing on spelling and ignoring non-compilable codeblocks. --- README.md | 6 ++-- book/src/README.md | 2 +- book/src/configuration.md | 8 +++--- book/src/development/README.md | 2 +- book/src/development/adding_lints.md | 24 ++++++++-------- book/src/development/basics.md | 2 +- .../development/common_tools_writing_lints.md | 14 +++++----- book/src/development/infrastructure/book.md | 4 +-- .../infrastructure/changelog_update.md | 2 +- .../src/development/infrastructure/release.md | 2 +- book/src/development/infrastructure/sync.md | 8 +++--- book/src/development/proposals/README.md | 2 +- .../src/development/proposals/roadmap-2021.md | 6 ++-- .../proposals/syntax-tree-patterns.md | 28 +++++++++---------- book/src/installation.md | 4 +-- book/src/lint_configuration.md | 8 +++--- book/src/lints.md | 10 +++---- book/src/usage.md | 4 +-- clippy_lints/src/utils/conf.rs | 8 +++--- etc/relicense/RELICENSE_DOCUMENTATION.md | 4 +-- lintcheck/README.md | 2 +- 21 files changed, 75 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index b69ed8900a491..85798e0e80c15 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Lints are divided into categories, each with a default [lint level](https://doc. You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. | Category | Description | Default level | -| --------------------- | ----------------------------------------------------------------------------------- | ------------- | +|-----------------------|-------------------------------------------------------------------------------------|---------------| | `clippy::all` | all lints that are on by default (correctness, suspicious, style, complexity, perf) | **warn/deny** | | `clippy::correctness` | code that is outright wrong or useless | **deny** | | `clippy::suspicious` | code that is most likely wrong or useless | **warn** | @@ -130,7 +130,7 @@ for example. You can add Clippy to Travis CI in the same way you use it locally: -```yml +```yaml language: rust rust: - stable @@ -253,7 +253,7 @@ rust-version = "1.30" The MSRV can also be specified as an attribute, like below. -```rust +```rust,ignore #![feature(custom_inner_attributes)] #![clippy::msrv = "1.30.0"] diff --git a/book/src/README.md b/book/src/README.md index df4a1f2702e47..3b6270962680e 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -14,7 +14,7 @@ much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. | Category | Description | Default level | -| --------------------- | ----------------------------------------------------------------------------------- | ------------- | +|-----------------------|-------------------------------------------------------------------------------------|---------------| | `clippy::all` | all lints that are on by default (correctness, suspicious, style, complexity, perf) | **warn/deny** | | `clippy::correctness` | code that is outright wrong or useless | **deny** | | `clippy::suspicious` | code that is most likely wrong or useless | **warn** | diff --git a/book/src/configuration.md b/book/src/configuration.md index 87f4a697af9fd..4947d3d122624 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -3,7 +3,7 @@ > **Note:** The configuration file is unstable and may be deprecated in the future. Some lints can be configured in a TOML file named `clippy.toml` or `.clippy.toml`. It contains a -basic `variable = value` mapping eg. +basic `variable = value` mapping e.g. ```toml avoid-breaking-exported-api = false @@ -60,7 +60,7 @@ And to warn on `lint_name`, run cargo clippy -- -W clippy::lint_name ``` -This also works with lint groups. For example you can run Clippy with warnings for all lints enabled: +This also works with lint groups. For example, you can run Clippy with warnings for all lints enabled: ```terminal cargo clippy -- -W clippy::pedantic @@ -84,7 +84,7 @@ msrv = "1.30.0" The MSRV can also be specified as an attribute, like below. -```rust +```rust,ignore #![feature(custom_inner_attributes)] #![clippy::msrv = "1.30.0"] @@ -96,7 +96,7 @@ fn main() { You can also omit the patch version when specifying the MSRV, so `msrv = 1.30` is equivalent to `msrv = 1.30.0`. -Note: `custom_inner_attributes` is an unstable feature so it has to be enabled explicitly. +Note: `custom_inner_attributes` is an unstable feature, so it has to be enabled explicitly. Lints that recognize this configuration option can be found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv) diff --git a/book/src/development/README.md b/book/src/development/README.md index 5cf7201cffad7..616e6d182b729 100644 --- a/book/src/development/README.md +++ b/book/src/development/README.md @@ -5,7 +5,7 @@ making Clippy better by contributing to it. In that case, welcome to the project! > _Note:_ If you're just interested in using Clippy, there's nothing to see from -> this point onward and you should return to one of the earlier chapters. +> this point onward, and you should return to one of the earlier chapters. ## Getting started diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index 0f5ee7103de1a..3f4088e74eaf7 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -24,7 +24,7 @@ because that's clearly a non-descriptive name. - [Lint passes](#lint-passes) - [Emitting a lint](#emitting-a-lint) - [Adding the lint logic](#adding-the-lint-logic) - - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version-msrv) + - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version--msrv-) - [Author lint](#author-lint) - [Print HIR lint](#print-hir-lint) - [Documentation](#documentation) @@ -275,7 +275,7 @@ When declaring a new lint by hand and `cargo dev update_lints` is used, the lint pass may have to be registered manually in the `register_plugins` function in `clippy_lints/src/lib.rs`: -```rust +```rust,ignore store.register_early_pass(|| Box::new(foo_functions::FooFunctions)); ``` @@ -301,7 +301,7 @@ either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass]. In short, the `LateLintPass` has access to type information while the `EarlyLintPass` doesn't. If you don't need access to type information, use the -`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed +`EarlyLintPass`. The `EarlyLintPass` is also faster. However, linting speed hasn't really been a concern with Clippy so far. Since we don't need type information for checking the function name, we used @@ -318,7 +318,7 @@ implementation of the lint logic. Let's start by implementing the `EarlyLintPass` for our `FooFunctions`: -```rust +```rust,ignore impl EarlyLintPass for FooFunctions { fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { // TODO: Emit lint here @@ -337,10 +337,10 @@ variety of lint emission functions. They can all be found in [`clippy_utils/src/diagnostics.rs`][diagnostics]. `span_lint_and_help` seems most appropriate in this case. It allows us to -provide an extra help message and we can't really suggest a better name +provide an extra help message, and we can't really suggest a better name automatically. This is how it looks: -```rust +```rust,ignore impl EarlyLintPass for FooFunctions { fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { span_lint_and_help( @@ -479,7 +479,7 @@ the value from `clippy.toml`. This can be accounted for using the `extract_msrv_attr!(LintContext)` macro and passing `LateContext`/`EarlyContext`. -```rust +```rust,ignore impl<'tcx> LateLintPass<'tcx> for ManualStrip { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { ... @@ -493,7 +493,7 @@ the lint's test file, `tests/ui/manual_strip.rs` in this example. It should have a case for the version below the MSRV and one with the same contents but for the MSRV version itself. -```rust +```rust,ignore ... #[clippy::msrv = "1.44"] @@ -524,7 +524,7 @@ define_Conf! { If you have trouble implementing your lint, there is also the internal `author` lint to generate Clippy code that detects the offending pattern. It does not -work for all of the Rust syntax, but can give a good starting point. +work for all the Rust syntax, but can give a good starting point. The quickest way to use it, is the [Rust playground: play.rust-lang.org][author_example]. Put the code you want to lint into the @@ -617,7 +617,7 @@ output in the `stdout` part. ## PR Checklist -Before submitting your PR make sure you followed all of the basic requirements: +Before submitting your PR make sure you followed all the basic requirements: @@ -637,7 +637,7 @@ for some users. Adding a configuration is done in the following steps: 1. Adding a new configuration entry to [`clippy_lints::utils::conf`] like this: - ```rust + ```rust,ignore /// Lint: LINT_NAME. /// /// @@ -690,7 +690,7 @@ for some users. Adding a configuration is done in the following steps: configuration value is now cloned or copied into a local value that is then passed to the impl struct like this: - ```rust + ```rust,ignore // Default generated registration: store.register_*_pass(|| box module::StructName); diff --git a/book/src/development/basics.md b/book/src/development/basics.md index 6fb53236e6f1a..b07d3de7187e5 100644 --- a/book/src/development/basics.md +++ b/book/src/development/basics.md @@ -125,7 +125,7 @@ We follow a rustc no merge-commit policy. See ## Common Abbreviations | Abbreviation | Meaning | -| ------------ | -------------------------------------- | +|--------------|----------------------------------------| | UB | Undefined Behavior | | FP | False Positive | | FN | False Negative | diff --git a/book/src/development/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md index f5aa06e4bf653..09171d86a2097 100644 --- a/book/src/development/common_tools_writing_lints.md +++ b/book/src/development/common_tools_writing_lints.md @@ -3,7 +3,7 @@ You may need following tooltips to catch up with common operations. - [Common tools for writing lints](#common-tools-for-writing-lints) - - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression) + - [Retrieving the type of expression](#retrieving-the-type-of-expression) - [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method) - [Checking for a specific type](#checking-for-a-specific-type) - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait) @@ -16,7 +16,7 @@ Useful Rustc dev guide links: - [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) - [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) -## Retrieving the type of an expression +## Retrieving the type of expression Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions: @@ -45,7 +45,7 @@ impl LateLintPass<'_> for MyStructLint { } ``` -Similarly in [`TypeckResults`][TypeckResults] methods, you have the +Similarly, in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method to retrieve a type from a pattern. Two noticeable items here: @@ -192,7 +192,7 @@ functions to deal with macros: - `span.from_expansion()`: detects if a span is from macro expansion or desugaring. Checking this is a common first step in a lint. - ```rust + ```rust,ignore if expr.span.from_expansion() { // just forget it return; @@ -203,11 +203,11 @@ functions to deal with macros: if so, which macro call expanded it. It is sometimes useful to check if the context of two spans are equal. - ```rust + ```rust,ignore // expands to `1 + 0`, but don't lint 1 + mac!() ``` - ```rust + ```rust,ignore if left.span.ctxt() != right.span.ctxt() { // the coder most likely cannot modify this expression return; @@ -246,7 +246,7 @@ functions to deal with macros: `macro_rules!` with `a == $b`, `$b` is expanded to some expression with a different context from `a`. - ```rust + ```rust,ignore macro_rules! m { ($a:expr, $b:expr) => { if $a.is_some() { diff --git a/book/src/development/infrastructure/book.md b/book/src/development/infrastructure/book.md index dbd624ecd7382..de5de4bebaa91 100644 --- a/book/src/development/infrastructure/book.md +++ b/book/src/development/infrastructure/book.md @@ -13,7 +13,7 @@ guide to Clippy that you're reading right now. The Clippy book is formatted with While not strictly necessary since the book source is simply Markdown text files, having mdBook locally will allow you to build, test and serve the book locally to view changes before you commit them to the repository. You likely -already have `cargo` installed, so the easiest option is to simply: +already have `cargo` installed, so the easiest option is to: ```shell cargo install mdbook @@ -26,7 +26,7 @@ instructions for other options. The book's [src](https://github.com/rust-lang/rust-clippy/tree/master/book/src) -directory contains all of the markdown files used to generate the book. If you +directory contains all the markdown files used to generate the book. If you want to see your changes in real time, you can use the mdBook `serve` command to run a web server locally that will automatically update changes as they are made. From the top level of your `rust-clippy` directory: diff --git a/book/src/development/infrastructure/changelog_update.md b/book/src/development/infrastructure/changelog_update.md index d1ac7237b5e35..df9b1bbe18f32 100644 --- a/book/src/development/infrastructure/changelog_update.md +++ b/book/src/development/infrastructure/changelog_update.md @@ -101,7 +101,7 @@ Look for the [`beta-accepted`] label and make sure to also include the PRs with that label in the changelog. If you can, remove the `beta-accepted` labels **after** the changelog PR was merged. -> _Note:_ Some of those PRs might even got backported to the previous `beta`. +> _Note:_ Some of those PRs might even get backported to the previous `beta`. > Those have to be included in the changelog of the _previous_ release. ### 4. Update `clippy::version` attributes diff --git a/book/src/development/infrastructure/release.md b/book/src/development/infrastructure/release.md index 0572281803e71..98fabf8e89aee 100644 --- a/book/src/development/infrastructure/release.md +++ b/book/src/development/infrastructure/release.md @@ -44,7 +44,7 @@ $ git push origin backport_remerge # This can be pushed to your fork ``` After this, open a PR to the master branch. In this PR, the commit hash of the -`HEAD` of the `beta` branch must exists. In addition to that, no files should be +`HEAD` of the `beta` branch must exist. In addition to that, no files should be changed by this PR. ## Update the `beta` branch diff --git a/book/src/development/infrastructure/sync.md b/book/src/development/infrastructure/sync.md index 02cfc11b55acb..7fb127c6284f9 100644 --- a/book/src/development/infrastructure/sync.md +++ b/book/src/development/infrastructure/sync.md @@ -47,7 +47,7 @@ sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subt > _Note:_ If you are a Debian user, `dash` is the shell used by default for > scripts instead of `sh`. This shell has a hardcoded recursion limit set to -> 1000. In order to make this process work, you need to force the script to run +> 1,000. In order to make this process work, you need to force the script to run > `bash` instead. You can do this by editing the first line of the `git-subtree` > script and changing `sh` to `bash`. @@ -71,10 +71,10 @@ $ git remote add clippy-local /path/to/rust-clippy ## Performing the sync from [`rust-lang/rust`] to Clippy -Here is a TL;DR version of the sync process (all of the following commands have +Here is a TL;DR version of the sync process (all the following commands have to be run inside the `rust` directory): -1. Clone the [`rust-lang/rust`] repository or make sure it is up to date. +1. Clone the [`rust-lang/rust`] repository or make sure it is up-to-date. 2. Checkout the commit from the latest available nightly. You can get it using `rustup check`. 3. Sync the changes to the rust-copy of Clippy to your Clippy fork: @@ -107,7 +107,7 @@ to be run inside the `rust` directory): ## Performing the sync from Clippy to [`rust-lang/rust`] -All of the following commands have to be run inside the `rust` directory. +All the following commands have to be run inside the `rust` directory. 1. Make sure you have checked out the latest `master` of `rust-lang/rust`. 2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy: diff --git a/book/src/development/proposals/README.md b/book/src/development/proposals/README.md index 78fe34ebf8faf..059c22ce1ce8c 100644 --- a/book/src/development/proposals/README.md +++ b/book/src/development/proposals/README.md @@ -6,6 +6,6 @@ or around Clippy in the long run. Besides adding more and more lints and improve the lints that Clippy already has, Clippy is also interested in making the experience of its users, developers and maintainers better over time. Projects that address bigger picture things -like this usually take more time and it is useful to have a proposal for those +like this usually take more time, and it is useful to have a proposal for those first. This is the place where such proposals are collected, so that we can refer to them when working on them. diff --git a/book/src/development/proposals/roadmap-2021.md b/book/src/development/proposals/roadmap-2021.md index fe8b080f56f2b..4406616bbb613 100644 --- a/book/src/development/proposals/roadmap-2021.md +++ b/book/src/development/proposals/roadmap-2021.md @@ -52,8 +52,8 @@ In the following, plans to improve the usability are covered. #### No Output After `cargo check` -Currently when `cargo clippy` is run after `cargo check`, it does not produce -any output. This is especially problematic since `rust-analyzer` is on the rise +Currently, when `cargo clippy` is run after `cargo check`, it does not produce +any output. This is especially problematic since `rust-analyzer` is on the rise, and it uses `cargo check` for checking code. A fix is already implemented, but it still has to be pushed over the finish line. This also includes the stabilization of the `cargo clippy --fix` command or the support of multi-span @@ -221,7 +221,7 @@ regarding the user facing issues. Rust's roadmap process was established by [RFC 1728] in 2016. Since then every year a roadmap was published, that defined the bigger plans for the coming -years. This years roadmap can be found [here][Rust Roadmap 2021]. +years. This year roadmap can be found [here][Rust Roadmap 2021]. [RFC 1728]: https://rust-lang.github.io/rfcs/1728-north-star.html diff --git a/book/src/development/proposals/syntax-tree-patterns.md b/book/src/development/proposals/syntax-tree-patterns.md index ea4978011b19c..36d722609f4ac 100644 --- a/book/src/development/proposals/syntax-tree-patterns.md +++ b/book/src/development/proposals/syntax-tree-patterns.md @@ -16,7 +16,7 @@ lints. For non-trivial lints, it often requires nested pattern matching of AST / HIR nodes. For example, testing that an expression is a boolean literal requires the following checks: -```rust +```rust,ignore if let ast::ExprKind::Lit(lit) = &expr.node { if let ast::LitKind::Bool(_) = &lit.node { ... @@ -28,7 +28,7 @@ Writing this kind of matching code quickly becomes a complex task and the resulting code is often hard to comprehend. The code below shows a simplified version of the pattern matching required by the `collapsible_if` lint: -```rust +```rust,ignore // simplified version of the collapsible_if lint if let ast::ExprKind::If(check, then, None) = &expr.node { if then.stmts.len() == 1 { @@ -111,7 +111,7 @@ expressions that are boolean literals with value `false`. The pattern can then be used to implement lints in the following way: -```rust +```rust,ignore ... impl EarlyLintPass for MyAwesomeLint { @@ -346,7 +346,7 @@ pattern!{ one could get references to the nodes that matched the subpatterns in the following way: -```rust +```rust,ignore ... fn check_expr(expr: &syntax::ast::Expr) { if let Some(result) = my_pattern(expr) { @@ -372,7 +372,7 @@ matches arrays that consist of any number of literal expressions. Because those expressions are named `foo`, the result struct contains a `foo` attribute which is a vector of expressions: -```rust +```rust,ignore ... if let Some(result) = my_pattern_seq(expr) { result.foo // type: Vec<&syntax::ast::Expr> @@ -394,7 +394,7 @@ In the pattern above, the `bar` name is only defined if the pattern matches a boolean literal. If it matches an integer literal, the name isn't set. To account for this, the result struct's `bar` attribute is an option type: -```rust +```rust,ignore ... if let Some(result) = my_pattern_alt(expr) { result.bar // type: Option<&bool> @@ -404,7 +404,7 @@ if let Some(result) = my_pattern_alt(expr) { It's also possible to use a name in multiple alternation branches if they have compatible types: -```rust +```rust,ignore pattern!{ // matches if expression is a boolean or integer literal my_pattern_mult: Expr = @@ -519,7 +519,7 @@ The `Alt`, `Seq` and `Opt` structs look like these: > Note: The current implementation can be found > [here](https://github.com/fkohlgrueber/pattern-matching/blob/dfb3bc9fbab69cec7c91e72564a63ebaa2ede638/pattern-match/src/matchers.rs#L35-L60). -```rust +```rust,ignore pub enum Alt { Any, Elmt(Box), @@ -580,7 +580,7 @@ implementations is the `IsMatch` trait. It defines how to match *PatternTree* nodes against specific syntax tree nodes. A simplified implementation of the `IsMatch` trait is shown below: -```rust +```rust,ignore pub trait IsMatch { fn is_match(&self, other: &'o O) -> bool; } @@ -619,7 +619,7 @@ approach (matching against the coarse pattern first and checking for additional properties later) might be slower than the current practice of checking for structure and additional properties in one pass. For example, the following lint -```rust +```rust,ignore pattern!{ pat_if_without_else: Expr = If( @@ -644,7 +644,7 @@ first matches against the pattern and then checks that the `then` block doesn't start with a comment. Using clippy's current approach, it's possible to check for these conditions earlier: -```rust +```rust,ignore fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { if_chain! { if let ast::ExprKind::If(ref check, ref then, None) = expr.node; @@ -708,7 +708,7 @@ is similar to actual Rust syntax (probably like the `quote!` macro). For example, a pattern that matches `if` expressions that have `false` in their condition could look like this: -```rust +```rust,ignore if false { #[*] } @@ -742,7 +742,7 @@ affects the structure of the resulting AST. `1 + 0 + 0` is parsed as `(1 + 0) + Another example of a problem would be named submatches. Take a look at this pattern: -```rust +```rust,ignore fn test() { 1 #foo } @@ -862,7 +862,7 @@ op b` and recommends changing it to `a op= b` requires that both occurrences of `a` are the same. Using `=#...` as syntax for backreferences, the lint could be implemented like this: -```rust +```rust,ignore pattern!{ assign_op_pattern: Expr = Assign(_#target, Binary(_, =#target, _) diff --git a/book/src/installation.md b/book/src/installation.md index cce888b17d4d3..d54fff9deba18 100644 --- a/book/src/installation.md +++ b/book/src/installation.md @@ -17,8 +17,8 @@ $ rustup component add clippy [--toolchain=] ## From Source -Take a look at the [Basics] chapter in the Clippy developer guide to find step -by step instructions on how to build and install Clippy from source. +Take a look at the [Basics] chapter in the Clippy developer guide to find step-by-step +instructions on how to build and install Clippy from source. [Basics]: development/basics.md#install-from-source [Usage]: usage.md diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 4415a05251e9c..78e1a55cff32e 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -195,7 +195,7 @@ The maximum cognitive complexity a function can have ### disallowed-names The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value `".."` can be used as part of the list to indicate, that the configured values should be appended to the -default configuration of Clippy. By default any configuration will replace the default value. +default configuration of Clippy. By default, any configuration will replace the default value. **Default Value:** `["foo", "baz", "quux"]` (`Vec`) @@ -205,7 +205,7 @@ default configuration of Clippy. By default any configuration will replace the d ### doc-valid-idents The list of words this lint should not consider as identifiers needing ticks. The value `".."` can be used as part of the list to indicate, that the configured values should be appended to the -default configuration of Clippy. By default any configuraction will replace the default value. For example: +default configuration of Clippy. By default, any configuration will replace the default value. For example: * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. @@ -415,7 +415,7 @@ For internal testing only, ignores the current `publish` settings in the Cargo m Enforce the named macros always use the braces specified. A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro -is could be used with a full path two `MacroMatcher`s have to be added one with the full path +could be used with a full path two `MacroMatcher`s have to be added one with the full path `crate_name::macro_name` and one with just the macro name. **Default Value:** `[]` (`Vec`) @@ -449,7 +449,7 @@ Whether to apply the raw pointer heuristic to determine if a type is `Send`. ### max-suggested-slice-pattern-length When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in -the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed. +the slice pattern that is suggested. If more elements are necessary, the lint is suppressed. For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. **Default Value:** `3` (`u64`) diff --git a/book/src/lints.md b/book/src/lints.md index 35e30960b56c6..442dc63914e9d 100644 --- a/book/src/lints.md +++ b/book/src/lints.md @@ -17,7 +17,7 @@ The different lint groups were defined in the [Clippy 1.0 RFC]. The `clippy::correctness` group is the only lint group in Clippy which lints are deny-by-default and abort the compilation when triggered. This is for good reason: If you see a `correctness` lint, it means that your code is outright -wrong or useless and you should try to fix it. +wrong or useless, and you should try to fix it. Lints in this category are carefully picked and should be free of false positives. So just `#[allow]`ing those lints is not recommended. @@ -41,7 +41,7 @@ simplify your code. It mostly focuses on code that can be written in a shorter and more readable way, while preserving the semantics. If you should see a complexity lint, it usually means that you can remove or -replace some code and it is recommended to do so. However, if you need the more +replace some code, and it is recommended to do so. However, if you need the more complex code for some expressiveness reason, it is recommended to allow complexity lints on a case-by-case basis. @@ -50,9 +50,9 @@ complexity lints on a case-by-case basis. The `clippy::perf` group gives you suggestions on how you can increase the performance of your code. Those lints are mostly about code that the compiler can't trivially optimize, but has to be written in a slightly different way to -make the optimizer's job easier. +make the optimizer job easier. -Perf lints are usually easy to apply and it is recommended to do so. +Perf lints are usually easy to apply, and it is recommended to do so. ## Style @@ -91,7 +91,7 @@ and your use case. Lints from this group will restrict you in some way. If you enable a restriction lint for your crate it is recommended to also fix code that this lint triggers -on. However, those lints are really strict by design and you might want to +on. However, those lints are really strict by design, and you might want to `#[allow]` them in some special cases, with a comment justifying that. ## Cargo diff --git a/book/src/usage.md b/book/src/usage.md index 61a90445d753c..32084a9199b73 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -19,7 +19,7 @@ cargo clippy ### Lint configuration The above command will run the default set of lints, which are included in the -lint group `clippy::all`. You might want to use even more lints or you might not +lint group `clippy::all`. You might want to use even more lints, or you may not agree with every Clippy lint, and for that there are ways to configure lint levels. @@ -98,7 +98,7 @@ other of Clippy's lint groups. You can configure lint levels in source code the same way you can configure `rustc` lints: -```rust +```rust,ignore #![allow(clippy::style)] #[warn(clippy::double_neg)] diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index e1c9dd1011772..f75a5cf7196a2 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -275,13 +275,13 @@ define_Conf! { /// /// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the - /// default configuration of Clippy. By default any configuration will replace the default value. + /// default configuration of Clippy. By default, any configuration will replace the default value. (disallowed_names: Vec = super::DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect()), /// Lint: DOC_MARKDOWN. /// /// The list of words this lint should not consider as identifiers needing ticks. The value /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the - /// default configuration of Clippy. By default any configuraction will replace the default value. For example: + /// default configuration of Clippy. By default, any configuration will replace the default value. For example: /// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. /// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. /// @@ -390,7 +390,7 @@ define_Conf! { /// Enforce the named macros always use the braces specified. /// /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro - /// is could be used with a full path two `MacroMatcher`s have to be added one with the full path + /// could be used with a full path two `MacroMatcher`s have to be added one with the full path /// `crate_name::macro_name` and one with just the macro name. (standard_macro_braces: Vec = Vec::new()), /// Lint: MISSING_ENFORCED_IMPORT_RENAMES. @@ -408,7 +408,7 @@ define_Conf! { /// Lint: INDEX_REFUTABLE_SLICE. /// /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in - /// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed. + /// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed. /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. (max_suggested_slice_pattern_length: u64 = 3), /// Lint: AWAIT_HOLDING_INVALID_TYPE. diff --git a/etc/relicense/RELICENSE_DOCUMENTATION.md b/etc/relicense/RELICENSE_DOCUMENTATION.md index fcd7abbf3f169..ffb99cde4f869 100644 --- a/etc/relicense/RELICENSE_DOCUMENTATION.md +++ b/etc/relicense/RELICENSE_DOCUMENTATION.md @@ -35,7 +35,7 @@ relicensing are archived on GitHub. We also have saved Wayback Machine copies of The usernames of commenters on these issues can be found in relicense_comments.txt -There are a couple people in relicense_comments.txt who are not found in contributors.txt: +There are a few people in relicense_comments.txt who are not found in contributors.txt: - @EpocSquadron has [made minor text contributions to the README](https://github.com/rust-lang/rust-clippy/commits?author=EpocSquadron) which have since been overwritten, and @@ -55,7 +55,7 @@ There are a couple people in relicense_comments.txt who are not found in contrib we rewrote (see below) -Two of these contributors had nonminor contributions (#2184, #427) requiring a rewrite, carried out in #3251 +Two of these contributors had non-minor contributions (#2184, #427) requiring a rewrite, carried out in #3251 ([archive](http://web.archive.org/web/20181005192411/https://github.com/rust-lang-nursery/rust-clippy/pull/3251), [screenshot](https://user-images.githubusercontent.com/1617736/46573515-5cb69580-c94b-11e8-86e5-b456452121b2.png)) diff --git a/lintcheck/README.md b/lintcheck/README.md index e997eb47e321f..faf3ce9093a21 100644 --- a/lintcheck/README.md +++ b/lintcheck/README.md @@ -16,7 +16,7 @@ or cargo lintcheck ``` -By default the logs will be saved into +By default, the logs will be saved into `lintcheck-logs/lintcheck_crates_logs.txt`. You can set a custom sources.toml by adding `--crates-toml custom.toml` or using From 6601d85c22a0e5b632e3332991c45058ed420ba2 Mon Sep 17 00:00:00 2001 From: "Samuel \"Sam\" Tardieu" Date: Wed, 22 Mar 2023 22:56:17 +0100 Subject: [PATCH 44/86] Flag `bufreader.lines().filter_map(Result::ok)` as suspicious --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/lines_filter_map_ok.rs | 100 ++++++++++++++++++++++++ clippy_utils/src/paths.rs | 2 + tests/ui/lines_filter_map_ok.fixed | 29 +++++++ tests/ui/lines_filter_map_ok.rs | 29 +++++++ tests/ui/lines_filter_map_ok.stderr | 51 ++++++++++++ 8 files changed, 215 insertions(+) create mode 100644 clippy_lints/src/lines_filter_map_ok.rs create mode 100644 tests/ui/lines_filter_map_ok.fixed create mode 100644 tests/ui/lines_filter_map_ok.rs create mode 100644 tests/ui/lines_filter_map_ok.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index f54bfbfa472e8..239631777e9a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4647,6 +4647,7 @@ Released 2018-09-13 [`let_underscore_untyped`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_untyped [`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value [`let_with_type_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_with_type_underscore +[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok [`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist [`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug [`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index f07c7f0534c68..7836e5ccb9a6d 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -232,6 +232,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::let_with_type_underscore::LET_WITH_TYPE_UNDERSCORE_INFO, crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO, crate::lifetimes::NEEDLESS_LIFETIMES_INFO, + crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO, crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO, crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO, crate::literal_representation::LARGE_DIGIT_GROUPS_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 75c53a09d7964..ce055f16240bc 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -171,6 +171,7 @@ mod let_if_seq; mod let_underscore; mod let_with_type_underscore; mod lifetimes; +mod lines_filter_map_ok; mod literal_representation; mod loops; mod macro_use; @@ -949,6 +950,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: avoid_breaking_exported_api, )) }); + store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs new file mode 100644 index 0000000000000..b0f9276475d3e --- /dev/null +++ b/clippy_lints/src/lines_filter_map_ok.rs @@ -0,0 +1,100 @@ +use clippy_utils::{ + diagnostics::span_lint_and_then, is_diag_item_method, is_trait_method, match_def_path, path_to_local_id, paths, + ty::match_type, +}; +use rustc_errors::Applicability; +use rustc_hir::{Body, Closure, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Detect uses of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)` + /// when `lines` has type `std::io::Lines`. + /// + /// ### Why is this bad? + /// `Lines` instances might produce a never-ending stream of `Err`, in which case + /// `filter_map(Result::ok)` will enter an infinite loop while waiting for an + /// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop, + /// even in the absence of explicit loops in the user code. + /// + /// This situation can arise when working with user-provided paths. On some platforms, + /// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory, + /// but any later attempt to read from `fs` will return an error. + /// + /// ### Known problems + /// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines` + /// instance in all cases. There two cases where the suggestion might not be + /// appropriate or necessary: + /// + /// - If the `Lines` instance can never produce any error, or if an error is produced + /// only once just before terminating the iterator, using `map_while()` is not + /// necessary but will not do any harm. + /// - If the `Lines` instance can produce intermittent errors then recover and produce + /// successful results, using `map_while()` would stop at the first error. + /// + /// ### Example + /// ```rust + /// # use std::{fs::File, io::{self, BufRead, BufReader}}; + /// # let _ = || -> io::Result<()> { + /// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok); + /// // If "some-path" points to a directory, the next statement never terminates: + /// let first_line: Option = lines.next(); + /// # Ok(()) }; + /// ``` + /// Use instead: + /// ```rust + /// # use std::{fs::File, io::{self, BufRead, BufReader}}; + /// # let _ = || -> io::Result<()> { + /// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok); + /// let first_line: Option = lines.next(); + /// # Ok(()) }; + /// ``` + #[clippy::version = "1.70.0"] + pub LINES_FILTER_MAP_OK, + suspicious, + "filtering `std::io::Lines` with `filter_map()` or `flat_map()` might cause an infinite loop" +} +declare_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]); + +impl LateLintPass<'_> for LinesFilterMapOk { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::MethodCall(fm_method, fm_receiver, [fm_arg], fm_span) = expr.kind && + is_trait_method(cx, expr, sym::Iterator) && + (fm_method.ident.as_str() == "filter_map" || fm_method.ident.as_str() == "flat_map") && + match_type(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), &paths::STD_IO_LINES) + { + let lint = match &fm_arg.kind { + // Detect `Result::ok` + ExprKind::Path(qpath) => + cx.qpath_res(qpath, fm_arg.hir_id).opt_def_id().map(|did| + match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)).unwrap_or_default(), + // Detect `|x| x.ok()` + ExprKind::Closure(Closure { body, .. }) => + if let Body { params: [param], value, .. } = cx.tcx.hir().body(*body) && + let ExprKind::MethodCall(method, receiver, [], _) = value.kind && + path_to_local_id(receiver, param.pat.hir_id) && + let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id) + { + is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok" + } else { + false + } + _ => false, + }; + if lint { + span_lint_and_then(cx, + LINES_FILTER_MAP_OK, + fm_span, + &format!("`{}()` will run forever if the iterator repeatedly produces an `Err`", fm_method.ident), + |diag| { + diag.span_note( + fm_receiver.span, + "this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error"); + diag.span_suggestion(fm_span, "replace with", "map_while(Result::ok)", Applicability::MaybeIncorrect); + }); + } + } + } +} diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index c919575bfe906..9be2d0eae80aa 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -23,6 +23,7 @@ pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"]; pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"]; pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"]; pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"]; +pub const CORE_RESULT_OK_METHOD: [&str; 4] = ["core", "result", "Result", "ok"]; pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"]; pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"]; pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"]; @@ -113,6 +114,7 @@ pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"]; pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"]; pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"]; pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"]; +pub const STD_IO_LINES: [&str; 3] = ["std", "io", "Lines"]; pub const STD_IO_SEEK: [&str; 3] = ["std", "io", "Seek"]; pub const STD_IO_SEEK_FROM_CURRENT: [&str; 4] = ["std", "io", "SeekFrom", "Current"]; pub const STD_IO_SEEKFROM_START: [&str; 4] = ["std", "io", "SeekFrom", "Start"]; diff --git a/tests/ui/lines_filter_map_ok.fixed b/tests/ui/lines_filter_map_ok.fixed new file mode 100644 index 0000000000000..f4033cd8ed850 --- /dev/null +++ b/tests/ui/lines_filter_map_ok.fixed @@ -0,0 +1,29 @@ +// run-rustfix + +#![allow(unused, clippy::map_identity)] +#![warn(clippy::lines_filter_map_ok)] + +use std::io::{self, BufRead, BufReader}; + +fn main() -> io::Result<()> { + let f = std::fs::File::open("/")?; + // Lint + BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); + // Lint + let f = std::fs::File::open("/")?; + BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); + let s = "foo\nbar\nbaz\n"; + // Lint + io::stdin().lines().map_while(Result::ok).for_each(|_| ()); + // Lint + io::stdin().lines().map_while(Result::ok).for_each(|_| ()); + // Do not lint (not a `Lines` iterator) + io::stdin() + .lines() + .map(std::convert::identity) + .filter_map(|x| x.ok()) + .for_each(|_| ()); + // Do not lint (not a `Result::ok()` extractor) + io::stdin().lines().filter_map(|x| x.err()).for_each(|_| ()); + Ok(()) +} diff --git a/tests/ui/lines_filter_map_ok.rs b/tests/ui/lines_filter_map_ok.rs new file mode 100644 index 0000000000000..7e11816b2acd3 --- /dev/null +++ b/tests/ui/lines_filter_map_ok.rs @@ -0,0 +1,29 @@ +// run-rustfix + +#![allow(unused, clippy::map_identity)] +#![warn(clippy::lines_filter_map_ok)] + +use std::io::{self, BufRead, BufReader}; + +fn main() -> io::Result<()> { + let f = std::fs::File::open("/")?; + // Lint + BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); + // Lint + let f = std::fs::File::open("/")?; + BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); + let s = "foo\nbar\nbaz\n"; + // Lint + io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); + // Lint + io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); + // Do not lint (not a `Lines` iterator) + io::stdin() + .lines() + .map(std::convert::identity) + .filter_map(|x| x.ok()) + .for_each(|_| ()); + // Do not lint (not a `Result::ok()` extractor) + io::stdin().lines().filter_map(|x| x.err()).for_each(|_| ()); + Ok(()) +} diff --git a/tests/ui/lines_filter_map_ok.stderr b/tests/ui/lines_filter_map_ok.stderr new file mode 100644 index 0000000000000..cddd403d589c3 --- /dev/null +++ b/tests/ui/lines_filter_map_ok.stderr @@ -0,0 +1,51 @@ +error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` + --> $DIR/lines_filter_map_ok.rs:11:31 + | +LL | BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` + | +note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error + --> $DIR/lines_filter_map_ok.rs:11:5 + | +LL | BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::lines-filter-map-ok` implied by `-D warnings` + +error: `flat_map()` will run forever if the iterator repeatedly produces an `Err` + --> $DIR/lines_filter_map_ok.rs:14:31 + | +LL | BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); + | ^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` + | +note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error + --> $DIR/lines_filter_map_ok.rs:14:5 + | +LL | BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` + --> $DIR/lines_filter_map_ok.rs:17:25 + | +LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` + | +note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error + --> $DIR/lines_filter_map_ok.rs:17:5 + | +LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); + | ^^^^^^^^^^^^^^^^^^^ + +error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` + --> $DIR/lines_filter_map_ok.rs:19:25 + | +LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` + | +note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error + --> $DIR/lines_filter_map_ok.rs:19:5 + | +LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + From b2856a763e2dcb4ffeaf918a7c0d54f6e950cc21 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Fri, 24 Mar 2023 22:39:26 +0100 Subject: [PATCH 45/86] Add `tests_outside_test_module` lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/tests_outside_test_module.rs | 101 ++++++++++++++++++ tests/ui/tests_outside_test_module.rs | 18 ++++ tests/ui/tests_outside_test_module.stderr | 11 ++ 6 files changed, 134 insertions(+) create mode 100644 clippy_lints/src/tests_outside_test_module.rs create mode 100644 tests/ui/tests_outside_test_module.rs create mode 100644 tests/ui/tests_outside_test_module.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 239631777e9a3..ba10cb53ec929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4936,6 +4936,7 @@ Released 2018-09-13 [`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments [`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment [`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr +[`tests_outside_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#tests_outside_test_module [`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some [`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display [`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 7836e5ccb9a6d..b451055703354 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -577,6 +577,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::swap_ptr_to_ref::SWAP_PTR_TO_REF_INFO, crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO, crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO, + crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO, crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ce055f16240bc..c63abf0979dee 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -291,6 +291,7 @@ mod swap; mod swap_ptr_to_ref; mod tabs_in_doc_comments; mod temporary_assignment; +mod tests_outside_test_module; mod to_digit_is_some; mod trailing_empty_array; mod trait_bounds; @@ -951,6 +952,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: )) }); store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk)); + store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule::new())); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/tests_outside_test_module.rs b/clippy_lints/src/tests_outside_test_module.rs new file mode 100644 index 0000000000000..d14a292a52ffe --- /dev/null +++ b/clippy_lints/src/tests_outside_test_module.rs @@ -0,0 +1,101 @@ +use clippy_utils::{diagnostics::span_lint_and_note, is_in_cfg_test, is_in_test_function, is_test_module_or_function}; +use rustc_data_structures::sync::par_for_each_in; +use rustc_hir::{intravisit::FnKind, Body, FnDecl, HirId, ItemKind, Mod}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{def_id::LocalDefId, Span}; + +declare_clippy_lint! { + /// ### What it does + /// + /// Triggers when a testing function (marked with the `#[test]` attribute) isn't inside a testing module (marked with `#[cfg(test)]`). + /// + /// ### Why is this bad? + /// + /// The idiomatic (and more performant) way of writing tests is inside a testing module (flagged with `#[cfg(test)]`), having test functions outside of this module is confusing and may lead to them being "hidden". + /// + /// ### Example + /// ```rust + /// #[test] + /// fn my_cool_test() { + /// // [...] + /// } + /// + /// #[cfg(test)] + /// mod tests { + /// // [...] + /// } + /// + /// ``` + /// Use instead: + /// ```rust + /// #[cfg(test)] + /// mod tests { + /// #[test] + /// fn my_cool_test() { + /// // [...] + /// } + /// } + /// ``` + #[clippy::version = "1.70.0"] + pub TESTS_OUTSIDE_TEST_MODULE, + restriction, + "The test function `my_cool_test` is outside the testing module `tests`." +} + +pub(crate) struct TestsOutsideTestModule { + pub test_mod_exists: bool, +} + +impl TestsOutsideTestModule { + pub fn new() -> Self { + Self { test_mod_exists: false } + } +} + +impl_lint_pass!(TestsOutsideTestModule => [TESTS_OUTSIDE_TEST_MODULE]); + +impl LateLintPass<'_> for TestsOutsideTestModule { + fn check_mod(&mut self, cx: &LateContext<'_>, _: &Mod<'_>, _: HirId) { + self.test_mod_exists = false; + + // par_for_each_item uses Fn, while par_for_each_in uses FnMut + par_for_each_in(cx.tcx.hir_crate_items(()).items(), |itemid| { + let item = cx.tcx.hir().item(itemid); + if_chain! { + if matches!(item.kind, ItemKind::Mod(_)); + if is_test_module_or_function(cx.tcx, item); + then { + self.test_mod_exists = true; + } + } + }); + } + + fn check_fn( + &mut self, + cx: &LateContext<'_>, + kind: FnKind<'_>, + _: &FnDecl<'_>, + body: &Body<'_>, + sp: Span, + _: LocalDefId, + ) { + if_chain! { + if !matches!(kind, FnKind::Closure); + if self.test_mod_exists; + if is_in_test_function(cx.tcx, body.id().hir_id); + if !is_in_cfg_test(cx.tcx, body.id().hir_id); + then { + span_lint_and_note( + cx, + TESTS_OUTSIDE_TEST_MODULE, + sp, + "this function marked with #[test] is outside a #[cfg(test)] module", + None, + "move it to a testing module marked with #[cfg(test)]", + ); + } + } + } +} diff --git a/tests/ui/tests_outside_test_module.rs b/tests/ui/tests_outside_test_module.rs new file mode 100644 index 0000000000000..1982b1d0107f8 --- /dev/null +++ b/tests/ui/tests_outside_test_module.rs @@ -0,0 +1,18 @@ +// compile-flags: --test +#![allow(unused)] +#![warn(clippy::tests_outside_test_module)] + +fn main() { + // test code goes here +} + +// Should lint +#[test] +fn my_test() {} + +#[cfg(test)] +mod tests { + // Should not lint + #[test] + fn my_test() {} +} diff --git a/tests/ui/tests_outside_test_module.stderr b/tests/ui/tests_outside_test_module.stderr new file mode 100644 index 0000000000000..125a79d6edfed --- /dev/null +++ b/tests/ui/tests_outside_test_module.stderr @@ -0,0 +1,11 @@ +error: this function marked with #[test] is outside a #[cfg(test)] module + --> $DIR/tests_outside_test_module.rs:11:1 + | +LL | fn my_test() {} + | ^^^^^^^^^^^^^^^ + | + = note: move it to a testing module marked with #[cfg(test)] + = note: `-D clippy::tests-outside-test-module` implied by `-D warnings` + +error: aborting due to previous error + From 05650b7215bfda4c03cab8acc16b83e67139d7de Mon Sep 17 00:00:00 2001 From: Caio Date: Sun, 2 Apr 2023 07:50:25 -0300 Subject: [PATCH 46/86] [arithmetic_side_effects] Fix #10583 --- .../src/operators/arithmetic_side_effects.rs | 12 ++++++++++-- tests/ui/arithmetic_side_effects.rs | 4 ++++ tests/ui/arithmetic_side_effects.stderr | 14 +++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index 25e8de9486370..e5713735672da 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -154,10 +154,18 @@ impl ArithmeticSideEffects { Self::literal_integer(cx, actual_rhs), ) { (None, None) => false, - (None, Some(n)) | (Some(n), None) => match (&op.node, n) { + (None, Some(n)) => match (&op.node, n) { // Division and module are always valid if applied to non-zero integers (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true, - // Addition or subtracting zeros is always a no-op + // Adding or subtracting zeros is always a no-op + (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) + // Multiplication by 1 or 0 will never overflow + | (hir::BinOpKind::Mul, 0 | 1) + => true, + _ => false, + }, + (Some(n), None) => match (&op.node, n) { + // Adding or subtracting zeros is always a no-op (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) // Multiplication by 1 or 0 will never overflow | (hir::BinOpKind::Mul, 0 | 1) diff --git a/tests/ui/arithmetic_side_effects.rs b/tests/ui/arithmetic_side_effects.rs index ee7d2ba444b20..3c06676d7228a 100644 --- a/tests/ui/arithmetic_side_effects.rs +++ b/tests/ui/arithmetic_side_effects.rs @@ -425,4 +425,8 @@ pub fn integer_arithmetic() { i ^= i; } +pub fn issue_10583(a: u16) -> u16 { + 10 / a +} + fn main() {} diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index 3895f08964ce8..2c8ee2884e732 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -576,6 +576,12 @@ error: arithmetic operation that can potentially result in unexpected side-effec LL | i * 2; | ^^^^^ +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:394:5 + | +LL | 1 % i / 2; + | ^^^^^ + error: arithmetic operation that can potentially result in unexpected side-effects --> $DIR/arithmetic_side_effects.rs:395:5 | @@ -642,5 +648,11 @@ error: arithmetic operation that can potentially result in unexpected side-effec LL | i %= var2; | ^^^^^^^^^ -error: aborting due to 107 previous errors +error: arithmetic operation that can potentially result in unexpected side-effects + --> $DIR/arithmetic_side_effects.rs:429:5 + | +LL | 10 / a + | ^^^^^^ + +error: aborting due to 109 previous errors From 36047b0216b9fb4741e8927c7e6b644727e02fdf Mon Sep 17 00:00:00 2001 From: blyxyas Date: Sun, 2 Apr 2023 16:18:03 +0200 Subject: [PATCH 47/86] Fix `nonminimal_bool` `#[allow]` attributes. --- clippy_lints/src/booleans.rs | 39 +++++++++++++++++++----------------- tests/ui/nonminimal_bool.rs | 18 +++++++++++++++++ 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 29fde9336c0b9..455f0df7cd0ad 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -7,7 +7,7 @@ use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, Level}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; @@ -430,23 +430,25 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> { } } let nonminimal_bool_lint = |suggestions: Vec<_>| { - span_lint_hir_and_then( - self.cx, - NONMINIMAL_BOOL, - e.hir_id, - e.span, - "this boolean expression can be simplified", - |diag| { - diag.span_suggestions( - e.span, - "try", - suggestions.into_iter(), - // nonminimal_bool can produce minimal but - // not human readable expressions (#3141) - Applicability::Unspecified, - ); - }, - ); + if self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, e.hir_id).0 != Level::Allow { + span_lint_hir_and_then( + self.cx, + NONMINIMAL_BOOL, + e.hir_id, + e.span, + "this boolean expression can be simplified", + |diag| { + diag.span_suggestions( + e.span, + "try", + suggestions.into_iter(), + // nonminimal_bool can produce minimal but + // not human readable expressions (#3141) + Applicability::Unspecified, + ); + }, + ); + } }; if improvements.is_empty() { let mut visitor = NotSimplificationVisitor { cx: self.cx }; @@ -498,6 +500,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> { if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind && !inner.span.from_expansion() && let Some(suggestion) = simplify_not(self.cx, inner) + && self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow { span_lint_and_sugg( self.cx, diff --git a/tests/ui/nonminimal_bool.rs b/tests/ui/nonminimal_bool.rs index 3b5a374b4a7b4..80cc7c60f56e5 100644 --- a/tests/ui/nonminimal_bool.rs +++ b/tests/ui/nonminimal_bool.rs @@ -92,3 +92,21 @@ fn issue_10523_2() { } if a!() {} } + +fn issue_10435() { + let x = vec![0]; + let y = vec![1]; + let z = vec![2]; + + // vvv Should not lint + #[allow(clippy::nonminimal_bool)] + if !x.is_empty() && !(y.is_empty() || z.is_empty()) { + println!("{}", line!()); + } + + // vvv Should not lint (#10435 talks about a bug where it lints) + #[allow(clippy::nonminimal_bool)] + if !(x == [0]) { + println!("{}", line!()); + } +} From e2742a0ea24b4c596514021d0c94ff5a78ff0757 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Mon, 3 Apr 2023 01:27:53 +0200 Subject: [PATCH 48/86] Fix `double_must_use` for async functions --- clippy_lints/src/functions/must_use.rs | 7 ++++--- tests/ui/double_must_use.rs | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 1e9e826631c37..56acd31b1d2d2 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -23,7 +23,8 @@ use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT}; pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { let attrs = cx.tcx.hir().attrs(item.hir_id()); let attr = cx.tcx.get_attr(item.owner_id, sym::must_use); - if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind { + if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind && !sig.header.is_async() /* (#10486) */ { + let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); if let Some(attr) = attr { @@ -43,7 +44,7 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_> } pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { - if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind { + if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind && !sig.header.is_async() /* (#10486) */ { let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); let attrs = cx.tcx.hir().attrs(item.hir_id()); @@ -65,7 +66,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp } pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { - if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind { + if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind && !sig.header.is_async() /* (#10486) */ { let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); diff --git a/tests/ui/double_must_use.rs b/tests/ui/double_must_use.rs index 05e087b08bc14..a4cd8630de599 100644 --- a/tests/ui/double_must_use.rs +++ b/tests/ui/double_must_use.rs @@ -21,6 +21,12 @@ pub fn must_use_with_note() -> Result<(), ()> { unimplemented!(); } +// vvvv Should not lint (#10486) +#[must_use] +async fn async_must_use() -> usize { + unimplemented!(); +} + fn main() { must_use_result(); must_use_tuple(); From d60274355894c0e0768b034ab538aa0625fc2a91 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Mon, 3 Apr 2023 15:15:43 +0200 Subject: [PATCH 49/86] only focus on `double_must_use` + Add `Result<(), ()>` test --- clippy_lints/src/functions/must_use.rs | 17 +++++++++-------- tests/ui/double_must_use.rs | 5 +++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 56acd31b1d2d2..ab68d7a3726fa 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -1,3 +1,4 @@ +use hir::FnSig; use rustc_ast::ast::Attribute; use rustc_errors::Applicability; use rustc_hir::def_id::DefIdSet; @@ -23,12 +24,11 @@ use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT}; pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { let attrs = cx.tcx.hir().attrs(item.hir_id()); let attr = cx.tcx.get_attr(item.owner_id, sym::must_use); - if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind && !sig.header.is_async() /* (#10486) */ { - + if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind { let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); if let Some(attr) = attr { - check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr); + check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig); } else if is_public && !is_proc_macro(attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) { check_must_use_candidate( cx, @@ -44,13 +44,13 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_> } pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { - if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind && !sig.header.is_async() /* (#10486) */ { + if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind { let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); let attrs = cx.tcx.hir().attrs(item.hir_id()); let attr = cx.tcx.get_attr(item.owner_id, sym::must_use); if let Some(attr) = attr { - check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr); + check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig); } else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id.def_id).is_none() { check_must_use_candidate( cx, @@ -66,14 +66,14 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp } pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { - if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind && !sig.header.is_async() /* (#10486) */ { + if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind { let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); let attrs = cx.tcx.hir().attrs(item.hir_id()); let attr = cx.tcx.get_attr(item.owner_id, sym::must_use); if let Some(attr) = attr { - check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr); + check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig); } else if let hir::TraitFn::Provided(eid) = *eid { let body = cx.tcx.hir().body(eid); if attr.is_none() && is_public && !is_proc_macro(attrs) { @@ -98,6 +98,7 @@ fn check_needless_must_use( item_span: Span, fn_header_span: Span, attr: &Attribute, + sig: &FnSig<'_>, ) { if in_external_macro(cx.sess(), item_span) { return; @@ -112,7 +113,7 @@ fn check_needless_must_use( diag.span_suggestion(attr.span, "remove the attribute", "", Applicability::MachineApplicable); }, ); - } else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) { + } else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) && !sig.header.is_async() { span_lint_and_help( cx, DOUBLE_MUST_USE, diff --git a/tests/ui/double_must_use.rs b/tests/ui/double_must_use.rs index a4cd8630de599..26a387b3cf049 100644 --- a/tests/ui/double_must_use.rs +++ b/tests/ui/double_must_use.rs @@ -27,6 +27,11 @@ async fn async_must_use() -> usize { unimplemented!(); } +#[must_use] +async fn async_must_use_result() -> Result<(), ()> { + Ok(()) +} + fn main() { must_use_result(); must_use_tuple(); From afdfbf8fdea085f62ec64537135170a748c991d4 Mon Sep 17 00:00:00 2001 From: bhould287 Date: Sat, 1 Apr 2023 21:30:39 +0100 Subject: [PATCH 50/86] Show multiple clippy.toml warnings with `sess.warn` instead of `eprintln!` --- clippy_lints/src/lib.rs | 14 +++++++++----- clippy_lints/src/utils/conf.rs | 19 ++++++++++--------- src/driver.rs | 2 +- .../warn/src/main.stderr | 6 ++++-- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ce055f16240bc..51e21765953c7 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -348,13 +348,17 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se } #[doc(hidden)] -pub fn read_conf(sess: &Session, path: &io::Result>) -> Conf { +pub fn read_conf(sess: &Session, path: &io::Result<(Option, Vec)>) -> Conf { + if let Ok((_, warnings)) = path { + for warning in warnings { + sess.warn(warning); + } + } let file_name = match path { - Ok(Some(path)) => path, - Ok(None) => return Conf::default(), + Ok((Some(path), _)) => path, + Ok((None, _)) => return Conf::default(), Err(error) => { - sess.struct_err(format!("error finding Clippy's configuration file: {error}")) - .emit(); + sess.err(format!("error finding Clippy's configuration file: {error}")); return Conf::default(); }, }; diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index f75a5cf7196a2..25626897b510d 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -470,7 +470,7 @@ define_Conf! { /// # Errors /// /// Returns any unexpected filesystem error encountered when searching for the config file -pub fn lookup_conf_file() -> io::Result> { +pub fn lookup_conf_file() -> io::Result<(Option, Vec)> { /// Possible filename to search for. const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"]; @@ -481,6 +481,7 @@ pub fn lookup_conf_file() -> io::Result> { .map_or_else(|| PathBuf::from("."), PathBuf::from); let mut found_config: Option = None; + let mut warnings = vec![]; loop { for config_file_name in &CONFIG_FILE_NAMES { @@ -491,12 +492,12 @@ pub fn lookup_conf_file() -> io::Result> { Ok(md) if md.is_dir() => {}, Ok(_) => { // warn if we happen to find two config files #8323 - if let Some(ref found_config_) = found_config { - eprintln!( - "Using config file `{}`\nWarning: `{}` will be ignored.", - found_config_.display(), - config_file.display(), - ); + if let Some(ref found_config) = found_config { + warnings.push(format!( + "using config file `{}`, `{}` will be ignored", + found_config.display(), + config_file.display() + )); } else { found_config = Some(config_file); } @@ -506,12 +507,12 @@ pub fn lookup_conf_file() -> io::Result> { } if found_config.is_some() { - return Ok(found_config); + return Ok((found_config, warnings)); } // If the current directory has no parent, we're done searching. if !current.pop() { - return Ok(None); + return Ok((None, warnings)); } } } diff --git a/src/driver.rs b/src/driver.rs index f08393c303ef8..39f2bf9aa1998 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -130,7 +130,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks { #[allow(rustc::bad_opt_access)] fn config(&mut self, config: &mut interface::Config) { let conf_path = clippy_lints::lookup_conf_file(); - let conf_path_string = if let Ok(Some(path)) = &conf_path { + let conf_path_string = if let Ok((Some(path), _)) = &conf_path { path.to_str().map(String::from) } else { None diff --git a/tests/ui-cargo/multiple_config_files/warn/src/main.stderr b/tests/ui-cargo/multiple_config_files/warn/src/main.stderr index 98697e001f99a..aa1b3c638a036 100644 --- a/tests/ui-cargo/multiple_config_files/warn/src/main.stderr +++ b/tests/ui-cargo/multiple_config_files/warn/src/main.stderr @@ -1,2 +1,4 @@ -Using config file `$SRC_DIR/.clippy.toml` -Warning: `$SRC_DIR/clippy.toml` will be ignored. +warning: using config file `$SRC_DIR/.clippy.toml`, `$SRC_DIR/clippy.toml` will be ignored + +warning: 1 warning emitted + From a37eb4dfc97cdcbd177ccc0fda1909014b145635 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Mon, 3 Apr 2023 16:01:30 +0200 Subject: [PATCH 51/86] Fix false negative on `Result<(), ()>` --- clippy_lints/src/functions/must_use.rs | 12 +++++++++++- tests/ui/double_must_use.stderr | 10 +++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index ab68d7a3726fa..d0ad26282642c 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -3,6 +3,7 @@ use rustc_ast::ast::Attribute; use rustc_errors::Applicability; use rustc_hir::def_id::DefIdSet; use rustc_hir::{self as hir, def::Res, QPath}; +use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LintContext}; use rustc_middle::{ lint::in_external_macro, @@ -113,7 +114,16 @@ fn check_needless_must_use( diag.span_suggestion(attr.span, "remove the attribute", "", Applicability::MachineApplicable); }, ); - } else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) && !sig.header.is_async() { + } else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) { + // Ignore async functions unless Future::Output type is a must_use type + if sig.header.is_async() { + let infcx = cx.tcx.infer_ctxt().build(); + if let Some(future_ty) = infcx.get_impl_future_output_ty(return_ty(cx, item_id)) + && !is_must_use_ty(cx, future_ty) { + return; + } + } + span_lint_and_help( cx, DOUBLE_MUST_USE, diff --git a/tests/ui/double_must_use.stderr b/tests/ui/double_must_use.stderr index 3d34557a881bd..49ab2ea3e12b4 100644 --- a/tests/ui/double_must_use.stderr +++ b/tests/ui/double_must_use.stderr @@ -23,5 +23,13 @@ LL | pub fn must_use_array() -> [Result<(), ()>; 1] { | = help: either add some descriptive text or remove the attribute -error: aborting due to 3 previous errors +error: this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]` + --> $DIR/double_must_use.rs:31:1 + | +LL | async fn async_must_use_result() -> Result<(), ()> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: either add some descriptive text or remove the attribute + +error: aborting due to 4 previous errors From 3b22352d0d16034e112e58b032f1bf0fc1c818ba Mon Sep 17 00:00:00 2001 From: bhould287 Date: Sat, 1 Apr 2023 21:29:05 +0100 Subject: [PATCH 52/86] Fix bug with getting parent directories in `lookup_conf_file` --- clippy_lints/src/utils/conf.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 25626897b510d..896a01af37d99 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -478,7 +478,8 @@ pub fn lookup_conf_file() -> io::Result<(Option, Vec)> { // If neither of those exist, use ".". let mut current = env::var_os("CLIPPY_CONF_DIR") .or_else(|| env::var_os("CARGO_MANIFEST_DIR")) - .map_or_else(|| PathBuf::from("."), PathBuf::from); + .map_or_else(|| PathBuf::from("."), PathBuf::from) + .canonicalize()?; let mut found_config: Option = None; let mut warnings = vec![]; From bd2a5b2322729137b26b899947e777c41c9aae96 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Mon, 3 Apr 2023 18:42:00 +0200 Subject: [PATCH 53/86] Remove check for `#[cfg(test)]` --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/tests_outside_test_module.rs | 36 +++---------------- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c63abf0979dee..c16d662151899 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -952,7 +952,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: )) }); store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk)); - store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule::new())); + store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/tests_outside_test_module.rs b/clippy_lints/src/tests_outside_test_module.rs index d14a292a52ffe..e454008c68d95 100644 --- a/clippy_lints/src/tests_outside_test_module.rs +++ b/clippy_lints/src/tests_outside_test_module.rs @@ -1,8 +1,7 @@ -use clippy_utils::{diagnostics::span_lint_and_note, is_in_cfg_test, is_in_test_function, is_test_module_or_function}; -use rustc_data_structures::sync::par_for_each_in; -use rustc_hir::{intravisit::FnKind, Body, FnDecl, HirId, ItemKind, Mod}; +use clippy_utils::{diagnostics::span_lint_and_note, is_in_cfg_test, is_in_test_function}; +use rustc_hir::{intravisit::FnKind, Body, FnDecl}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::{def_id::LocalDefId, Span}; declare_clippy_lint! { @@ -43,35 +42,9 @@ declare_clippy_lint! { "The test function `my_cool_test` is outside the testing module `tests`." } -pub(crate) struct TestsOutsideTestModule { - pub test_mod_exists: bool, -} - -impl TestsOutsideTestModule { - pub fn new() -> Self { - Self { test_mod_exists: false } - } -} - -impl_lint_pass!(TestsOutsideTestModule => [TESTS_OUTSIDE_TEST_MODULE]); +declare_lint_pass!(TestsOutsideTestModule => [TESTS_OUTSIDE_TEST_MODULE]); impl LateLintPass<'_> for TestsOutsideTestModule { - fn check_mod(&mut self, cx: &LateContext<'_>, _: &Mod<'_>, _: HirId) { - self.test_mod_exists = false; - - // par_for_each_item uses Fn, while par_for_each_in uses FnMut - par_for_each_in(cx.tcx.hir_crate_items(()).items(), |itemid| { - let item = cx.tcx.hir().item(itemid); - if_chain! { - if matches!(item.kind, ItemKind::Mod(_)); - if is_test_module_or_function(cx.tcx, item); - then { - self.test_mod_exists = true; - } - } - }); - } - fn check_fn( &mut self, cx: &LateContext<'_>, @@ -83,7 +56,6 @@ impl LateLintPass<'_> for TestsOutsideTestModule { ) { if_chain! { if !matches!(kind, FnKind::Closure); - if self.test_mod_exists; if is_in_test_function(cx.tcx, body.id().hir_id); if !is_in_cfg_test(cx.tcx, body.id().hir_id); then { From c12748fab3a14beae4958f13551e7e6c52298490 Mon Sep 17 00:00:00 2001 From: feniljain Date: Sat, 1 Apr 2023 16:33:17 +0530 Subject: [PATCH 54/86] fix(needless_return): do not trigger on ambiguous match arms return --- clippy_lints/src/returns.rs | 32 +++++++++++++++++++++++++------- tests/ui/needless_return.fixed | 6 +++--- tests/ui/needless_return.stderr | 12 ++++++------ 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index f0d7dd23a6786..b8172b74dc011 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -9,7 +9,7 @@ use rustc_hir::intravisit::FnKind; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, LangItem, MatchSource, PatKind, QPath, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::{self, subst::GenericArgKind, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; @@ -175,7 +175,7 @@ impl<'tcx> LateLintPass<'tcx> for Return { } else { RetReplacement::Empty }; - check_final_expr(cx, body.value, vec![], replacement); + check_final_expr(cx, body.value, vec![], replacement, None); }, FnKind::ItemFn(..) | FnKind::Method(..) => { check_block_return(cx, &body.value.kind, sp, vec![]); @@ -188,11 +188,11 @@ impl<'tcx> LateLintPass<'tcx> for Return { fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec) { if let ExprKind::Block(block, _) = expr_kind { if let Some(block_expr) = block.expr { - check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty); + check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None); } else if let Some(stmt) = block.stmts.iter().last() { match stmt.kind { StmtKind::Expr(expr) => { - check_final_expr(cx, expr, semi_spans, RetReplacement::Empty); + check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None); }, StmtKind::Semi(semi_expr) => { // Remove ending semicolons and any whitespace ' ' in between. @@ -202,7 +202,7 @@ fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi())); semi_spans.push(semi_span_to_remove); } - check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty); + check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None); }, _ => (), } @@ -216,6 +216,7 @@ fn check_final_expr<'tcx>( semi_spans: Vec, /* containing all the places where we would need to remove semicolons if finding an * needless return */ replacement: RetReplacement<'tcx>, + match_ty_opt: Option>, ) { let peeled_drop_expr = expr.peel_drop_temps(); match &peeled_drop_expr.kind { @@ -244,7 +245,22 @@ fn check_final_expr<'tcx>( RetReplacement::Expr(snippet, applicability) } } else { - replacement + match match_ty_opt { + Some(match_ty) => { + match match_ty.kind() { + // If the code got till here with + // tuple not getting detected before it, + // then we are sure it's going to be Unit + // type + ty::Tuple(_) => RetReplacement::Unit, + // We don't want to anything in this case + // cause we can't predict what the user would + // want here + _ => return, + } + }, + None => replacement, + } }; if !cx.tcx.hir().attrs(expr.hir_id).is_empty() { @@ -268,8 +284,9 @@ fn check_final_expr<'tcx>( // note, if without else is going to be a type checking error anyways // (except for unit type functions) so we don't match it ExprKind::Match(_, arms, MatchSource::Normal) => { + let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr); for arm in arms.iter() { - check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit); + check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Empty, Some(match_ty)); } }, // if it's a whole block, check it @@ -293,6 +310,7 @@ fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, semi_spans: Vec, if ret_span.from_expansion() { return; } + let applicability = replacement.applicability().unwrap_or(Applicability::MachineApplicable); let return_replacement = replacement.to_string(); let sugg_help = replacement.sugg_help(); diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 0f525dd294c9b..38ae448618c02 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -81,7 +81,7 @@ fn test_void_if_fun(b: bool) { fn test_void_match(x: u32) { match x { 0 => (), - _ => (), + _ =>(), } } @@ -91,7 +91,7 @@ fn test_nested_match(x: u32) { 1 => { let _ = 42; }, - _ => (), + _ =>(), } } @@ -196,7 +196,7 @@ async fn async_test_void_if_fun(b: bool) { async fn async_test_void_match(x: u32) { match x { 0 => (), - _ => (), + _ =>(), } } diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index 87d0cd3e14cfa..5d471a3c52147 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -118,10 +118,10 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:87:14 + --> $DIR/needless_return.rs:87:13 | LL | _ => return, - | ^^^^^^ + | ^^^^^^^ | = help: replace `return` with a unit value @@ -136,10 +136,10 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:98:14 + --> $DIR/needless_return.rs:98:13 | LL | _ => return, - | ^^^^^^ + | ^^^^^^^ | = help: replace `return` with a unit value @@ -296,10 +296,10 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:207:14 + --> $DIR/needless_return.rs:207:13 | LL | _ => return, - | ^^^^^^ + | ^^^^^^^ | = help: replace `return` with a unit value From b2f919182045773ee2a4091ef071ffd3183efe45 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Tue, 4 Apr 2023 11:42:32 +0200 Subject: [PATCH 55/86] Fix formatting and lint description --- clippy_lints/src/tests_outside_test_module.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/tests_outside_test_module.rs b/clippy_lints/src/tests_outside_test_module.rs index e454008c68d95..0a0a77082e02f 100644 --- a/clippy_lints/src/tests_outside_test_module.rs +++ b/clippy_lints/src/tests_outside_test_module.rs @@ -6,13 +6,11 @@ use rustc_span::{def_id::LocalDefId, Span}; declare_clippy_lint! { /// ### What it does - /// - /// Triggers when a testing function (marked with the `#[test]` attribute) isn't inside a testing module (marked with `#[cfg(test)]`). - /// + /// Triggers when a testing function (marked with the `#[test]` attribute) isn't inside a testing module + /// (marked with `#[cfg(test)]`). /// ### Why is this bad? - /// - /// The idiomatic (and more performant) way of writing tests is inside a testing module (flagged with `#[cfg(test)]`), having test functions outside of this module is confusing and may lead to them being "hidden". - /// + /// The idiomatic (and more performant) way of writing tests is inside a testing module (flagged with `#[cfg(test)]`), + /// having test functions outside of this module is confusing and may lead to them being "hidden". /// ### Example /// ```rust /// #[test] @@ -39,7 +37,7 @@ declare_clippy_lint! { #[clippy::version = "1.70.0"] pub TESTS_OUTSIDE_TEST_MODULE, restriction, - "The test function `my_cool_test` is outside the testing module `tests`." + "A test function is outside the testing module." } declare_lint_pass!(TestsOutsideTestModule => [TESTS_OUTSIDE_TEST_MODULE]); From a6beddcc5a3419c7a829e654e9ce10f4a789bf31 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 29 Mar 2023 08:37:47 +0000 Subject: [PATCH 56/86] rust-analyzer guided enum variant structification --- clippy_lints/src/redundant_static_lifetimes.rs | 4 ++-- clippy_utils/src/ast_utils.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index 44bf824aa0e2d..d2f03f2a97fb0 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet; -use rustc_ast::ast::{Item, ItemKind, Ty, TyKind}; +use rustc_ast::ast::{Item, ItemKind, Ty, TyKind, Static}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -106,7 +106,7 @@ impl EarlyLintPass for RedundantStaticLifetimes { // #2438) } - if let ItemKind::Static(ref var_type, _, _) = item.kind { + if let ItemKind::Static(Static(ref var_type, _, _)) = item.kind { Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime"); } } diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index d2dedc2043957..30be129ad75a1 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -286,7 +286,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { match (l, r) { (ExternCrate(l), ExternCrate(r)) => l == r, (Use(l), Use(r)) => eq_use_tree(l, r), - (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Static(ast::Static(lt, lm, le)), Static(ast::Static(rt, rm, re))) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { From 929696d75473d990f8fb5c9f3058501b04b12a7e Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 29 Mar 2023 08:50:04 +0000 Subject: [PATCH 57/86] rust-analyzer guided tuple field to named field --- clippy_lints/src/redundant_static_lifetimes.rs | 2 +- clippy_utils/src/ast_utils.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index d2f03f2a97fb0..434f001133193 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -106,7 +106,7 @@ impl EarlyLintPass for RedundantStaticLifetimes { // #2438) } - if let ItemKind::Static(Static(ref var_type, _, _)) = item.kind { + if let ItemKind::Static(Static{ ty: ref var_type,.. }) = item.kind { Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime"); } } diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 30be129ad75a1..258207639a170 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -286,7 +286,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { match (l, r) { (ExternCrate(l), ExternCrate(r)) => l == r, (Use(l), Use(r)) => eq_use_tree(l, r), - (Static(ast::Static(lt, lm, le)), Static(ast::Static(rt, rm, re))) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Static(ast::Static{ ty: lt, mutability: lm, expr: le}), Static(ast::Static { ty: rt, mutability: rm, expr: re})) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { From ff7636db6a1350fe626b2172e367e721593505ed Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 29 Mar 2023 09:20:45 +0000 Subject: [PATCH 58/86] Split out ast::ItemKind::Const into its own struct --- clippy_lints/src/redundant_static_lifetimes.rs | 4 ++-- clippy_utils/src/ast_utils.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index 434f001133193..d41f79f81315e 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet; -use rustc_ast::ast::{Item, ItemKind, Ty, TyKind, Static}; +use rustc_ast::ast::{Item, ItemKind, Ty, TyKind, Static, ConstItem}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -100,7 +100,7 @@ impl EarlyLintPass for RedundantStaticLifetimes { } if !item.span.from_expansion() { - if let ItemKind::Const(_, ref var_type, _) = item.kind { + if let ItemKind::Const(ConstItem { ty: ref var_type, .. }) = item.kind { Self::visit_type(var_type, cx, "constants have by default a `'static` lifetime"); // Don't check associated consts because `'static` cannot be elided on those (issue // #2438) diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 258207639a170..be841098dba30 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -287,7 +287,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { (ExternCrate(l), ExternCrate(r)) => l == r, (Use(l), Use(r)) => eq_use_tree(l, r), (Static(ast::Static{ ty: lt, mutability: lm, expr: le}), Static(ast::Static { ty: rt, mutability: rm, expr: re})) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), - (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Const(ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(ast::ConstItem { defaultness: rd, ty: rt, expr: re} )) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { defaultness: ld, @@ -451,7 +451,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { use AssocItemKind::*; match (l, r) { - (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Const(ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(ast::ConstItem { defaultness: rd, ty: rt, expr: re})) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { defaultness: ld, From e610ddfa5ef4c6d1cd82c1cb7ab0092b799e7b00 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 29 Mar 2023 12:34:05 +0000 Subject: [PATCH 59/86] box a bunch of large types --- clippy_lints/src/redundant_static_lifetimes.rs | 4 ++-- clippy_utils/src/ast_utils.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index d41f79f81315e..aa47f3aed54d4 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -100,13 +100,13 @@ impl EarlyLintPass for RedundantStaticLifetimes { } if !item.span.from_expansion() { - if let ItemKind::Const(ConstItem { ty: ref var_type, .. }) = item.kind { + if let ItemKind::Const(box ConstItem { ty: ref var_type, .. }) = item.kind { Self::visit_type(var_type, cx, "constants have by default a `'static` lifetime"); // Don't check associated consts because `'static` cannot be elided on those (issue // #2438) } - if let ItemKind::Static(Static{ ty: ref var_type,.. }) = item.kind { + if let ItemKind::Static(box Static { ty: ref var_type,.. }) = item.kind { Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime"); } } diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index be841098dba30..0d2a7440c4659 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -286,8 +286,8 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { match (l, r) { (ExternCrate(l), ExternCrate(r)) => l == r, (Use(l), Use(r)) => eq_use_tree(l, r), - (Static(ast::Static{ ty: lt, mutability: lm, expr: le}), Static(ast::Static { ty: rt, mutability: rm, expr: re})) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), - (Const(ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(ast::ConstItem { defaultness: rd, ty: rt, expr: re} )) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Static(box ast::Static{ ty: lt, mutability: lm, expr: le}), Static(box ast::Static { ty: rt, mutability: rm, expr: re})) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Const(box ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(box ast::ConstItem { defaultness: rd, ty: rt, expr: re} )) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { defaultness: ld, @@ -451,7 +451,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { use AssocItemKind::*; match (l, r) { - (Const(ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(ast::ConstItem { defaultness: rd, ty: rt, expr: re})) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Const(box ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(box ast::ConstItem { defaultness: rd, ty: rt, expr: re})) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { defaultness: ld, From e9c7fb10b92c47e9bb2713fae5ef74a07efce92d Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 29 Mar 2023 14:14:11 +0000 Subject: [PATCH 60/86] Rename `ast::Static` to `ast::StaticItem` to match `ast::ConstItem` --- clippy_lints/src/redundant_static_lifetimes.rs | 4 ++-- clippy_utils/src/ast_utils.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index aa47f3aed54d4..11b908e7e53d1 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet; -use rustc_ast::ast::{Item, ItemKind, Ty, TyKind, Static, ConstItem}; +use rustc_ast::ast::{Item, ItemKind, Ty, TyKind, StaticItem, ConstItem}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -106,7 +106,7 @@ impl EarlyLintPass for RedundantStaticLifetimes { // #2438) } - if let ItemKind::Static(box Static { ty: ref var_type,.. }) = item.kind { + if let ItemKind::Static(box StaticItem { ty: ref var_type,.. }) = item.kind { Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime"); } } diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 0d2a7440c4659..c5b58b0c060c0 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -286,7 +286,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { match (l, r) { (ExternCrate(l), ExternCrate(r)) => l == r, (Use(l), Use(r)) => eq_use_tree(l, r), - (Static(box ast::Static{ ty: lt, mutability: lm, expr: le}), Static(box ast::Static { ty: rt, mutability: rm, expr: re})) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), + (Static(box ast::StaticItem { ty: lt, mutability: lm, expr: le}), Static(box ast::StaticItem { ty: rt, mutability: rm, expr: re})) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), (Const(box ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(box ast::ConstItem { defaultness: rd, ty: rt, expr: re} )) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { From 2891d8f72f8e598177c0bf6c1d56ad2e70a1f536 Mon Sep 17 00:00:00 2001 From: "Samuel \"Sam\" Tardieu" Date: Sun, 26 Mar 2023 17:11:34 +0200 Subject: [PATCH 61/86] Make redundant_async_block a more complete late pass This lets us detect more complex situations: `async { x.await }` is simplified into `x` if: - `x` is an expression without side-effect - or `x` is an async block itself In both cases, no part of the `async` expression can be part of a macro expansion. --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/redundant_async_block.rs | 145 ++++++++++----------- tests/ui/redundant_async_block.fixed | 148 +++++++++++++++++----- tests/ui/redundant_async_block.rs | 148 +++++++++++++++++----- tests/ui/redundant_async_block.stderr | 46 ++++++- 5 files changed, 338 insertions(+), 151 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c9210bf73f896..4638af8da9f35 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -935,7 +935,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi)); store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead)); store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage)); - store.register_early_pass(|| Box::new(redundant_async_block::RedundantAsyncBlock)); + store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock)); store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped)); store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute)); store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv()))); diff --git a/clippy_lints/src/redundant_async_block.rs b/clippy_lints/src/redundant_async_block.rs index 2d30e77d55ded..a0f831764d071 100644 --- a/clippy_lints/src/redundant_async_block.rs +++ b/clippy_lints/src/redundant_async_block.rs @@ -1,8 +1,15 @@ -use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet}; -use rustc_ast::ast::{Expr, ExprKind, Stmt, StmtKind}; -use rustc_ast::visit::Visitor as AstVisitor; +use std::ops::ControlFlow; + +use clippy_utils::{ + diagnostics::span_lint_and_sugg, + peel_blocks, + source::{snippet, walk_span_to_context}, + visitors::for_each_expr, +}; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_hir::{AsyncGeneratorKind, Closure, Expr, ExprKind, GeneratorKind, MatchSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::{lint::in_external_macro, ty::UpvarCapture}; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { @@ -14,106 +21,88 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// async fn f() -> i32 { - /// 1 + 2 - /// } - /// + /// let f = async { + /// 1 + 2 + /// }; /// let fut = async { - /// f().await + /// f.await /// }; /// ``` /// Use instead: /// ```rust - /// async fn f() -> i32 { - /// 1 + 2 - /// } - /// - /// let fut = f(); + /// let f = async { + /// 1 + 2 + /// }; + /// let fut = f; /// ``` #[clippy::version = "1.69.0"] pub REDUNDANT_ASYNC_BLOCK, - nursery, + complexity, "`async { future.await }` can be replaced by `future`" } declare_lint_pass!(RedundantAsyncBlock => [REDUNDANT_ASYNC_BLOCK]); -impl EarlyLintPass for RedundantAsyncBlock { - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if expr.span.from_expansion() { - return; - } - if let ExprKind::Async(_, _, block) = &expr.kind && block.stmts.len() == 1 && - let Some(Stmt { kind: StmtKind::Expr(last), .. }) = block.stmts.last() && - let ExprKind::Await(future) = &last.kind && - !future.span.from_expansion() && - !await_in_expr(future) +impl<'tcx> LateLintPass<'tcx> for RedundantAsyncBlock { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let span = expr.span; + if !in_external_macro(cx.tcx.sess, span) && + let Some(body_expr) = desugar_async_block(cx, expr) && + let Some(expr) = desugar_await(peel_blocks(body_expr)) && + // The await prefix must not come from a macro as its content could change in the future. + expr.span.ctxt() == body_expr.span.ctxt() && + // An async block does not have immediate side-effects from a `.await` point-of-view. + (!expr.can_have_side_effects() || desugar_async_block(cx, expr).is_some()) && + let Some(shortened_span) = walk_span_to_context(expr.span, span.ctxt()) { - if captures_value(last) { - // If the async block captures variables then there is no equivalence. - return; - } - span_lint_and_sugg( cx, REDUNDANT_ASYNC_BLOCK, - expr.span, + span, "this async expression only awaits a single future", "you can reduce it to", - snippet(cx, future.span, "..").into_owned(), + snippet(cx, shortened_span, "..").into_owned(), Applicability::MachineApplicable, ); } } } -/// Check whether an expression contains `.await` -fn await_in_expr(expr: &Expr) -> bool { - let mut detector = AwaitDetector::default(); - detector.visit_expr(expr); - detector.await_found -} - -#[derive(Default)] -struct AwaitDetector { - await_found: bool, -} - -impl<'ast> AstVisitor<'ast> for AwaitDetector { - fn visit_expr(&mut self, ex: &'ast Expr) { - match (&ex.kind, self.await_found) { - (ExprKind::Await(_), _) => self.await_found = true, - (_, false) => rustc_ast::visit::walk_expr(self, ex), - _ => (), - } +/// If `expr` is a desugared `async` block, return the original expression if it does not capture +/// any variable by ref. +fn desugar_async_block<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Closure(Closure { body, def_id, .. }) = expr.kind && + let body = cx.tcx.hir().body(*body) && + matches!(body.generator_kind, Some(GeneratorKind::Async(AsyncGeneratorKind::Block))) + { + cx + .typeck_results() + .closure_min_captures + .get(def_id) + .map_or(true, |m| { + m.values().all(|places| { + places + .iter() + .all(|place| matches!(place.info.capture_kind, UpvarCapture::ByValue)) + }) + }) + .then_some(body.value) + } else { + None } } -/// Check whether an expression may have captured a local variable. -/// This is done by looking for paths with only one segment, except as -/// a prefix of `.await` since this would be captured by value. -/// -/// This function will sometimes return `true` even tough there are no -/// captures happening: at the AST level, it is impossible to -/// dinstinguish a function call from a call to a closure which comes -/// from the local environment. -fn captures_value(expr: &Expr) -> bool { - let mut detector = CaptureDetector::default(); - detector.visit_expr(expr); - detector.capture_found -} - -#[derive(Default)] -struct CaptureDetector { - capture_found: bool, -} - -impl<'ast> AstVisitor<'ast> for CaptureDetector { - fn visit_expr(&mut self, ex: &'ast Expr) { - match (&ex.kind, self.capture_found) { - (ExprKind::Await(fut), _) if matches!(fut.kind, ExprKind::Path(..)) => (), - (ExprKind::Path(_, path), _) if path.segments.len() == 1 => self.capture_found = true, - (_, false) => rustc_ast::visit::walk_expr(self, ex), - _ => (), - } +/// If `expr` is a desugared `.await`, return the original expression if it does not come from a +/// macro expansion. +fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind && + let ExprKind::Call(_, [into_future_arg]) = match_value.kind && + let ctxt = expr.span.ctxt() && + for_each_expr(into_future_arg, |e| + walk_span_to_context(e.span, ctxt) + .map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(()))).is_none() + { + Some(into_future_arg) + } else { + None } } diff --git a/tests/ui/redundant_async_block.fixed b/tests/ui/redundant_async_block.fixed index d26b7a332cbbf..ad96993c4a78c 100644 --- a/tests/ui/redundant_async_block.fixed +++ b/tests/ui/redundant_async_block.fixed @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused)] +#![allow(unused, clippy::manual_async_fn)] #![warn(clippy::redundant_async_block)] use std::future::Future; @@ -16,40 +16,16 @@ async fn func2() -> String { x.await } -macro_rules! await_in_macro { - ($e:expr) => { - std::convert::identity($e).await - }; -} - -async fn func3(n: usize) -> usize { - // Do not lint (suggestion would be `std::convert::identity(func1(n))` - // which copies code from inside the macro) - async move { await_in_macro!(func1(n)) }.await -} - -// This macro should never be linted as `$e` might contain `.await` -macro_rules! async_await_parameter_in_macro { - ($e:expr) => { - async { $e.await } - }; -} - -// MISSED OPPORTUNITY: this macro could be linted as the `async` block does not -// contain code coming from the parameters -macro_rules! async_await_in_macro { - ($f:expr) => { - ($f)(async { func2().await }) - }; -} - fn main() { let fut1 = async { 17 }; + // Lint let fut2 = fut1; let fut1 = async { 25 }; + // Lint let fut2 = fut1; + // Lint let fut = async { 42 }; // Do not lint: not a single expression @@ -60,15 +36,12 @@ fn main() { // Do not lint: expression contains `.await` let fut = async { func1(func2().await.len()).await }; - - let fut = async_await_parameter_in_macro!(func2()); - let fut = async_await_in_macro!(std::convert::identity); } #[allow(clippy::let_and_return)] fn capture_local() -> impl Future { - // Lint let fut = async { 17 }; + // Lint fut } @@ -80,11 +53,39 @@ fn capture_local_closure(s: &str) -> impl Future { #[allow(clippy::let_and_return)] fn capture_arg(s: &str) -> impl Future { - // Lint let fut = async move { s }; + // Lint fut } +fn capture_future_arg(f: impl Future) -> impl Future { + // Lint + f +} + +fn capture_func_result(f: FN) -> impl Future +where + F: Future, + FN: FnOnce() -> F, +{ + // Do not lint, as f() would be evaluated prematurely + async { f().await } +} + +fn double_future(f: impl Future>) -> impl Future { + // Do not lint, we will get a `.await` outside a `.async` + async { f.await.await } +} + +fn await_in_async(f: F) -> impl Future +where + F: FnOnce() -> R, + R: Future, +{ + // Lint + async { f().await + 1 } +} + #[derive(Debug, Clone)] struct F {} @@ -109,3 +110,84 @@ fn capture() { // Do not lint: `val` would not live long enough spawn(async { work(&{ val }).await }); } + +fn await_from_macro() -> impl Future { + macro_rules! mac { + ($e:expr) => { + $e.await + }; + } + // Do not lint: the macro may change in the future + // or return different things depending on its argument + async { mac!(async { 42 }) } +} + +fn async_expr_from_macro() -> impl Future { + macro_rules! mac { + () => { + async { 42 } + }; + } + // Do not lint: the macro may change in the future + async { mac!().await } +} + +fn async_expr_from_macro_deep() -> impl Future { + macro_rules! mac { + () => { + async { 42 } + }; + } + // Do not lint: the macro may change in the future + async { ({ mac!() }).await } +} + +fn all_from_macro() -> impl Future { + macro_rules! mac { + () => { + // Lint + async { 42 } + }; + } + mac!() +} + +fn parts_from_macro() -> impl Future { + macro_rules! mac { + ($e: expr) => { + // Do not lint: `$e` might not always be side-effect free + async { $e.await } + }; + } + mac!(async { 42 }) +} + +fn safe_parts_from_macro() -> impl Future { + macro_rules! mac { + ($e: expr) => { + // Lint + async { $e } + }; + } + mac!(42) +} + +fn parts_from_macro_deep() -> impl Future { + macro_rules! mac { + ($e: expr) => { + // Do not lint: `$e` might not always be side-effect free + async { ($e,).0.await } + }; + } + let f = std::future::ready(42); + mac!(f) +} + +fn await_from_macro_deep() -> impl Future { + macro_rules! mac { + ($e:expr) => {{ $e }.await}; + } + // Do not lint: the macro may change in the future + // or return different things depending on its argument + async { mac!(async { 42 }) } +} diff --git a/tests/ui/redundant_async_block.rs b/tests/ui/redundant_async_block.rs index 04726e6280508..7ae235583694d 100644 --- a/tests/ui/redundant_async_block.rs +++ b/tests/ui/redundant_async_block.rs @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused)] +#![allow(unused, clippy::manual_async_fn)] #![warn(clippy::redundant_async_block)] use std::future::Future; @@ -16,40 +16,16 @@ async fn func2() -> String { x.await } -macro_rules! await_in_macro { - ($e:expr) => { - std::convert::identity($e).await - }; -} - -async fn func3(n: usize) -> usize { - // Do not lint (suggestion would be `std::convert::identity(func1(n))` - // which copies code from inside the macro) - async move { await_in_macro!(func1(n)) }.await -} - -// This macro should never be linted as `$e` might contain `.await` -macro_rules! async_await_parameter_in_macro { - ($e:expr) => { - async { $e.await } - }; -} - -// MISSED OPPORTUNITY: this macro could be linted as the `async` block does not -// contain code coming from the parameters -macro_rules! async_await_in_macro { - ($f:expr) => { - ($f)(async { func2().await }) - }; -} - fn main() { let fut1 = async { 17 }; + // Lint let fut2 = async { fut1.await }; let fut1 = async { 25 }; + // Lint let fut2 = async move { fut1.await }; + // Lint let fut = async { async { 42 }.await }; // Do not lint: not a single expression @@ -60,15 +36,12 @@ fn main() { // Do not lint: expression contains `.await` let fut = async { func1(func2().await.len()).await }; - - let fut = async_await_parameter_in_macro!(func2()); - let fut = async_await_in_macro!(std::convert::identity); } #[allow(clippy::let_and_return)] fn capture_local() -> impl Future { - // Lint let fut = async { 17 }; + // Lint async move { fut.await } } @@ -80,11 +53,39 @@ fn capture_local_closure(s: &str) -> impl Future { #[allow(clippy::let_and_return)] fn capture_arg(s: &str) -> impl Future { - // Lint let fut = async move { s }; + // Lint async move { fut.await } } +fn capture_future_arg(f: impl Future) -> impl Future { + // Lint + async { f.await } +} + +fn capture_func_result(f: FN) -> impl Future +where + F: Future, + FN: FnOnce() -> F, +{ + // Do not lint, as f() would be evaluated prematurely + async { f().await } +} + +fn double_future(f: impl Future>) -> impl Future { + // Do not lint, we will get a `.await` outside a `.async` + async { f.await.await } +} + +fn await_in_async(f: F) -> impl Future +where + F: FnOnce() -> R, + R: Future, +{ + // Lint + async { async { f().await + 1 }.await } +} + #[derive(Debug, Clone)] struct F {} @@ -109,3 +110,84 @@ fn capture() { // Do not lint: `val` would not live long enough spawn(async { work(&{ val }).await }); } + +fn await_from_macro() -> impl Future { + macro_rules! mac { + ($e:expr) => { + $e.await + }; + } + // Do not lint: the macro may change in the future + // or return different things depending on its argument + async { mac!(async { 42 }) } +} + +fn async_expr_from_macro() -> impl Future { + macro_rules! mac { + () => { + async { 42 } + }; + } + // Do not lint: the macro may change in the future + async { mac!().await } +} + +fn async_expr_from_macro_deep() -> impl Future { + macro_rules! mac { + () => { + async { 42 } + }; + } + // Do not lint: the macro may change in the future + async { ({ mac!() }).await } +} + +fn all_from_macro() -> impl Future { + macro_rules! mac { + () => { + // Lint + async { async { 42 }.await } + }; + } + mac!() +} + +fn parts_from_macro() -> impl Future { + macro_rules! mac { + ($e: expr) => { + // Do not lint: `$e` might not always be side-effect free + async { $e.await } + }; + } + mac!(async { 42 }) +} + +fn safe_parts_from_macro() -> impl Future { + macro_rules! mac { + ($e: expr) => { + // Lint + async { async { $e }.await } + }; + } + mac!(42) +} + +fn parts_from_macro_deep() -> impl Future { + macro_rules! mac { + ($e: expr) => { + // Do not lint: `$e` might not always be side-effect free + async { ($e,).0.await } + }; + } + let f = std::future::ready(42); + mac!(f) +} + +fn await_from_macro_deep() -> impl Future { + macro_rules! mac { + ($e:expr) => {{ $e }.await}; + } + // Do not lint: the macro may change in the future + // or return different things depending on its argument + async { mac!(async { 42 }) } +} diff --git a/tests/ui/redundant_async_block.stderr b/tests/ui/redundant_async_block.stderr index 1a1c1603e0809..f3dcb09b4440a 100644 --- a/tests/ui/redundant_async_block.stderr +++ b/tests/ui/redundant_async_block.stderr @@ -7,34 +7,68 @@ LL | let x = async { f.await }; = note: `-D clippy::redundant-async-block` implied by `-D warnings` error: this async expression only awaits a single future - --> $DIR/redundant_async_block.rs:48:16 + --> $DIR/redundant_async_block.rs:22:16 | LL | let fut2 = async { fut1.await }; | ^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut1` error: this async expression only awaits a single future - --> $DIR/redundant_async_block.rs:51:16 + --> $DIR/redundant_async_block.rs:26:16 | LL | let fut2 = async move { fut1.await }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut1` error: this async expression only awaits a single future - --> $DIR/redundant_async_block.rs:53:15 + --> $DIR/redundant_async_block.rs:29:15 | LL | let fut = async { async { 42 }.await }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `async { 42 }` error: this async expression only awaits a single future - --> $DIR/redundant_async_block.rs:72:5 + --> $DIR/redundant_async_block.rs:45:5 | LL | async move { fut.await } | ^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut` error: this async expression only awaits a single future - --> $DIR/redundant_async_block.rs:85:5 + --> $DIR/redundant_async_block.rs:58:5 | LL | async move { fut.await } | ^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut` -error: aborting due to 6 previous errors +error: this async expression only awaits a single future + --> $DIR/redundant_async_block.rs:63:5 + | +LL | async { f.await } + | ^^^^^^^^^^^^^^^^^ help: you can reduce it to: `f` + +error: this async expression only awaits a single future + --> $DIR/redundant_async_block.rs:86:5 + | +LL | async { async { f().await + 1 }.await } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `async { f().await + 1 }` + +error: this async expression only awaits a single future + --> $DIR/redundant_async_block.rs:149:13 + | +LL | async { async { 42 }.await } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `async { 42 }` +... +LL | mac!() + | ------ in this macro invocation + | + = note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this async expression only awaits a single future + --> $DIR/redundant_async_block.rs:169:13 + | +LL | async { async { $e }.await } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `async { $e }` +... +LL | mac!(42) + | -------- in this macro invocation + | + = note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 10 previous errors From b499b7dc73f3571e2627a34a93e3079cab848bbc Mon Sep 17 00:00:00 2001 From: feniljain Date: Wed, 5 Apr 2023 19:50:12 +0530 Subject: [PATCH 62/86] test: add test for match as stmt no triggering needless_return --- tests/ui/needless_return.fixed | 9 +++++++++ tests/ui/needless_return.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 38ae448618c02..47a9f031a42cf 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -307,4 +307,13 @@ mod issue10049 { } } +fn test_match_as_stmt() { + let x = 9; + match x { + 1 => 2, + 2 => return, + _ => 0, + }; +} + fn main() {} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index a1db8375d95b9..7c1feefbe32bb 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -317,4 +317,13 @@ mod issue10049 { } } +fn test_match_as_stmt() { + let x = 9; + match x { + 1 => 2, + 2 => return, + _ => 0, + }; +} + fn main() {} From 9cf57d0a8f95c51b0faeda4ce5c5b4cd629f1cae Mon Sep 17 00:00:00 2001 From: feniljain Date: Wed, 5 Apr 2023 20:03:45 +0530 Subject: [PATCH 63/86] fix(needles_return): correct span selection for text replacement --- clippy_lints/src/returns.rs | 2 +- tests/ui/needless_return.fixed | 6 +++--- tests/ui/needless_return.stderr | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index b8172b74dc011..df126d7617ebe 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -286,7 +286,7 @@ fn check_final_expr<'tcx>( ExprKind::Match(_, arms, MatchSource::Normal) => { let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr); for arm in arms.iter() { - check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Empty, Some(match_ty)); + check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty)); } }, // if it's a whole block, check it diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 47a9f031a42cf..57c08996ce25d 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -81,7 +81,7 @@ fn test_void_if_fun(b: bool) { fn test_void_match(x: u32) { match x { 0 => (), - _ =>(), + _ => (), } } @@ -91,7 +91,7 @@ fn test_nested_match(x: u32) { 1 => { let _ = 42; }, - _ =>(), + _ => (), } } @@ -196,7 +196,7 @@ async fn async_test_void_if_fun(b: bool) { async fn async_test_void_match(x: u32) { match x { 0 => (), - _ =>(), + _ => (), } } diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index 5d471a3c52147..87d0cd3e14cfa 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -118,10 +118,10 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:87:13 + --> $DIR/needless_return.rs:87:14 | LL | _ => return, - | ^^^^^^^ + | ^^^^^^ | = help: replace `return` with a unit value @@ -136,10 +136,10 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:98:13 + --> $DIR/needless_return.rs:98:14 | LL | _ => return, - | ^^^^^^^ + | ^^^^^^ | = help: replace `return` with a unit value @@ -296,10 +296,10 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:207:13 + --> $DIR/needless_return.rs:207:14 | LL | _ => return, - | ^^^^^^^ + | ^^^^^^ | = help: replace `return` with a unit value From 008e07d4e8e434933e1ba25a5c324a3a9d46dd7b Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Thu, 6 Apr 2023 16:16:22 +0800 Subject: [PATCH 64/86] fix [mem_replace_option_with_none] not considering field variables --- clippy_lints/src/mem_replace.rs | 71 +++++++++++++-------------------- tests/ui/mem_replace.fixed | 34 ++++++++++++++++ tests/ui/mem_replace.rs | 34 ++++++++++++++++ tests/ui/mem_replace.stderr | 26 +++++++++++- 4 files changed, 121 insertions(+), 44 deletions(-) diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index 35024ec1224f0..8a921d4af1655 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -1,12 +1,13 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{snippet, snippet_with_applicability}; +use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_non_aggregate_primitive_type; -use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res}; +use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res, peel_ref_operators}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -101,40 +102,26 @@ declare_clippy_lint! { impl_lint_pass!(MemReplace => [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]); -fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { - // Check that second argument is `Option::None` - if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) { - // Since this is a late pass (already type-checked), - // and we already know that the second argument is an - // `Option`, we do not need to check the first - // argument's type. All that's left is to get - // replacee's path. - let replaced_path = match dest.kind { - ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, replaced) => { - if let ExprKind::Path(QPath::Resolved(None, replaced_path)) = replaced.kind { - replaced_path - } else { - return; - } - }, - ExprKind::Path(QPath::Resolved(None, replaced_path)) => replaced_path, - _ => return, - }; - - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - MEM_REPLACE_OPTION_WITH_NONE, - expr_span, - "replacing an `Option` with `None`", - "consider `Option::take()` instead", - format!( - "{}.take()", - snippet_with_applicability(cx, replaced_path.span, "", &mut applicability) - ), - applicability, - ); - } +fn check_replace_option_with_none(cx: &LateContext<'_>, dest: &Expr<'_>, expr_span: Span) { + // Since this is a late pass (already type-checked), + // and we already know that the second argument is an + // `Option`, we do not need to check the first + // argument's type. All that's left is to get + // the replacee's expr after peeling off the `&mut` + let sugg_expr = peel_ref_operators(cx, dest); + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MEM_REPLACE_OPTION_WITH_NONE, + expr_span, + "replacing an `Option` with `None`", + "consider `Option::take()` instead", + format!( + "{}.take()", + Sugg::hir_with_context(cx, sugg_expr, expr_span.ctxt(), "", &mut applicability).maybe_par() + ), + applicability, + ); } fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { @@ -200,10 +187,6 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr< if is_non_aggregate_primitive_type(expr_type) { return; } - // disable lint for Option since it is covered in another lint - if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) { - return; - } if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) { span_lint_and_then( cx, @@ -246,11 +229,13 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace { if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id); then { - check_replace_option_with_none(cx, src, dest, expr.span); - check_replace_with_uninit(cx, src, dest, expr.span); - if self.msrv.meets(msrvs::MEM_TAKE) { + // Check that second argument is `Option::None` + if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) { + check_replace_option_with_none(cx, dest, expr.span); + } else if self.msrv.meets(msrvs::MEM_TAKE) { check_replace_with_default(cx, src, dest, expr.span); } + check_replace_with_uninit(cx, src, dest, expr.span); } } } diff --git a/tests/ui/mem_replace.fixed b/tests/ui/mem_replace.fixed index 874d558433034..7fd340173af9e 100644 --- a/tests/ui/mem_replace.fixed +++ b/tests/ui/mem_replace.fixed @@ -90,3 +90,37 @@ fn msrv_1_40() { let mut s = String::from("foo"); let _ = std::mem::take(&mut s); } + +fn issue9824() { + struct Foo<'a>(Option<&'a str>); + impl<'a> std::ops::Deref for Foo<'a> { + type Target = Option<&'a str>; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl<'a> std::ops::DerefMut for Foo<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + struct Bar { + opt: Option, + val: String, + } + + let mut f = Foo(Some("foo")); + let mut b = Bar { + opt: Some(1), + val: String::from("bar"), + }; + + // replace option with none + let _ = f.0.take(); + let _ = (*f).take(); + let _ = b.opt.take(); + // replace with default + let _ = std::mem::take(&mut b.val); +} diff --git a/tests/ui/mem_replace.rs b/tests/ui/mem_replace.rs index f4f3bff514463..fa2903addbc03 100644 --- a/tests/ui/mem_replace.rs +++ b/tests/ui/mem_replace.rs @@ -90,3 +90,37 @@ fn msrv_1_40() { let mut s = String::from("foo"); let _ = std::mem::replace(&mut s, String::default()); } + +fn issue9824() { + struct Foo<'a>(Option<&'a str>); + impl<'a> std::ops::Deref for Foo<'a> { + type Target = Option<&'a str>; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl<'a> std::ops::DerefMut for Foo<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + struct Bar { + opt: Option, + val: String, + } + + let mut f = Foo(Some("foo")); + let mut b = Bar { + opt: Some(1), + val: String::from("bar"), + }; + + // replace option with none + let _ = std::mem::replace(&mut f.0, None); + let _ = std::mem::replace(&mut *f, None); + let _ = std::mem::replace(&mut b.opt, None); + // replace with default + let _ = std::mem::replace(&mut b.val, String::default()); +} diff --git a/tests/ui/mem_replace.stderr b/tests/ui/mem_replace.stderr index caa127f76eeff..58b57be750702 100644 --- a/tests/ui/mem_replace.stderr +++ b/tests/ui/mem_replace.stderr @@ -122,5 +122,29 @@ error: replacing a value of type `T` with `T::default()` is better expressed usi LL | let _ = std::mem::replace(&mut s, String::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(&mut s)` -error: aborting due to 20 previous errors +error: replacing an `Option` with `None` + --> $DIR/mem_replace.rs:121:13 + | +LL | let _ = std::mem::replace(&mut f.0, None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Option::take()` instead: `f.0.take()` + +error: replacing an `Option` with `None` + --> $DIR/mem_replace.rs:122:13 + | +LL | let _ = std::mem::replace(&mut *f, None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Option::take()` instead: `(*f).take()` + +error: replacing an `Option` with `None` + --> $DIR/mem_replace.rs:123:13 + | +LL | let _ = std::mem::replace(&mut b.opt, None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Option::take()` instead: `b.opt.take()` + +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> $DIR/mem_replace.rs:125:13 + | +LL | let _ = std::mem::replace(&mut b.val, String::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(&mut b.val)` + +error: aborting due to 24 previous errors From fa425061e641429cc63fcd02b0c42694cd74dfc6 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 6 Apr 2023 12:32:47 +0200 Subject: [PATCH 65/86] Bump nightly version -> 2023-04-06 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 0b2458ea00708..91e8ccea1f434 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-03-24" +channel = "nightly-2023-04-06" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] From b66aa09b951dace249fad3376c2d97c95d11b6a9 Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Sun, 19 Mar 2023 09:29:32 +0000 Subject: [PATCH 66/86] Add [`manual_slice_size_calculation`] --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + .../src/manual_slice_size_calculation.rs | 90 +++++++++++++++++++ tests/ui/manual_slice_size_calculation.rs | 30 +++++++ tests/ui/manual_slice_size_calculation.stderr | 27 ++++++ 6 files changed, 151 insertions(+) create mode 100644 clippy_lints/src/manual_slice_size_calculation.rs create mode 100644 tests/ui/manual_slice_size_calculation.rs create mode 100644 tests/ui/manual_slice_size_calculation.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index ba10cb53ec929..f615b27bf6883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4674,6 +4674,7 @@ Released 2018-09-13 [`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid [`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic +[`manual_slice_size_calculation`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_size_calculation [`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once [`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat [`manual_string_new`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index b451055703354..09ae6b8ee571e 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -269,6 +269,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO, crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO, crate::manual_retain::MANUAL_RETAIN_INFO, + crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO, crate::manual_string_new::MANUAL_STRING_NEW_INFO, crate::manual_strip::MANUAL_STRIP_INFO, crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 82d63ddca6d7b..f842e629c53c1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -186,6 +186,7 @@ mod manual_main_separator_str; mod manual_non_exhaustive; mod manual_rem_euclid; mod manual_retain; +mod manual_slice_size_calculation; mod manual_string_new; mod manual_strip; mod map_unit_fn; @@ -957,6 +958,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: }); store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk)); store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)); + store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/manual_slice_size_calculation.rs b/clippy_lints/src/manual_slice_size_calculation.rs new file mode 100644 index 0000000000000..2659f347778c2 --- /dev/null +++ b/clippy_lints/src/manual_slice_size_calculation.rs @@ -0,0 +1,90 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::in_constant; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; + +declare_clippy_lint! { + /// ### What it does + /// When `a` is `&[T]`, detect `a.len() * size_of::()` and suggest `size_of_val(a)` + /// instead. + /// + /// ### Why is this better? + /// * Shorter to write + /// * Removes the need for the human and the compiler to worry about overflow in the + /// multiplication + /// * Potentially faster at runtime as rust emits special no-wrapping flags when it + /// calculates the byte length + /// * Less turbofishing + /// + /// ### Example + /// ```rust + /// # let data : &[i32] = &[1, 2, 3]; + /// let newlen = data.len() * std::mem::size_of::(); + /// ``` + /// Use instead: + /// ```rust + /// # let data : &[i32] = &[1, 2, 3]; + /// let newlen = std::mem::size_of_val(data); + /// ``` + #[clippy::version = "1.70.0"] + pub MANUAL_SLICE_SIZE_CALCULATION, + complexity, + "manual slice size calculation" +} +declare_lint_pass!(ManualSliceSizeCalculation => [MANUAL_SLICE_SIZE_CALCULATION]); + +impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + // Does not apply inside const because size_of_value is not cost in stable. + if !in_constant(cx, expr.hir_id) + && let ExprKind::Binary(ref op, left, right) = expr.kind + && BinOpKind::Mul == op.node + && let Some(_receiver) = simplify(cx, left, right) + { + span_lint_and_help( + cx, + MANUAL_SLICE_SIZE_CALCULATION, + expr.span, + "manual slice size calculation", + None, + "consider using std::mem::size_of_value instead"); + } + } +} + +fn simplify<'tcx>( + cx: &LateContext<'tcx>, + expr1: &'tcx Expr<'tcx>, + expr2: &'tcx Expr<'tcx>, +) -> Option<&'tcx Expr<'tcx>> { + simplify_half(cx, expr1, expr2).or_else(|| simplify_half(cx, expr2, expr1)) +} + +fn simplify_half<'tcx>( + cx: &LateContext<'tcx>, + expr1: &'tcx Expr<'tcx>, + expr2: &'tcx Expr<'tcx>, +) -> Option<&'tcx Expr<'tcx>> { + if + // expr1 is `[T1].len()`? + let ExprKind::MethodCall(method_path, receiver, _, _) = expr1.kind + && method_path.ident.name == sym::len + && let receiver_ty = cx.typeck_results().expr_ty(receiver) + && let ty::Slice(ty1) = receiver_ty.peel_refs().kind() + // expr2 is `size_of::()`? + && let ExprKind::Call(func, _) = expr2.kind + && let ExprKind::Path(ref func_qpath) = func.kind + && let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id() + && cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id) + && let Some(ty2) = cx.typeck_results().node_substs(func.hir_id).types().next() + // T1 == T2? + && *ty1 == ty2 + { + Some(receiver) + } else { + None + } +} diff --git a/tests/ui/manual_slice_size_calculation.rs b/tests/ui/manual_slice_size_calculation.rs new file mode 100644 index 0000000000000..8ec4ae0aed736 --- /dev/null +++ b/tests/ui/manual_slice_size_calculation.rs @@ -0,0 +1,30 @@ +#![allow(unused)] +#![warn(clippy::manual_slice_size_calculation)] + +use core::mem::{align_of, size_of}; + +fn main() { + let v_i32 = Vec::::new(); + let s_i32 = v_i32.as_slice(); + + // True positives: + let _ = s_i32.len() * size_of::(); // WARNING + let _ = size_of::() * s_i32.len(); // WARNING + let _ = size_of::() * s_i32.len() * 5; // WARNING + + // True negatives: + let _ = size_of::() + s_i32.len(); // Ok, not a multiplication + let _ = size_of::() * s_i32.partition_point(|_| true); // Ok, not len() + let _ = size_of::() * v_i32.len(); // Ok, not a slice + let _ = align_of::() * s_i32.len(); // Ok, not size_of() + let _ = size_of::() * s_i32.len(); // Ok, different types + + // False negatives: + let _ = 5 * size_of::() * s_i32.len(); // Ok (MISSED OPPORTUNITY) + let _ = size_of::() * 5 * s_i32.len(); // Ok (MISSED OPPORTUNITY) +} + +const fn _const(s_i32: &[i32]) { + // True negative: + let _ = s_i32.len() * size_of::(); // Ok, can't use size_of_val in const +} diff --git a/tests/ui/manual_slice_size_calculation.stderr b/tests/ui/manual_slice_size_calculation.stderr new file mode 100644 index 0000000000000..33de9fad4d381 --- /dev/null +++ b/tests/ui/manual_slice_size_calculation.stderr @@ -0,0 +1,27 @@ +error: manual slice size calculation + --> $DIR/manual_slice_size_calculation.rs:11:13 + | +LL | let _ = s_i32.len() * size_of::(); // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using std::mem::size_of_value instead + = note: `-D clippy::manual-slice-size-calculation` implied by `-D warnings` + +error: manual slice size calculation + --> $DIR/manual_slice_size_calculation.rs:12:13 + | +LL | let _ = size_of::() * s_i32.len(); // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using std::mem::size_of_value instead + +error: manual slice size calculation + --> $DIR/manual_slice_size_calculation.rs:13:13 + | +LL | let _ = size_of::() * s_i32.len() * 5; // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using std::mem::size_of_value instead + +error: aborting due to 3 previous errors + From b47a322ef11e541231b18c67dfe133dac8764b11 Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Thu, 6 Apr 2023 13:45:50 +0100 Subject: [PATCH 67/86] Add tests suggested by @llogiq --- tests/ui/manual_slice_size_calculation.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ui/manual_slice_size_calculation.rs b/tests/ui/manual_slice_size_calculation.rs index 8ec4ae0aed736..2cb8c4c2ad3ee 100644 --- a/tests/ui/manual_slice_size_calculation.rs +++ b/tests/ui/manual_slice_size_calculation.rs @@ -22,6 +22,12 @@ fn main() { // False negatives: let _ = 5 * size_of::() * s_i32.len(); // Ok (MISSED OPPORTUNITY) let _ = size_of::() * 5 * s_i32.len(); // Ok (MISSED OPPORTUNITY) + + let len = s_i32.len(); + let size = size_of::(); + let _ = len * size_of::(); // Ok (MISSED OPPORTUNITY) + let _ = s_i32.len() * size; // Ok (MISSED OPPORTUNITY) + let _ = len * size; // Ok (MISSED OPPORTUNITY) } const fn _const(s_i32: &[i32]) { From cf260bc90f2c2df8cc974fced7336129478fd4ef Mon Sep 17 00:00:00 2001 From: blyxyas Date: Thu, 6 Apr 2023 21:50:49 +0200 Subject: [PATCH 68/86] Fix broken links for Rust merge --- book/src/development/adding_lints.md | 2 +- book/src/usage.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index 3f4088e74eaf7..9dacaaaae5c92 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -24,7 +24,7 @@ because that's clearly a non-descriptive name. - [Lint passes](#lint-passes) - [Emitting a lint](#emitting-a-lint) - [Adding the lint logic](#adding-the-lint-logic) - - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version--msrv-) + - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version-msrv) - [Author lint](#author-lint) - [Print HIR lint](#print-hir-lint) - [Documentation](#documentation) diff --git a/book/src/usage.md b/book/src/usage.md index 32084a9199b73..372a23320d859 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -148,4 +148,4 @@ clippy-driver --edition 2018 -Cpanic=abort foo.rs > that are not optimized as expected, for example. [Installation]: installation.md -[CI]: continuous_integration/index.md +[CI]: continuous_integration/README.md From b1c784d31f3361b3093466d61b8e62c997b1d086 Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Fri, 7 Apr 2023 08:00:53 +0900 Subject: [PATCH 69/86] Fix false negatives by using `expr_or_init` --- .../src/manual_slice_size_calculation.rs | 5 +++- tests/ui/manual_slice_size_calculation.rs | 12 ++++----- tests/ui/manual_slice_size_calculation.stderr | 26 ++++++++++++++++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/manual_slice_size_calculation.rs b/clippy_lints/src/manual_slice_size_calculation.rs index 2659f347778c2..92ee79453a3b8 100644 --- a/clippy_lints/src/manual_slice_size_calculation.rs +++ b/clippy_lints/src/manual_slice_size_calculation.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::in_constant; +use clippy_utils::{expr_or_init, in_constant}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; @@ -60,6 +60,9 @@ fn simplify<'tcx>( expr1: &'tcx Expr<'tcx>, expr2: &'tcx Expr<'tcx>, ) -> Option<&'tcx Expr<'tcx>> { + let expr1 = expr_or_init(cx, expr1); + let expr2 = expr_or_init(cx, expr2); + simplify_half(cx, expr1, expr2).or_else(|| simplify_half(cx, expr2, expr1)) } diff --git a/tests/ui/manual_slice_size_calculation.rs b/tests/ui/manual_slice_size_calculation.rs index 2cb8c4c2ad3ee..5082f931f3c2b 100644 --- a/tests/ui/manual_slice_size_calculation.rs +++ b/tests/ui/manual_slice_size_calculation.rs @@ -12,6 +12,12 @@ fn main() { let _ = size_of::() * s_i32.len(); // WARNING let _ = size_of::() * s_i32.len() * 5; // WARNING + let len = s_i32.len(); + let size = size_of::(); + let _ = len * size_of::(); // WARNING + let _ = s_i32.len() * size; // WARNING + let _ = len * size; // WARNING + // True negatives: let _ = size_of::() + s_i32.len(); // Ok, not a multiplication let _ = size_of::() * s_i32.partition_point(|_| true); // Ok, not len() @@ -22,12 +28,6 @@ fn main() { // False negatives: let _ = 5 * size_of::() * s_i32.len(); // Ok (MISSED OPPORTUNITY) let _ = size_of::() * 5 * s_i32.len(); // Ok (MISSED OPPORTUNITY) - - let len = s_i32.len(); - let size = size_of::(); - let _ = len * size_of::(); // Ok (MISSED OPPORTUNITY) - let _ = s_i32.len() * size; // Ok (MISSED OPPORTUNITY) - let _ = len * size; // Ok (MISSED OPPORTUNITY) } const fn _const(s_i32: &[i32]) { diff --git a/tests/ui/manual_slice_size_calculation.stderr b/tests/ui/manual_slice_size_calculation.stderr index 33de9fad4d381..4a24fc60a0faf 100644 --- a/tests/ui/manual_slice_size_calculation.stderr +++ b/tests/ui/manual_slice_size_calculation.stderr @@ -23,5 +23,29 @@ LL | let _ = size_of::() * s_i32.len() * 5; // WARNING | = help: consider using std::mem::size_of_value instead -error: aborting due to 3 previous errors +error: manual slice size calculation + --> $DIR/manual_slice_size_calculation.rs:17:13 + | +LL | let _ = len * size_of::(); // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using std::mem::size_of_value instead + +error: manual slice size calculation + --> $DIR/manual_slice_size_calculation.rs:18:13 + | +LL | let _ = s_i32.len() * size; // WARNING + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using std::mem::size_of_value instead + +error: manual slice size calculation + --> $DIR/manual_slice_size_calculation.rs:19:13 + | +LL | let _ = len * size; // WARNING + | ^^^^^^^^^^ + | + = help: consider using std::mem::size_of_value instead + +error: aborting due to 6 previous errors From 5d01e6e96cc72694529a32c42459b20ddf665507 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Mon, 13 Mar 2023 13:06:31 +0100 Subject: [PATCH 70/86] new lint: suspicious_doc_comments --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/suspicious_doc_comments.rs | 94 +++++++++++++++ tests/ui/suspicious_doc_comments.fixed | 81 +++++++++++++ tests/ui/suspicious_doc_comments.rs | 81 +++++++++++++ tests/ui/suspicious_doc_comments.stderr | 114 ++++++++++++++++++ tests/ui/suspicious_doc_comments_unfixable.rs | 16 +++ .../suspicious_doc_comments_unfixable.stderr | 37 ++++++ 9 files changed, 427 insertions(+) create mode 100644 clippy_lints/src/suspicious_doc_comments.rs create mode 100644 tests/ui/suspicious_doc_comments.fixed create mode 100644 tests/ui/suspicious_doc_comments.rs create mode 100644 tests/ui/suspicious_doc_comments.stderr create mode 100644 tests/ui/suspicious_doc_comments_unfixable.rs create mode 100644 tests/ui/suspicious_doc_comments_unfixable.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index f615b27bf6883..559b560dde4ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4925,6 +4925,7 @@ Released 2018-09-13 [`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl [`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting [`suspicious_command_arg_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_command_arg_space +[`suspicious_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_doc_comments [`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting [`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map [`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 09ae6b8ee571e..f24dab6278095 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -569,6 +569,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::strings::STR_TO_STRING_INFO, crate::strings::TRIM_SPLIT_WHITESPACE_INFO, crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO, + crate::suspicious_doc_comments::SUSPICIOUS_DOC_COMMENTS_INFO, crate::suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO, crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO, crate::suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 2a0f219331e25..bac82eca81748 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -284,6 +284,7 @@ mod slow_vector_initialization; mod std_instead_of_core; mod strings; mod strlen_on_c_strings; +mod suspicious_doc_comments; mod suspicious_operation_groupings; mod suspicious_trait_impl; mod suspicious_xor_used_as_pow; @@ -958,6 +959,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk)); store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)); store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation)); + store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/suspicious_doc_comments.rs b/clippy_lints/src/suspicious_doc_comments.rs new file mode 100644 index 0000000000000..e5746ca99cac1 --- /dev/null +++ b/clippy_lints/src/suspicious_doc_comments.rs @@ -0,0 +1,94 @@ +use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_then}; +use if_chain::if_chain; +use rustc_ast::{token::CommentKind, AttrKind, AttrStyle, Attribute, Item}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Detects the use of outer doc comments (`///`, `/**`) followed by a bang (`!`): `///!` + /// + /// ### Why is this bad? + /// Triple-slash comments (known as "outer doc comments") apply to items that follow it. + /// An outer doc comment followed by a bang (i.e. `///!`) has no specific meaning. + /// + /// The user most likely meant to write an inner doc comment (`//!`, `/*!`), which + /// applies to the parent item (i.e. the item that the comment is contained in, + /// usually a module or crate). + /// + /// ### Known problems + /// Inner doc comments can only appear before items, so there are certain cases where the suggestion + /// made by this lint is not valid code. For example: + /// ```rs + /// fn foo() {} + /// ///! + /// fn bar() {} + /// ``` + /// This lint detects the doc comment and suggests changing it to `//!`, but an inner doc comment + /// is not valid at that position. + /// + /// ### Example + /// In this example, the doc comment is attached to the *function*, rather than the *module*. + /// ```rust + /// pub mod util { + /// ///! This module contains utility functions. + /// + /// pub fn dummy() {} + /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// pub mod util { + /// //! This module contains utility functions. + /// + /// pub fn dummy() {} + /// } + /// ``` + #[clippy::version = "1.70.0"] + pub SUSPICIOUS_DOC_COMMENTS, + suspicious, + "suspicious usage of (outer) doc comments" +} +declare_lint_pass!(SuspiciousDocComments => [SUSPICIOUS_DOC_COMMENTS]); + +const WARNING: &str = "this is an outer doc comment and does not apply to the parent module or crate"; +const HELP: &str = "use an inner doc comment to document the parent module or crate"; + +impl EarlyLintPass for SuspiciousDocComments { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + let replacements = collect_doc_comment_replacements(&item.attrs); + + if let Some(((lo_span, _), (hi_span, _))) = replacements.first().zip(replacements.last()) { + let span = lo_span.to(*hi_span); + + span_lint_and_then(cx, SUSPICIOUS_DOC_COMMENTS, span, WARNING, |diag| { + multispan_sugg_with_applicability(diag, HELP, Applicability::MaybeIncorrect, replacements); + }); + } + } +} + +fn collect_doc_comment_replacements(attrs: &[Attribute]) -> Vec<(Span, String)> { + attrs + .iter() + .filter_map(|attr| { + if_chain! { + if let AttrKind::DocComment(com_kind, sym) = attr.kind; + if let AttrStyle::Outer = attr.style; + if let Some(com) = sym.as_str().strip_prefix('!'); + then { + let sugg = match com_kind { + CommentKind::Line => format!("//!{com}"), + CommentKind::Block => format!("/*!{com}*/") + }; + Some((attr.span, sugg)) + } else { + None + } + } + }) + .collect() +} diff --git a/tests/ui/suspicious_doc_comments.fixed b/tests/ui/suspicious_doc_comments.fixed new file mode 100644 index 0000000000000..b404df94d3c29 --- /dev/null +++ b/tests/ui/suspicious_doc_comments.fixed @@ -0,0 +1,81 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::suspicious_doc_comments)] + +//! Real module documentation. +//! Fake module documentation. +fn baz() {} + +pub mod singleline_outer_doc { + //! This module contains useful functions. + + pub fn bar() {} +} + +pub mod singleline_inner_doc { + //! This module contains useful functions. + + pub fn bar() {} +} + +pub mod multiline_outer_doc { + /*! This module contains useful functions. + */ + + pub fn bar() {} +} + +pub mod multiline_inner_doc { + /*! This module contains useful functions. + */ + + pub fn bar() {} +} + +pub mod multiline_outer_doc2 { + //! This module + //! contains + //! useful functions. + + pub fn bar() {} +} + +pub mod multiline_outer_doc3 { + //! a + //! b + + /// c + pub fn bar() {} +} + +pub mod multiline_outer_doc4 { + //! a + /// b + pub fn bar() {} +} + +pub mod multiline_outer_doc_gap { + //! a + + //! b + pub fn bar() {} +} + +pub mod multiline_outer_doc_commented { + /////! This outer doc comment was commented out. + pub fn bar() {} +} + +pub mod outer_doc_macro { + //! Very cool macro + macro_rules! x { + () => {}; + } +} + +pub mod useless_outer_doc { + //! Huh. + use std::mem; +} + +fn main() {} diff --git a/tests/ui/suspicious_doc_comments.rs b/tests/ui/suspicious_doc_comments.rs new file mode 100644 index 0000000000000..46eff51e220f5 --- /dev/null +++ b/tests/ui/suspicious_doc_comments.rs @@ -0,0 +1,81 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::suspicious_doc_comments)] + +//! Real module documentation. +///! Fake module documentation. +fn baz() {} + +pub mod singleline_outer_doc { + ///! This module contains useful functions. + + pub fn bar() {} +} + +pub mod singleline_inner_doc { + //! This module contains useful functions. + + pub fn bar() {} +} + +pub mod multiline_outer_doc { + /**! This module contains useful functions. + */ + + pub fn bar() {} +} + +pub mod multiline_inner_doc { + /*! This module contains useful functions. + */ + + pub fn bar() {} +} + +pub mod multiline_outer_doc2 { + ///! This module + ///! contains + ///! useful functions. + + pub fn bar() {} +} + +pub mod multiline_outer_doc3 { + ///! a + ///! b + + /// c + pub fn bar() {} +} + +pub mod multiline_outer_doc4 { + ///! a + /// b + pub fn bar() {} +} + +pub mod multiline_outer_doc_gap { + ///! a + + ///! b + pub fn bar() {} +} + +pub mod multiline_outer_doc_commented { + /////! This outer doc comment was commented out. + pub fn bar() {} +} + +pub mod outer_doc_macro { + ///! Very cool macro + macro_rules! x { + () => {}; + } +} + +pub mod useless_outer_doc { + ///! Huh. + use std::mem; +} + +fn main() {} diff --git a/tests/ui/suspicious_doc_comments.stderr b/tests/ui/suspicious_doc_comments.stderr new file mode 100644 index 0000000000000..6c167df27873c --- /dev/null +++ b/tests/ui/suspicious_doc_comments.stderr @@ -0,0 +1,114 @@ +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments.rs:6:1 + | +LL | ///! Fake module documentation. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::suspicious-doc-comments` implied by `-D warnings` +help: use an inner doc comment to document the parent module or crate + | +LL | //! Fake module documentation. + | + +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments.rs:10:5 + | +LL | ///! This module contains useful functions. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use an inner doc comment to document the parent module or crate + | +LL | //! This module contains useful functions. + | + +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments.rs:22:5 + | +LL | / /**! This module contains useful functions. +LL | | */ + | |_______^ + | +help: use an inner doc comment to document the parent module or crate + | +LL ~ /*! This module contains useful functions. +LL + */ + | + +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments.rs:36:5 + | +LL | / ///! This module +LL | | ///! contains +LL | | ///! useful functions. + | |__________________________^ + | +help: use an inner doc comment to document the parent module or crate + | +LL ~ //! This module +LL ~ //! contains +LL ~ //! useful functions. + | + +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments.rs:44:5 + | +LL | / ///! a +LL | | ///! b + | |__________^ + | +help: use an inner doc comment to document the parent module or crate + | +LL ~ //! a +LL ~ //! b + | + +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments.rs:52:5 + | +LL | ///! a + | ^^^^^^ + | +help: use an inner doc comment to document the parent module or crate + | +LL | //! a + | + +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments.rs:58:5 + | +LL | / ///! a +LL | | +LL | | ///! b + | |__________^ + | +help: use an inner doc comment to document the parent module or crate + | +LL ~ //! a +LL | +LL ~ //! b + | + +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments.rs:70:5 + | +LL | ///! Very cool macro + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use an inner doc comment to document the parent module or crate + | +LL | //! Very cool macro + | + +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments.rs:77:5 + | +LL | ///! Huh. + | ^^^^^^^^^ + | +help: use an inner doc comment to document the parent module or crate + | +LL | //! Huh. + | + +error: aborting due to 9 previous errors + diff --git a/tests/ui/suspicious_doc_comments_unfixable.rs b/tests/ui/suspicious_doc_comments_unfixable.rs new file mode 100644 index 0000000000000..ad98c7f4966ff --- /dev/null +++ b/tests/ui/suspicious_doc_comments_unfixable.rs @@ -0,0 +1,16 @@ +#![allow(unused)] +#![warn(clippy::suspicious_doc_comments)] + +///! a +///! b +/// c +///! d +pub fn foo() {} + +///! a +///! b +/// c +///! d +use std::mem; + +fn main() {} diff --git a/tests/ui/suspicious_doc_comments_unfixable.stderr b/tests/ui/suspicious_doc_comments_unfixable.stderr new file mode 100644 index 0000000000000..f89146dad36e0 --- /dev/null +++ b/tests/ui/suspicious_doc_comments_unfixable.stderr @@ -0,0 +1,37 @@ +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments_unfixable.rs:4:1 + | +LL | / ///! a +LL | | ///! b +LL | | /// c +LL | | ///! d + | |______^ + | + = note: `-D clippy::suspicious-doc-comments` implied by `-D warnings` +help: use an inner doc comment to document the parent module or crate + | +LL + //! a +LL + //! b +LL | /// c +LL + //! d + | + +error: this is an outer doc comment and does not apply to the parent module or crate + --> $DIR/suspicious_doc_comments_unfixable.rs:10:1 + | +LL | / ///! a +LL | | ///! b +LL | | /// c +LL | | ///! d + | |______^ + | +help: use an inner doc comment to document the parent module or crate + | +LL + //! a +LL + //! b +LL | /// c +LL + //! d + | + +error: aborting due to 2 previous errors + From 90e6ddc8e77eadf5cc83bc94a1d81dfb51c64371 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Fri, 7 Apr 2023 13:08:29 +0200 Subject: [PATCH 71/86] New chapter: "Type Checking" --- book/src/SUMMARY.md | 1 + book/src/development/type_checking.md | 126 ++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 book/src/development/type_checking.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 0649f7a631df4..cbd73376dfa07 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -13,6 +13,7 @@ - [Development](development/README.md) - [Basics](development/basics.md) - [Adding Lints](development/adding_lints.md) + - [Type Checking](development/type_checking.md) - [Common Tools](development/common_tools_writing_lints.md) - [Infrastructure](development/infrastructure/README.md) - [Syncing changes between Clippy and rust-lang/rust](development/infrastructure/sync.md) diff --git a/book/src/development/type_checking.md b/book/src/development/type_checking.md new file mode 100644 index 0000000000000..ff08cb1e52400 --- /dev/null +++ b/book/src/development/type_checking.md @@ -0,0 +1,126 @@ +# Type Checking + +When we work on a new lint or improve an existing lint, we might want +to retrieve the type `Ty` of an expression `Expr` for a variety of +reasons. This can be achieved by utilizing the [`LateContext`][LateContext] +that is available for [`LateLintPass`][LateLintPass]. + +## `LateContext` and `TypeckResults` + +The lint context [`LateContext`][LateContext] and [`TypeckResults`][TypeckResults] +(returned by `LateContext::typeck_results`) are the two most useful data structures +in `LateLintPass`. They allow us to jump to type definitions and other compilation +stages such as HIR. + +> Note: `LateContext.typeck_results`'s return value is [`TypeckResults`][TypeckResults] +> and is created in the type checking step, it includes useful information such as types of +> expressions, ways to resolve methods and so on. + +`TypeckResults` contains useful methods such as [`expr_ty`][expr_ty], +which gives us access to the underlying structure [`Ty`][Ty] of a given expression. + +```rust +pub fn expr_ty(&self, expr: &Expr<'_>) -> Ty<'tcx> +``` + +As a side note, besides `expr_ty`, [`TypeckResults`][TypeckResults] contains a +[`pat_ty()`][pat_ty] method that is useful for retrieving a type from a pattern. + +## `Ty` + +`Ty` struct contains the type information of an expression. +Let's take a look at `rustc_middle`'s [`Ty`][Ty] struct to examine this struct: + +```rust +pub struct Ty<'tcx>(Interned<'tcx, WithStableHash>>); +``` + +At a first glance, this struct looks quite esoteric. But at a closer look, +we will see that this struct contains many useful methods for type checking. + +For instance, [`is_char`][is_char] checks if the given `Ty` struct corresponds +to the primitive character type. + +### `is_*` Usage + +In some scenarios, all we need to do is check if the `Ty` of an expression +is a specific type, such as `char` type, so we could write the following: + +```rust +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // Get type of `expr` + let ty = cx.typeck_results().expr_ty(expr); + + // Check if the `Ty` of this expression is of character type + if ty.is_char() { + println!("Our expression is a char!"); + } + } +} +``` + +Furthermore, if we examine the [source code][is_char_source] for `is_char`, +we find something very interesting: + +```rust +#[inline] +pub fn is_char(self) -> bool { + matches!(self.kind(), Char) +} +``` + +Indeed, we just discovered `Ty`'s [`kind` method][kind], which provides us +with [`TyKind`][TyKind] of a `Ty`. + +## `TyKind` + +`TyKind` defines the kinds of types in Rust's type system. +Peeking into [`TyKind` documentation][TyKind], we will see that it is an +enum of 27 variants, including items such as `Bool`, `Int`, `Ref`, etc. + +### `kind` Usage + +The `TyKind` of `Ty` can be returned by calling [`Ty.kind` method][kind]. +We often use this method to perform pattern matching in Clippy. + +For instance, if we want to check for a `struct`, we could examine if the +`ty.kind` corresponds to an [`Adt`][Adt] (algebraic data type) and if its +[`AdtDef`][AdtDef] is a struct: + +```rust +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // Get type of `expr` + let ty = cx.typeck_results().expr_ty(expr); + // Match its kind to enter the type + match ty.kind { + ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"), + _ => () + } + } +} +``` + +## Useful Links + +Below are some useful links to further explore the concepts covered +in this chapter: + +- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation) +- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html) +- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) +- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) + +[Adt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html#variant.Adt +[AdtDef]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/adt/struct.AdtDef.html +[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty +[is_char]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.is_char +[is_char_source]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_middle/ty/sty.rs.html#1831-1834 +[kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.kind +[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html +[LateLintPass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html +[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty +[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html +[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html +[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html From 9ec542873cde1332baf2a0ed33088ba98eba25cf Mon Sep 17 00:00:00 2001 From: bluthej Date: Thu, 6 Apr 2023 07:49:59 +0200 Subject: [PATCH 72/86] Add 5 other container types and start testing --- clippy_lints/src/methods/clear_with_drain.rs | 33 +++++++++++++---- clippy_lints/src/methods/mod.rs | 11 +++--- tests/ui/clear_with_drain.fixed | 25 ++++++++++++- tests/ui/clear_with_drain.rs | 25 ++++++++++++- tests/ui/clear_with_drain.stderr | 38 ++++++++++++++++---- 5 files changed, 112 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints/src/methods/clear_with_drain.rs index 24496bd4689f5..4f5b7762e3c7c 100644 --- a/clippy_lints/src/methods/clear_with_drain.rs +++ b/clippy_lints/src/methods/clear_with_drain.rs @@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_range_full; use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; +use rustc_hir as hir; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -9,17 +10,37 @@ use rustc_span::Span; use super::CLEAR_WITH_DRAIN; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) { - let ty = cx.typeck_results().expr_ty(recv); - if is_type_diagnostic_item(cx, ty, sym::Vec) - && let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind - && is_range_full(cx, arg, Some(container_path)) +const ACCEPTABLE_TYPES_WITH_ARG: [rustc_span::Symbol; 3] = [sym::String, sym::Vec, sym::VecDeque]; + +const ACCEPTABLE_TYPES_WITHOUT_ARG: [rustc_span::Symbol; 3] = [sym::BinaryHeap, sym::HashMap, sym::HashSet]; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: Option<&Expr<'_>>) { + if let Some(arg) = arg { + if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITH_ARG) + && let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind + && is_range_full(cx, arg, Some(container_path)) + { + suggest(cx, expr, recv, span); + } + } else if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITHOUT_ARG) { + suggest(cx, expr, recv, span); + } +} + +fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, types: &[rustc_span::Symbol]) -> bool { + let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs(); + types.iter().any(|&ty| is_type_diagnostic_item(cx, expr_ty, ty)) +} + +fn suggest(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span) { + if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def() + && let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did()) { span_lint_and_sugg( cx, CLEAR_WITH_DRAIN, span.with_hi(expr.span.hi()), - "`drain` used to clear a `Vec`", + &format!("`drain` used to clear a `{}`", ty_name), "try", "clear()".to_string(), Applicability::MachineApplicable, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 257bc4eccc302..c25f1b60aa5f6 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3589,12 +3589,13 @@ impl Methods { Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2), _ => {}, }, - ("drain", [arg]) => { - if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.hir().get_parent(expr.hir_id) - && matches!(kind, StmtKind::Semi(_)) + ("drain", ..) => { + if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.hir().get_parent(expr.hir_id) + && matches!(kind, StmtKind::Semi(_)) + && args.len() <= 1 { - clear_with_drain::check(cx, expr, recv, span, arg); - } else { + clear_with_drain::check(cx, expr, recv, span, args.first()); + } else if let [arg] = args { iter_with_drain::check(cx, expr, recv, span, arg); } }, diff --git a/tests/ui/clear_with_drain.fixed b/tests/ui/clear_with_drain.fixed index 9c4dc010ca7fb..dd02cc9148f76 100644 --- a/tests/ui/clear_with_drain.fixed +++ b/tests/ui/clear_with_drain.fixed @@ -2,6 +2,11 @@ #![allow(unused)] #![warn(clippy::clear_with_drain)] +use std::collections::BinaryHeap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; + fn range() { let mut v = vec![1, 2, 3]; let iter = v.drain(0..v.len()); // Yay @@ -83,4 +88,22 @@ fn partial_drains() { let w: Vec = v.drain(1..v.len() - 1).collect(); // Yay } -fn main() {} +fn main() { + let mut deque: VecDeque<_> = [1, 2, 3].into(); + deque.clear(); + + let mut set = HashSet::from([1, 2, 3]); + set.clear(); + + let mut a = HashMap::new(); + a.insert(1, "a"); + a.insert(2, "b"); + a.clear(); + + let mut heap = BinaryHeap::from([1, 3]); + heap.clear(); + + // Not firing for now because `String` is not reckognized by `is_type_diagnostic_item` + let mut s = String::from("α is alpha, β is beta"); + s.drain(..); +} diff --git a/tests/ui/clear_with_drain.rs b/tests/ui/clear_with_drain.rs index f00dbab234cc8..af2fe503d9a60 100644 --- a/tests/ui/clear_with_drain.rs +++ b/tests/ui/clear_with_drain.rs @@ -2,6 +2,11 @@ #![allow(unused)] #![warn(clippy::clear_with_drain)] +use std::collections::BinaryHeap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; + fn range() { let mut v = vec![1, 2, 3]; let iter = v.drain(0..v.len()); // Yay @@ -83,4 +88,22 @@ fn partial_drains() { let w: Vec = v.drain(1..v.len() - 1).collect(); // Yay } -fn main() {} +fn main() { + let mut deque: VecDeque<_> = [1, 2, 3].into(); + deque.drain(..); + + let mut set = HashSet::from([1, 2, 3]); + set.drain(); + + let mut a = HashMap::new(); + a.insert(1, "a"); + a.insert(2, "b"); + a.drain(); + + let mut heap = BinaryHeap::from([1, 3]); + heap.drain(); + + // Not firing for now because `String` is not reckognized by `is_type_diagnostic_item` + let mut s = String::from("α is alpha, β is beta"); + s.drain(..); +} diff --git a/tests/ui/clear_with_drain.stderr b/tests/ui/clear_with_drain.stderr index c88aa1a23cb65..2c0cc846de2d1 100644 --- a/tests/ui/clear_with_drain.stderr +++ b/tests/ui/clear_with_drain.stderr @@ -1,5 +1,5 @@ error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:17:7 + --> $DIR/clear_with_drain.rs:22:7 | LL | v.drain(0..v.len()); // Nay | ^^^^^^^^^^^^^^^^^ help: try: `clear()` @@ -7,34 +7,58 @@ LL | v.drain(0..v.len()); // Nay = note: `-D clippy::clear-with-drain` implied by `-D warnings` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:20:7 + --> $DIR/clear_with_drain.rs:25:7 | LL | v.drain(usize::MIN..v.len()); // Nay | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:35:7 + --> $DIR/clear_with_drain.rs:40:7 | LL | v.drain(0..); // Nay | ^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:38:7 + --> $DIR/clear_with_drain.rs:43:7 | LL | v.drain(usize::MIN..); // Nay | ^^^^^^^^^^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:52:7 + --> $DIR/clear_with_drain.rs:57:7 | LL | v.drain(..); // Nay | ^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:66:7 + --> $DIR/clear_with_drain.rs:71:7 | LL | v.drain(..v.len()); // Nay | ^^^^^^^^^^^^^^^^ help: try: `clear()` -error: aborting due to 6 previous errors +error: `drain` used to clear a `VecDeque` + --> $DIR/clear_with_drain.rs:93:11 + | +LL | deque.drain(..); + | ^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `HashSet` + --> $DIR/clear_with_drain.rs:96:9 + | +LL | set.drain(); + | ^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `HashMap` + --> $DIR/clear_with_drain.rs:101:7 + | +LL | a.drain(); + | ^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `BinaryHeap` + --> $DIR/clear_with_drain.rs:104:10 + | +LL | heap.drain(); + | ^^^^^^^ help: try: `clear()` + +error: aborting due to 10 previous errors From 4905a2130ad0866761ccf99b4d1a134517b7f4ab Mon Sep 17 00:00:00 2001 From: beetrees Date: Sat, 8 Apr 2023 15:03:34 +0100 Subject: [PATCH 73/86] Add `max_line_length` to `.editorconfig`, matching `rustfmt.toml` --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index ec6e107d547f0..bc7642bf8c7c9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,6 +11,7 @@ trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 +max_line_length = 120 [*.md] # double whitespace at end of line From 1cf64060e37f1cafca05beb61113f82635da08b4 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Sat, 8 Apr 2023 17:08:06 +0200 Subject: [PATCH 74/86] Add "`hir::Ty` and `ty::Ty`" section --- book/src/development/type_checking.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/book/src/development/type_checking.md b/book/src/development/type_checking.md index ff08cb1e52400..5ce434b99a1a6 100644 --- a/book/src/development/type_checking.md +++ b/book/src/development/type_checking.md @@ -102,6 +102,21 @@ impl LateLintPass<'_> for MyStructLint { } ``` +## `hir::Ty` and `ty::Ty` + +We've been talking about [`ty::Ty`][middle_ty] this whole time without addressing [`hir::Ty`][hir_ty], but the latter +is also important to understand. + +`hir::Ty` would represent *what* an user wrote, while `ty::Ty` would understand the meaning of it (because it has more +information). + +**Example: `fn foo(x: u32) -> u32 { x }`** + +Here the HIR sees the types without "thinking" about them, it knows that the function takes an `u32` and returns +an `u32`. But at the `ty::Ty` level the compiler understands that they're the same type, in-depth lifetimes, etc... + +you can use the [`hir_ty_to_ty`][hir_ty_to_ty] function to convert from a `hir::Ty` to a `ty::Ty` + ## Useful Links Below are some useful links to further explore the concepts covered @@ -124,3 +139,6 @@ in this chapter: [Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html +[middle_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/ty/struct.Ty.html +[hir_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/struct.Ty.html +[hir_ty_to_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir_analysis/fn.hir_ty_to_ty.html From 5b57e5cec84a9c3c2f6240c9f0f4071f391fe6ab Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 9 Apr 2023 12:20:59 +0200 Subject: [PATCH 75/86] Fix false negative with `String` and add tests `String` is currently not a diagnostic item so it needs special treatment --- clippy_lints/src/methods/clear_with_drain.rs | 12 +- tests/ui/clear_with_drain.fixed | 333 ++++++++++++++++--- tests/ui/clear_with_drain.rs | 333 ++++++++++++++++--- tests/ui/clear_with_drain.stderr | 104 ++++-- 4 files changed, 675 insertions(+), 107 deletions(-) diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints/src/methods/clear_with_drain.rs index 4f5b7762e3c7c..6ce3fdae40749 100644 --- a/clippy_lints/src/methods/clear_with_drain.rs +++ b/clippy_lints/src/methods/clear_with_drain.rs @@ -1,16 +1,17 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_range_full; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_hir::{Expr, ExprKind, LangItem, QPath}; use rustc_lint::LateContext; use rustc_span::symbol::sym; use rustc_span::Span; use super::CLEAR_WITH_DRAIN; -const ACCEPTABLE_TYPES_WITH_ARG: [rustc_span::Symbol; 3] = [sym::String, sym::Vec, sym::VecDeque]; +// Add `String` here when it is added to diagnostic items +const ACCEPTABLE_TYPES_WITH_ARG: [rustc_span::Symbol; 2] = [sym::Vec, sym::VecDeque]; const ACCEPTABLE_TYPES_WITHOUT_ARG: [rustc_span::Symbol; 3] = [sym::BinaryHeap, sym::HashMap, sym::HashSet]; @@ -30,11 +31,14 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, types: &[rustc_span::Symbol]) -> bool { let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs(); types.iter().any(|&ty| is_type_diagnostic_item(cx, expr_ty, ty)) + // String type is a lang item but not a diagnostic item for now so we need a separate check + || is_type_lang_item(cx, expr_ty, LangItem::String) } fn suggest(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span) { if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def() - && let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did()) + // Use `opt_item_name` while `String` is not a diagnostic item + && let Some(ty_name) = cx.tcx.opt_item_name(adt.did()) { span_lint_and_sugg( cx, diff --git a/tests/ui/clear_with_drain.fixed b/tests/ui/clear_with_drain.fixed index dd02cc9148f76..2d9545eeed197 100644 --- a/tests/ui/clear_with_drain.fixed +++ b/tests/ui/clear_with_drain.fixed @@ -2,108 +2,357 @@ #![allow(unused)] #![warn(clippy::clear_with_drain)] -use std::collections::BinaryHeap; -use std::collections::HashMap; -use std::collections::HashSet; -use std::collections::VecDeque; +use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; -fn range() { +fn vec_range() { + // Do not lint because iterator is assigned let mut v = vec![1, 2, 3]; - let iter = v.drain(0..v.len()); // Yay + let iter = v.drain(0..v.len()); + // Do not lint because iterator is used let mut v = vec![1, 2, 3]; - let n = v.drain(0..v.len()).count(); // Yay + let n = v.drain(0..v.len()).count(); + // Do not lint because iterator is assigned and used let mut v = vec![1, 2, 3]; - let iter = v.drain(usize::MIN..v.len()); // Yay + let iter = v.drain(usize::MIN..v.len()); let n = iter.count(); + // Do lint let mut v = vec![1, 2, 3]; - v.clear(); // Nay + v.clear(); + // Do lint let mut v = vec![1, 2, 3]; - v.clear(); // Nay + v.clear(); } -fn range_from() { +fn vec_range_from() { + // Do not lint because iterator is assigned let mut v = vec![1, 2, 3]; - let iter = v.drain(0..); // Yay + let iter = v.drain(0..); + // Do not lint because iterator is assigned and used let mut v = vec![1, 2, 3]; - let mut iter = v.drain(0..); // Yay + let mut iter = v.drain(0..); let next = iter.next(); + // Do not lint because iterator is used let mut v = vec![1, 2, 3]; - let next = v.drain(usize::MIN..).next(); // Yay + let next = v.drain(usize::MIN..).next(); + // Do lint let mut v = vec![1, 2, 3]; - v.clear(); // Nay + v.clear(); + // Do lint let mut v = vec![1, 2, 3]; - v.clear(); // Nay + v.clear(); } -fn range_full() { +fn vec_range_full() { + // Do not lint because iterator is assigned let mut v = vec![1, 2, 3]; - let iter = v.drain(..); // Yay + let iter = v.drain(..); + // Do not lint because iterator is used let mut v = vec![1, 2, 3]; - // Yay for x in v.drain(..) { let y = format!("x = {x}"); } + // Do lint let mut v = vec![1, 2, 3]; - v.clear(); // Nay + v.clear(); } -fn range_to() { +fn vec_range_to() { + // Do not lint because iterator is assigned let mut v = vec![1, 2, 3]; - let iter = v.drain(..v.len()); // Yay + let iter = v.drain(..v.len()); + // Do not lint because iterator is assigned and used let mut v = vec![1, 2, 3]; - let iter = v.drain(..v.len()); // Yay + let iter = v.drain(..v.len()); for x in iter { let y = format!("x = {x}"); } + // Do lint let mut v = vec![1, 2, 3]; - v.clear(); // Nay + v.clear(); } -fn partial_drains() { +fn vec_partial_drains() { + // Do not lint any of these because the ranges are not full + let mut v = vec![1, 2, 3]; - v.drain(1..); // Yay + v.drain(1..); let mut v = vec![1, 2, 3]; - v.drain(1..).max(); // Yay + v.drain(1..).max(); let mut v = vec![1, 2, 3]; - v.drain(..v.len() - 1); // Yay + v.drain(..v.len() - 1); let mut v = vec![1, 2, 3]; - v.drain(..v.len() - 1).min(); // Yay + v.drain(..v.len() - 1).min(); let mut v = vec![1, 2, 3]; - v.drain(1..v.len() - 1); // Yay + v.drain(1..v.len() - 1); let mut v = vec![1, 2, 3]; - let w: Vec = v.drain(1..v.len() - 1).collect(); // Yay + let w: Vec = v.drain(1..v.len() - 1).collect(); +} + +fn vec_deque_range() { + // Do not lint because iterator is assigned + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(0..deque.len()); + + // Do not lint because iterator is used + let mut deque = VecDeque::from([1, 2, 3]); + let n = deque.drain(0..deque.len()).count(); + + // Do not lint because iterator is assigned and used + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(usize::MIN..deque.len()); + let n = iter.count(); + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.clear(); + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.clear(); +} + +fn vec_deque_range_from() { + // Do not lint because iterator is assigned + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(0..); + + // Do not lint because iterator is assigned and used + let mut deque = VecDeque::from([1, 2, 3]); + let mut iter = deque.drain(0..); + let next = iter.next(); + + // Do not lint because iterator is used + let mut deque = VecDeque::from([1, 2, 3]); + let next = deque.drain(usize::MIN..).next(); + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.clear(); + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.clear(); +} + +fn vec_deque_range_full() { + // Do not lint because iterator is assigned + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(..); + + // Do not lint because iterator is used + let mut deque = VecDeque::from([1, 2, 3]); + for x in deque.drain(..) { + let y = format!("x = {x}"); + } + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.clear(); } -fn main() { - let mut deque: VecDeque<_> = [1, 2, 3].into(); +fn vec_deque_range_to() { + // Do not lint because iterator is assigned + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(..deque.len()); + + // Do not lint because iterator is assigned and used + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(..deque.len()); + for x in iter { + let y = format!("x = {x}"); + } + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); deque.clear(); +} + +fn vec_deque_partial_drains() { + // Do not lint any of these because the ranges are not full + + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(1..); + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(1..).max(); + + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(..deque.len() - 1); + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(..deque.len() - 1).min(); + + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(1..deque.len() - 1); + let mut deque = VecDeque::from([1, 2, 3]); + let w: Vec = deque.drain(1..deque.len() - 1).collect(); +} + +fn string_range() { + // Do not lint because iterator is assigned + let mut s = String::from("Hello, world!"); + let iter = s.drain(0..s.len()); + + // Do not lint because iterator is used + let mut s = String::from("Hello, world!"); + let n = s.drain(0..s.len()).count(); + + // Do not lint because iterator is assigned and used + let mut s = String::from("Hello, world!"); + let iter = s.drain(usize::MIN..s.len()); + let n = iter.count(); + + // Do lint + let mut s = String::from("Hello, world!"); + s.clear(); + + // Do lint + let mut s = String::from("Hello, world!"); + s.clear(); +} +fn string_range_from() { + // Do not lint because iterator is assigned + let mut s = String::from("Hello, world!"); + let iter = s.drain(0..); + + // Do not lint because iterator is assigned and used + let mut s = String::from("Hello, world!"); + let mut iter = s.drain(0..); + let next = iter.next(); + + // Do not lint because iterator is used + let mut s = String::from("Hello, world!"); + let next = s.drain(usize::MIN..).next(); + + // Do lint + let mut s = String::from("Hello, world!"); + s.clear(); + + // Do lint + let mut s = String::from("Hello, world!"); + s.clear(); +} + +fn string_range_full() { + // Do not lint because iterator is assigned + let mut s = String::from("Hello, world!"); + let iter = s.drain(..); + + // Do not lint because iterator is used + let mut s = String::from("Hello, world!"); + for x in s.drain(..) { + let y = format!("x = {x}"); + } + + // Do lint + let mut s = String::from("Hello, world!"); + s.clear(); +} + +fn string_range_to() { + // Do not lint because iterator is assigned + let mut s = String::from("Hello, world!"); + let iter = s.drain(..s.len()); + + // Do not lint because iterator is assigned and used + let mut s = String::from("Hello, world!"); + let iter = s.drain(..s.len()); + for x in iter { + let y = format!("x = {x}"); + } + + // Do lint + let mut s = String::from("Hello, world!"); + s.clear(); +} + +fn string_partial_drains() { + // Do not lint any of these because the ranges are not full + + let mut s = String::from("Hello, world!"); + s.drain(1..); + let mut s = String::from("Hello, world!"); + s.drain(1..).max(); + + let mut s = String::from("Hello, world!"); + s.drain(..s.len() - 1); + let mut s = String::from("Hello, world!"); + s.drain(..s.len() - 1).min(); + + let mut s = String::from("Hello, world!"); + s.drain(1..s.len() - 1); + let mut s = String::from("Hello, world!"); + let w: String = s.drain(1..s.len() - 1).collect(); +} + +fn hash_set() { + // Do not lint because iterator is assigned + let mut set = HashSet::from([1, 2, 3]); + let iter = set.drain(); + + // Do not lint because iterator is assigned and used + let mut set = HashSet::from([1, 2, 3]); + let mut iter = set.drain(); + let next = iter.next(); + + // Do not lint because iterator is used + let mut set = HashSet::from([1, 2, 3]); + let next = set.drain().next(); + + // Do lint let mut set = HashSet::from([1, 2, 3]); set.clear(); +} - let mut a = HashMap::new(); - a.insert(1, "a"); - a.insert(2, "b"); - a.clear(); +fn hash_map() { + // Do not lint because iterator is assigned + let mut map = HashMap::from([(1, "a"), (2, "b")]); + let iter = map.drain(); - let mut heap = BinaryHeap::from([1, 3]); - heap.clear(); + // Do not lint because iterator is assigned and used + let mut map = HashMap::from([(1, "a"), (2, "b")]); + let mut iter = map.drain(); + let next = iter.next(); + + // Do not lint because iterator is used + let mut map = HashMap::from([(1, "a"), (2, "b")]); + let next = map.drain().next(); - // Not firing for now because `String` is not reckognized by `is_type_diagnostic_item` - let mut s = String::from("α is alpha, β is beta"); - s.drain(..); + // Do lint + let mut map = HashMap::from([(1, "a"), (2, "b")]); + map.clear(); } + +fn binary_heap() { + // Do not lint because iterator is assigned + let mut heap = BinaryHeap::from([1, 2]); + let iter = heap.drain(); + + // Do not lint because iterator is assigned and used + let mut heap = BinaryHeap::from([1, 2]); + let mut iter = heap.drain(); + let next = iter.next(); + + // Do not lint because iterator is used + let mut heap = BinaryHeap::from([1, 2]); + let next = heap.drain().next(); + + // Do lint + let mut heap = BinaryHeap::from([1, 2]); + heap.clear(); +} + +fn main() {} diff --git a/tests/ui/clear_with_drain.rs b/tests/ui/clear_with_drain.rs index af2fe503d9a60..4d60ee46e1865 100644 --- a/tests/ui/clear_with_drain.rs +++ b/tests/ui/clear_with_drain.rs @@ -2,108 +2,357 @@ #![allow(unused)] #![warn(clippy::clear_with_drain)] -use std::collections::BinaryHeap; -use std::collections::HashMap; -use std::collections::HashSet; -use std::collections::VecDeque; +use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; -fn range() { +fn vec_range() { + // Do not lint because iterator is assigned let mut v = vec![1, 2, 3]; - let iter = v.drain(0..v.len()); // Yay + let iter = v.drain(0..v.len()); + // Do not lint because iterator is used let mut v = vec![1, 2, 3]; - let n = v.drain(0..v.len()).count(); // Yay + let n = v.drain(0..v.len()).count(); + // Do not lint because iterator is assigned and used let mut v = vec![1, 2, 3]; - let iter = v.drain(usize::MIN..v.len()); // Yay + let iter = v.drain(usize::MIN..v.len()); let n = iter.count(); + // Do lint let mut v = vec![1, 2, 3]; - v.drain(0..v.len()); // Nay + v.drain(0..v.len()); + // Do lint let mut v = vec![1, 2, 3]; - v.drain(usize::MIN..v.len()); // Nay + v.drain(usize::MIN..v.len()); } -fn range_from() { +fn vec_range_from() { + // Do not lint because iterator is assigned let mut v = vec![1, 2, 3]; - let iter = v.drain(0..); // Yay + let iter = v.drain(0..); + // Do not lint because iterator is assigned and used let mut v = vec![1, 2, 3]; - let mut iter = v.drain(0..); // Yay + let mut iter = v.drain(0..); let next = iter.next(); + // Do not lint because iterator is used let mut v = vec![1, 2, 3]; - let next = v.drain(usize::MIN..).next(); // Yay + let next = v.drain(usize::MIN..).next(); + // Do lint let mut v = vec![1, 2, 3]; - v.drain(0..); // Nay + v.drain(0..); + // Do lint let mut v = vec![1, 2, 3]; - v.drain(usize::MIN..); // Nay + v.drain(usize::MIN..); } -fn range_full() { +fn vec_range_full() { + // Do not lint because iterator is assigned let mut v = vec![1, 2, 3]; - let iter = v.drain(..); // Yay + let iter = v.drain(..); + // Do not lint because iterator is used let mut v = vec![1, 2, 3]; - // Yay for x in v.drain(..) { let y = format!("x = {x}"); } + // Do lint let mut v = vec![1, 2, 3]; - v.drain(..); // Nay + v.drain(..); } -fn range_to() { +fn vec_range_to() { + // Do not lint because iterator is assigned let mut v = vec![1, 2, 3]; - let iter = v.drain(..v.len()); // Yay + let iter = v.drain(..v.len()); + // Do not lint because iterator is assigned and used let mut v = vec![1, 2, 3]; - let iter = v.drain(..v.len()); // Yay + let iter = v.drain(..v.len()); for x in iter { let y = format!("x = {x}"); } + // Do lint let mut v = vec![1, 2, 3]; - v.drain(..v.len()); // Nay + v.drain(..v.len()); } -fn partial_drains() { +fn vec_partial_drains() { + // Do not lint any of these because the ranges are not full + let mut v = vec![1, 2, 3]; - v.drain(1..); // Yay + v.drain(1..); let mut v = vec![1, 2, 3]; - v.drain(1..).max(); // Yay + v.drain(1..).max(); let mut v = vec![1, 2, 3]; - v.drain(..v.len() - 1); // Yay + v.drain(..v.len() - 1); let mut v = vec![1, 2, 3]; - v.drain(..v.len() - 1).min(); // Yay + v.drain(..v.len() - 1).min(); let mut v = vec![1, 2, 3]; - v.drain(1..v.len() - 1); // Yay + v.drain(1..v.len() - 1); let mut v = vec![1, 2, 3]; - let w: Vec = v.drain(1..v.len() - 1).collect(); // Yay + let w: Vec = v.drain(1..v.len() - 1).collect(); +} + +fn vec_deque_range() { + // Do not lint because iterator is assigned + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(0..deque.len()); + + // Do not lint because iterator is used + let mut deque = VecDeque::from([1, 2, 3]); + let n = deque.drain(0..deque.len()).count(); + + // Do not lint because iterator is assigned and used + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(usize::MIN..deque.len()); + let n = iter.count(); + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(0..deque.len()); + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(usize::MIN..deque.len()); +} + +fn vec_deque_range_from() { + // Do not lint because iterator is assigned + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(0..); + + // Do not lint because iterator is assigned and used + let mut deque = VecDeque::from([1, 2, 3]); + let mut iter = deque.drain(0..); + let next = iter.next(); + + // Do not lint because iterator is used + let mut deque = VecDeque::from([1, 2, 3]); + let next = deque.drain(usize::MIN..).next(); + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(0..); + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(usize::MIN..); } -fn main() { - let mut deque: VecDeque<_> = [1, 2, 3].into(); +fn vec_deque_range_full() { + // Do not lint because iterator is assigned + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(..); + + // Do not lint because iterator is used + let mut deque = VecDeque::from([1, 2, 3]); + for x in deque.drain(..) { + let y = format!("x = {x}"); + } + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); deque.drain(..); +} + +fn vec_deque_range_to() { + // Do not lint because iterator is assigned + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(..deque.len()); + + // Do not lint because iterator is assigned and used + let mut deque = VecDeque::from([1, 2, 3]); + let iter = deque.drain(..deque.len()); + for x in iter { + let y = format!("x = {x}"); + } + + // Do lint + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(..deque.len()); +} + +fn vec_deque_partial_drains() { + // Do not lint any of these because the ranges are not full + + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(1..); + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(1..).max(); + + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(..deque.len() - 1); + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(..deque.len() - 1).min(); + + let mut deque = VecDeque::from([1, 2, 3]); + deque.drain(1..deque.len() - 1); + let mut deque = VecDeque::from([1, 2, 3]); + let w: Vec = deque.drain(1..deque.len() - 1).collect(); +} + +fn string_range() { + // Do not lint because iterator is assigned + let mut s = String::from("Hello, world!"); + let iter = s.drain(0..s.len()); + + // Do not lint because iterator is used + let mut s = String::from("Hello, world!"); + let n = s.drain(0..s.len()).count(); + + // Do not lint because iterator is assigned and used + let mut s = String::from("Hello, world!"); + let iter = s.drain(usize::MIN..s.len()); + let n = iter.count(); + + // Do lint + let mut s = String::from("Hello, world!"); + s.drain(0..s.len()); + + // Do lint + let mut s = String::from("Hello, world!"); + s.drain(usize::MIN..s.len()); +} + +fn string_range_from() { + // Do not lint because iterator is assigned + let mut s = String::from("Hello, world!"); + let iter = s.drain(0..); + + // Do not lint because iterator is assigned and used + let mut s = String::from("Hello, world!"); + let mut iter = s.drain(0..); + let next = iter.next(); + + // Do not lint because iterator is used + let mut s = String::from("Hello, world!"); + let next = s.drain(usize::MIN..).next(); + // Do lint + let mut s = String::from("Hello, world!"); + s.drain(0..); + + // Do lint + let mut s = String::from("Hello, world!"); + s.drain(usize::MIN..); +} + +fn string_range_full() { + // Do not lint because iterator is assigned + let mut s = String::from("Hello, world!"); + let iter = s.drain(..); + + // Do not lint because iterator is used + let mut s = String::from("Hello, world!"); + for x in s.drain(..) { + let y = format!("x = {x}"); + } + + // Do lint + let mut s = String::from("Hello, world!"); + s.drain(..); +} + +fn string_range_to() { + // Do not lint because iterator is assigned + let mut s = String::from("Hello, world!"); + let iter = s.drain(..s.len()); + + // Do not lint because iterator is assigned and used + let mut s = String::from("Hello, world!"); + let iter = s.drain(..s.len()); + for x in iter { + let y = format!("x = {x}"); + } + + // Do lint + let mut s = String::from("Hello, world!"); + s.drain(..s.len()); +} + +fn string_partial_drains() { + // Do not lint any of these because the ranges are not full + + let mut s = String::from("Hello, world!"); + s.drain(1..); + let mut s = String::from("Hello, world!"); + s.drain(1..).max(); + + let mut s = String::from("Hello, world!"); + s.drain(..s.len() - 1); + let mut s = String::from("Hello, world!"); + s.drain(..s.len() - 1).min(); + + let mut s = String::from("Hello, world!"); + s.drain(1..s.len() - 1); + let mut s = String::from("Hello, world!"); + let w: String = s.drain(1..s.len() - 1).collect(); +} + +fn hash_set() { + // Do not lint because iterator is assigned + let mut set = HashSet::from([1, 2, 3]); + let iter = set.drain(); + + // Do not lint because iterator is assigned and used + let mut set = HashSet::from([1, 2, 3]); + let mut iter = set.drain(); + let next = iter.next(); + + // Do not lint because iterator is used + let mut set = HashSet::from([1, 2, 3]); + let next = set.drain().next(); + + // Do lint let mut set = HashSet::from([1, 2, 3]); set.drain(); +} - let mut a = HashMap::new(); - a.insert(1, "a"); - a.insert(2, "b"); - a.drain(); +fn hash_map() { + // Do not lint because iterator is assigned + let mut map = HashMap::from([(1, "a"), (2, "b")]); + let iter = map.drain(); - let mut heap = BinaryHeap::from([1, 3]); - heap.drain(); + // Do not lint because iterator is assigned and used + let mut map = HashMap::from([(1, "a"), (2, "b")]); + let mut iter = map.drain(); + let next = iter.next(); - // Not firing for now because `String` is not reckognized by `is_type_diagnostic_item` - let mut s = String::from("α is alpha, β is beta"); - s.drain(..); + // Do not lint because iterator is used + let mut map = HashMap::from([(1, "a"), (2, "b")]); + let next = map.drain().next(); + + // Do lint + let mut map = HashMap::from([(1, "a"), (2, "b")]); + map.drain(); +} + +fn binary_heap() { + // Do not lint because iterator is assigned + let mut heap = BinaryHeap::from([1, 2]); + let iter = heap.drain(); + + // Do not lint because iterator is assigned and used + let mut heap = BinaryHeap::from([1, 2]); + let mut iter = heap.drain(); + let next = iter.next(); + + // Do not lint because iterator is used + let mut heap = BinaryHeap::from([1, 2]); + let next = heap.drain().next(); + + // Do lint + let mut heap = BinaryHeap::from([1, 2]); + heap.drain(); } + +fn main() {} diff --git a/tests/ui/clear_with_drain.stderr b/tests/ui/clear_with_drain.stderr index 2c0cc846de2d1..20158da1121b7 100644 --- a/tests/ui/clear_with_drain.stderr +++ b/tests/ui/clear_with_drain.stderr @@ -1,64 +1,130 @@ error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:22:7 + --> $DIR/clear_with_drain.rs:23:7 | -LL | v.drain(0..v.len()); // Nay +LL | v.drain(0..v.len()); | ^^^^^^^^^^^^^^^^^ help: try: `clear()` | = note: `-D clippy::clear-with-drain` implied by `-D warnings` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:25:7 + --> $DIR/clear_with_drain.rs:27:7 | -LL | v.drain(usize::MIN..v.len()); // Nay +LL | v.drain(usize::MIN..v.len()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:40:7 + --> $DIR/clear_with_drain.rs:46:7 | -LL | v.drain(0..); // Nay +LL | v.drain(0..); | ^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:43:7 + --> $DIR/clear_with_drain.rs:50:7 | -LL | v.drain(usize::MIN..); // Nay +LL | v.drain(usize::MIN..); | ^^^^^^^^^^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:57:7 + --> $DIR/clear_with_drain.rs:66:7 | -LL | v.drain(..); // Nay +LL | v.drain(..); | ^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `Vec` - --> $DIR/clear_with_drain.rs:71:7 + --> $DIR/clear_with_drain.rs:83:7 | -LL | v.drain(..v.len()); // Nay +LL | v.drain(..v.len()); | ^^^^^^^^^^^^^^^^ help: try: `clear()` error: `drain` used to clear a `VecDeque` - --> $DIR/clear_with_drain.rs:93:11 + --> $DIR/clear_with_drain.rs:121:11 + | +LL | deque.drain(0..deque.len()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `VecDeque` + --> $DIR/clear_with_drain.rs:125:11 + | +LL | deque.drain(usize::MIN..deque.len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `VecDeque` + --> $DIR/clear_with_drain.rs:144:11 + | +LL | deque.drain(0..); + | ^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `VecDeque` + --> $DIR/clear_with_drain.rs:148:11 + | +LL | deque.drain(usize::MIN..); + | ^^^^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `VecDeque` + --> $DIR/clear_with_drain.rs:164:11 | LL | deque.drain(..); | ^^^^^^^^^ help: try: `clear()` +error: `drain` used to clear a `VecDeque` + --> $DIR/clear_with_drain.rs:181:11 + | +LL | deque.drain(..deque.len()); + | ^^^^^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `String` + --> $DIR/clear_with_drain.rs:219:7 + | +LL | s.drain(0..s.len()); + | ^^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `String` + --> $DIR/clear_with_drain.rs:223:7 + | +LL | s.drain(usize::MIN..s.len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `String` + --> $DIR/clear_with_drain.rs:242:7 + | +LL | s.drain(0..); + | ^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `String` + --> $DIR/clear_with_drain.rs:246:7 + | +LL | s.drain(usize::MIN..); + | ^^^^^^^^^^^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `String` + --> $DIR/clear_with_drain.rs:262:7 + | +LL | s.drain(..); + | ^^^^^^^^^ help: try: `clear()` + +error: `drain` used to clear a `String` + --> $DIR/clear_with_drain.rs:279:7 + | +LL | s.drain(..s.len()); + | ^^^^^^^^^^^^^^^^ help: try: `clear()` + error: `drain` used to clear a `HashSet` - --> $DIR/clear_with_drain.rs:96:9 + --> $DIR/clear_with_drain.rs:317:9 | LL | set.drain(); | ^^^^^^^ help: try: `clear()` error: `drain` used to clear a `HashMap` - --> $DIR/clear_with_drain.rs:101:7 + --> $DIR/clear_with_drain.rs:336:9 | -LL | a.drain(); - | ^^^^^^^ help: try: `clear()` +LL | map.drain(); + | ^^^^^^^ help: try: `clear()` error: `drain` used to clear a `BinaryHeap` - --> $DIR/clear_with_drain.rs:104:10 + --> $DIR/clear_with_drain.rs:355:10 | LL | heap.drain(); | ^^^^^^^ help: try: `clear()` -error: aborting due to 10 previous errors +error: aborting due to 21 previous errors From 32aa07f8323d0f1896376443b389a95f7b141bf3 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 9 Apr 2023 13:25:50 +0200 Subject: [PATCH 76/86] Fix false negative for `Strings` `String` is not a diagnostic item and was thus not picked up by `is_type_diagnostic_item`, leading to a false negative for `collection_is_never_read` --- clippy_lints/src/collection_is_never_read.rs | 18 ++++++++++++------ tests/ui/collection_is_never_read.rs | 20 ++++++++++++++++++++ tests/ui/collection_is_never_read.stderr | 14 +++++++++++++- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index 10f2bef268a24..15a5b7dd27485 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::visitors::for_each_expr_with_closures; use clippy_utils::{get_enclosing_block, get_parent_node, path_to_local_id}; use core::ops::ControlFlow; -use rustc_hir::{Block, ExprKind, HirId, Local, Node, PatKind}; +use rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; @@ -44,7 +44,8 @@ declare_clippy_lint! { } declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]); -static COLLECTIONS: [Symbol; 10] = [ +// Add `String` here when it is added to diagnostic items +static COLLECTIONS: [Symbol; 9] = [ sym::BTreeMap, sym::BTreeSet, sym::BinaryHeap, @@ -52,7 +53,6 @@ static COLLECTIONS: [Symbol; 10] = [ sym::HashSet, sym::LinkedList, sym::Option, - sym::String, sym::Vec, sym::VecDeque, ]; @@ -60,8 +60,7 @@ static COLLECTIONS: [Symbol; 10] = [ impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { // Look for local variables whose type is a container. Search surrounding bock for read access. - let ty = cx.typeck_results().pat_ty(local.pat); - if COLLECTIONS.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym)) + if match_acceptable_type(cx, local, &COLLECTIONS) && let PatKind::Binding(_, local_id, _, _) = local.pat.kind && let Some(enclosing_block) = get_enclosing_block(cx, local.hir_id) && has_no_read_access(cx, local_id, enclosing_block) @@ -71,6 +70,13 @@ impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead { } } +fn match_acceptable_type(cx: &LateContext<'_>, local: &Local<'_>, collections: &[rustc_span::Symbol]) -> bool { + let ty = cx.typeck_results().pat_ty(local.pat); + collections.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym)) + // String type is a lang item but not a diagnostic item for now so we need a separate check + || is_type_lang_item(cx, ty, LangItem::String) +} + fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool { let mut has_access = false; let mut has_read_access = false; diff --git a/tests/ui/collection_is_never_read.rs b/tests/ui/collection_is_never_read.rs index 068a49486cf8a..ca20031bfbefa 100644 --- a/tests/ui/collection_is_never_read.rs +++ b/tests/ui/collection_is_never_read.rs @@ -163,3 +163,23 @@ fn function_argument() { let x = vec![1, 2, 3]; // Ok foo(&x); } + +fn string() { + // Do lint (write without read) + let mut s = String::new(); + s.push_str("Hello, World!"); + + // Do not lint (read without write) + let mut s = String::from("Hello, World!"); + let _ = s.len(); + + // Do not lint (write and read) + let mut s = String::from("Hello, World!"); + s.push_str("foo, bar"); + let _ = s.len(); + + // Do lint the first line, but not the second + let mut s = String::from("Hello, World!"); + let t = String::from("foo, bar"); + s = t; +} diff --git a/tests/ui/collection_is_never_read.stderr b/tests/ui/collection_is_never_read.stderr index 7654b74be3d17..f5dea96116f84 100644 --- a/tests/ui/collection_is_never_read.stderr +++ b/tests/ui/collection_is_never_read.stderr @@ -48,5 +48,17 @@ error: collection is never read LL | let x = vec![1, 2, 3]; // WARNING | ^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 8 previous errors +error: collection is never read + --> $DIR/collection_is_never_read.rs:169:5 + | +LL | let mut s = String::new(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: collection is never read + --> $DIR/collection_is_never_read.rs:182:5 + | +LL | let mut s = String::from("Hello, World!"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors From 423b54ac121038aefb4b9972dbab9cce13e53086 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 9 Apr 2023 13:29:27 +0200 Subject: [PATCH 77/86] Update documentation for `clear_with_drain` The specific type `Vec` is replaced with the generic term 'container' --- clippy_lints/src/methods/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index c25f1b60aa5f6..64bf55ba24c98 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3193,7 +3193,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for usage of `.drain(..)` for the sole purpose of clearing a `Vec`. + /// Checks for usage of `.drain(..)` for the sole purpose of clearing a container. /// /// ### Why is this bad? /// This creates an unnecessary iterator that is dropped immediately. @@ -3213,7 +3213,7 @@ declare_clippy_lint! { #[clippy::version = "1.69.0"] pub CLEAR_WITH_DRAIN, nursery, - "calling `drain` in order to `clear` a `Vec`" + "calling `drain` in order to `clear` a container" } pub struct Methods { From 7852962820cf2cdbedf6a9a27b4c3795c09a1cff Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 9 Apr 2023 13:37:02 +0200 Subject: [PATCH 78/86] Change `format!` style to please dogfood test --- clippy_lints/src/methods/clear_with_drain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints/src/methods/clear_with_drain.rs index 6ce3fdae40749..67ad58d5a8c64 100644 --- a/clippy_lints/src/methods/clear_with_drain.rs +++ b/clippy_lints/src/methods/clear_with_drain.rs @@ -44,7 +44,7 @@ fn suggest(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span) { cx, CLEAR_WITH_DRAIN, span.with_hi(expr.span.hi()), - &format!("`drain` used to clear a `{}`", ty_name), + &format!("`drain` used to clear a `{ty_name}`"), "try", "clear()".to_string(), Applicability::MachineApplicable, From 5109a8a840bab0db9909a9ea3237987de45b28be Mon Sep 17 00:00:00 2001 From: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com> Date: Mon, 10 Apr 2023 00:55:32 +0900 Subject: [PATCH 79/86] [missing_const_for_fn] fix #7121 --- clippy_lints/src/missing_const_for_fn.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 87bd007a26a24..0cde9cb08f8bf 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// can't be const as it calls a non-const function. Making `a` const and running Clippy again, /// will suggest to make `b` const, too. /// + /// If you are marking public function with const, doing opposite will break API compatibility. /// ### Example /// ```rust /// # struct Foo { From ba1f19ee3087f6c47a4ba0f06b21361e1d38d90f Mon Sep 17 00:00:00 2001 From: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com> Date: Mon, 10 Apr 2023 01:31:30 +0900 Subject: [PATCH 80/86] apply code review suggestion Co-authored-by: llogiq --- clippy_lints/src/missing_const_for_fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 0cde9cb08f8bf..f1831a30461af 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -41,7 +41,7 @@ declare_clippy_lint! { /// can't be const as it calls a non-const function. Making `a` const and running Clippy again, /// will suggest to make `b` const, too. /// - /// If you are marking public function with const, doing opposite will break API compatibility. + /// If you are marking a public function with `const`, removing it again will break API compatibility. /// ### Example /// ```rust /// # struct Foo { From d6fe73db3fea55511db95969d13de5e1f5be327e Mon Sep 17 00:00:00 2001 From: The 8472 Date: Mon, 10 Apr 2023 13:13:09 +0200 Subject: [PATCH 81/86] remove unusued feature --- clippy_lints/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index bac82eca81748..b0ec14855e71b 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,7 +1,6 @@ #![feature(array_windows)] #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] -#![feature(drain_filter)] #![feature(if_let_guard)] #![feature(iter_intersperse)] #![feature(let_chains)] From 63030acf4ff453da2beb7f43b13cca8fb6cf425a Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Sun, 12 Mar 2023 14:45:22 +0000 Subject: [PATCH 82/86] Refactor --- clippy_lints/src/collection_is_never_read.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index 15a5b7dd27485..a5feffa310656 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -101,9 +101,9 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc return ControlFlow::Continue(()); } - // Method call on `id` in a statement ignores any return value, so it's not a read access: + // Look for method call with receiver `id`. It might be a non-read access: // - // id.foo(...); // Not reading `id`. + // id.foo(args) // // Only assuming this for "official" methods defined on the type. For methods defined in extension // traits (identified as local, based on the orphan rule), pessimistically assume that they might @@ -111,11 +111,15 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) && let ExprKind::MethodCall(_, receiver, _, _) = parent.kind && path_to_local_id(receiver, id) - && let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) && !method_def_id.is_local() { - return ControlFlow::Continue(()); + // The method call is a statement, so the return value is not used. That's not a read access: + // + // id.foo(args); + if let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) { + return ControlFlow::Continue(()); + } } // Any other access to `id` is a read access. Stop searching. From 008ba7326b315abd1d6ef693d773b88ccb044ba4 Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Sun, 12 Mar 2023 14:56:14 +0000 Subject: [PATCH 83/86] Ignore fake read access --- clippy_lints/src/collection_is_never_read.rs | 9 +++++++++ tests/ui/collection_is_never_read.rs | 3 +-- tests/ui/collection_is_never_read.stderr | 20 +++++++++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index a5feffa310656..5e2eb5789f627 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -120,6 +120,15 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc if let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) { return ControlFlow::Continue(()); } + + // The method call is not a statement, so its return value is used somehow but its type is the + // unit type, so this is not a real read access. Examples: + // + // let y = x.clear(); + // println!("{:?}", x.clear()); + if cx.typeck_results().expr_ty(parent).is_unit() { + return ControlFlow::Continue(()); + } } // Any other access to `id` is a read access. Stop searching. diff --git a/tests/ui/collection_is_never_read.rs b/tests/ui/collection_is_never_read.rs index ca20031bfbefa..4f9431be72a7f 100644 --- a/tests/ui/collection_is_never_read.rs +++ b/tests/ui/collection_is_never_read.rs @@ -85,9 +85,8 @@ fn shadowing_2() { #[allow(clippy::let_unit_value)] fn fake_read() { - let mut x = vec![1, 2, 3]; // Ok + let mut x = vec![1, 2, 3]; // WARNING x.reverse(); - // `collection_is_never_read` gets fooled, but other lints should catch this. let _: () = x.clear(); } diff --git a/tests/ui/collection_is_never_read.stderr b/tests/ui/collection_is_never_read.stderr index f5dea96116f84..cfb392983391e 100644 --- a/tests/ui/collection_is_never_read.stderr +++ b/tests/ui/collection_is_never_read.stderr @@ -25,40 +25,46 @@ LL | let mut x = HashMap::new(); // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:95:5 + --> $DIR/collection_is_never_read.rs:88:5 | LL | let mut x = vec![1, 2, 3]; // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:102:5 + --> $DIR/collection_is_never_read.rs:94:5 | LL | let mut x = vec![1, 2, 3]; // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:119:5 + --> $DIR/collection_is_never_read.rs:101:5 + | +LL | let mut x = vec![1, 2, 3]; // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: collection is never read + --> $DIR/collection_is_never_read.rs:118:5 | LL | let mut x = HashSet::new(); // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:133:5 + --> $DIR/collection_is_never_read.rs:132:5 | LL | let x = vec![1, 2, 3]; // WARNING | ^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:169:5 + --> $DIR/collection_is_never_read.rs:168:5 | LL | let mut s = String::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:182:5 + --> $DIR/collection_is_never_read.rs:181:5 | LL | let mut s = String::from("Hello, World!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 10 previous errors +error: aborting due to 11 previous errors From 3d711455c21fca69cad5de32385da90a4902c67c Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Sun, 12 Mar 2023 14:59:10 +0000 Subject: [PATCH 84/86] Add test case from #10488 --- tests/ui/collection_is_never_read.rs | 8 +++++++- tests/ui/collection_is_never_read.stderr | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/ui/collection_is_never_read.rs b/tests/ui/collection_is_never_read.rs index 4f9431be72a7f..01259a983ab6f 100644 --- a/tests/ui/collection_is_never_read.rs +++ b/tests/ui/collection_is_never_read.rs @@ -84,12 +84,18 @@ fn shadowing_2() { } #[allow(clippy::let_unit_value)] -fn fake_read() { +fn fake_read_1() { let mut x = vec![1, 2, 3]; // WARNING x.reverse(); let _: () = x.clear(); } +fn fake_read_2() { + let mut x = vec![1, 2, 3]; // WARNING + x.reverse(); + println!("{:?}", x.push(5)); +} + fn assignment() { let mut x = vec![1, 2, 3]; // WARNING let y = vec![4, 5, 6]; // Ok diff --git a/tests/ui/collection_is_never_read.stderr b/tests/ui/collection_is_never_read.stderr index cfb392983391e..cf51a53686f2d 100644 --- a/tests/ui/collection_is_never_read.stderr +++ b/tests/ui/collection_is_never_read.stderr @@ -37,34 +37,40 @@ LL | let mut x = vec![1, 2, 3]; // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:101:5 + --> $DIR/collection_is_never_read.rs:100:5 | LL | let mut x = vec![1, 2, 3]; // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:118:5 + --> $DIR/collection_is_never_read.rs:107:5 + | +LL | let mut x = vec![1, 2, 3]; // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: collection is never read + --> $DIR/collection_is_never_read.rs:124:5 | LL | let mut x = HashSet::new(); // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:132:5 + --> $DIR/collection_is_never_read.rs:138:5 | LL | let x = vec![1, 2, 3]; // WARNING | ^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:168:5 + --> $DIR/collection_is_never_read.rs:174:5 | LL | let mut s = String::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: collection is never read - --> $DIR/collection_is_never_read.rs:181:5 + --> $DIR/collection_is_never_read.rs:187:5 | LL | let mut s = String::from("Hello, World!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 11 previous errors +error: aborting due to 12 previous errors From 0266a9e9a36d226031b1012d912624d6876dc06f Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Tue, 11 Apr 2023 14:35:45 +0200 Subject: [PATCH 85/86] Fix links in Clippy book Links to README.md files must actually be links to index.md files, because of the inner workings of `mdbook`. Also use the latest mdbook version in CI. --- .github/workflows/remark.yml | 2 +- book/src/development/basics.md | 4 ++-- book/src/usage.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 81ef072bbb07f..116058b7c7538 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -29,7 +29,7 @@ jobs: - name: Install mdbook run: | mkdir mdbook - curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.18/mdbook-v0.4.18-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook + curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.28/mdbook-v0.4.28-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook echo `pwd`/mdbook >> $GITHUB_PATH # Run diff --git a/book/src/development/basics.md b/book/src/development/basics.md index b07d3de7187e5..7615dc12f9ebe 100644 --- a/book/src/development/basics.md +++ b/book/src/development/basics.md @@ -4,8 +4,8 @@ This document explains the basics for hacking on Clippy. Besides others, this includes how to build and test Clippy. For a more in depth description on the codebase take a look at [Adding Lints] or [Common Tools]. -[Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/adding_lints.md -[Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md +[Adding Lints]: adding_lints.md +[Common Tools]: common_tools_writing_lints.md - [Basics for hacking on Clippy](#basics-for-hacking-on-clippy) - [Get the Code](#get-the-code) diff --git a/book/src/usage.md b/book/src/usage.md index 372a23320d859..32084a9199b73 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -148,4 +148,4 @@ clippy-driver --edition 2018 -Cpanic=abort foo.rs > that are not optimized as expected, for example. [Installation]: installation.md -[CI]: continuous_integration/README.md +[CI]: continuous_integration/index.md From c355e6b9a8f9c92f57788566d075dc36de49f006 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Tue, 11 Apr 2023 15:34:15 +0200 Subject: [PATCH 86/86] Update Cargo.lock --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 321e9cc35c583..78ffa483304aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -830,6 +830,7 @@ dependencies = [ name = "clippy_lints" version = "0.1.70" dependencies = [ + "arrayvec 0.7.0", "cargo_metadata 0.15.3", "clippy_utils", "declare_clippy_lint",